[Dreamhack] Stack buffer overflow

·2024년 3월 11일

Security

목록 보기
6/60

x86 함수 호출 규약

함수호출규약사용 컴파일러인자 전달 방식스택 정리적용
stdcallMSVCStackCalleeWINAPI
cdeclGCC, MSVCStackCaller일반함수
fastcallMSVCECX, EDXCallee최적화된 함수
thiscallMSVCECX(인스턴스), Stack(인자)Callee클래스의 함수

x86 함수 호출 규약

함수호출규약사용 컴파일러인자 전달 방식스택 정리적용
MS ABIMSVCRCX, RDX, R8, R9Caller일반함수, Windows Syscall
System ABIGCCRDI, RSI, RDX, RCX, R8, R9, XMM0-7Caller일반함수

x86-64호출 규약: SYSV

SYSV

  • 리눅스는 SYSTEM V(SYSV) Application Binary Interface(ABI)를 기반으로 만들어짐
    -> ELF 포맷, 링킹 방법, 함수 호출 규약
  • 함수 호출 규약의 특징
    1. 6개의 인자를 RDI, RSI, RDX, RCS, R8, R9에 순서대로 저장하여 전달, 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용
    2. Caller에서 인자 전달에 사용된 스택을 정리함
    3. 함수의 반환 값은 RAX로 전달
$ file /bin/ls
/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV) ...
// Name: sysv.c
// Compile: gcc -fno-asynchronous-unwind-tables  -masm=intel \
//         -fno-omit-frame-pointer -S sysv.c -fno-pic -O0

#define ull unsigned long long

ull callee(ull a1, int a2, int a3, int a4, int a5, int a6, int a7) {
  ull ret = a1 + a2 + a3 + a4 + a5 + a6 + a7;
  return ret;
}

void caller() { callee(123456789123456789, 2, 3, 4, 5, 6, 7); }

int main() { caller(); } 

상세 분석하기

$ gcc -fno-asynchronous-unwind-tables -masm=intel -fno-omit-frame-pointer -o sysv sysv.c -fno-pic -O0

SYSV - 인자전달

$ gdb -q sysv
pwndbg: loaded 139 pwndbg commands and 49 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $ida GDB functions (can be used with print/break)
Reading symbols from sysv...
...
pwndbg> b *caller
Breakpoint 1 at 0x1185
pwndbg> r
Starting program: /home/dreamhack/sysv

Breakpoint 1, 0x0000555555555185 in caller ()
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555185 <caller>       endbr64
   0x555555555189 <caller+4>     push   rbp
   0x55555555518a <caller+5>     mov    rbp, rsp
   0x55555555518d <caller+8>     push   7
   0x55555555518f <caller+10>    mov    r9d, 6
   0x555555555195 <caller+16>    mov    r8d, 5
   0x55555555519b <caller+22>    mov    ecx, 4
   0x5555555551a0 <caller+27>    mov    edx, 3
   0x5555555551a5 <caller+32>    mov    esi, 2
   0x5555555551aa <caller+37>    movabs rax, 0x1b69b4bacd05f15
   0x5555555551b4 <caller+47>    mov    rdi, rax
   0x5555555551b7 <caller+50>    call   0x555555555129 <callee>
   0x5555555551bc <caller+55>    add    rsp,0x8
   
pwndbg> disass caller
...
   0x00005555555551b7 <+50>:  call   0x555555555129 <callee>
...
pwndbg> b *caller+50
Breakpoint 2 at 0x5555555551b7

pwndbg> c
Continuing.

Breakpoint 2, 0x00005555555551b7 in caller ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX  0x1b69b4bacd05f15
 RBX  0x0
*RCX  0x4
*RDX  0x3
*RDI  0x1b69b4bacd05f15
*RSI  0x2
*R8   0x5
*R9   0x6
 R10  0x7ffff7fc3908 ◂— 0xd00120000000e
 R11  0x7ffff7fde680 (_dl_audit_preinit) ◂— endbr64
...

pwndbg> x/4gx $rsp
0x7fffffffe2f8: 0x0000000000000007  0x00007fffffffe310
0x7fffffffe308: 0x00005555555551d5  0x0000000000000001

SYSV - 반환 주소 저장

pwndbg> si
0x00005555555545fa in callee ()
...
pwndbg> x/4gx $rsp
0x7fffffffdf70:	0x0000555555554682	0x0000000000000007
0x7fffffffdf80:	0x00007fffffffdf90	0x0000555555554697
pwndbg> x/10i 0x0000555555554682 - 5
   0x55555555467d <caller+43>:	call   0x5555555545fa <callee>
   0x555555554682 <caller+48>:	add    rsp,0x8

SYSV - 스택 프레임 저장

pwndbg> x/9i $rip
=> 0x555555555129 <callee>:	endbr64
   0x55555555512d <callee+4>:	push   rbp
   0x55555555512e <callee+5>:	mov    rbp,rsp
   0x555555555131 <callee+8>:	mov    QWORD PTR [rbp-0x18],rdi
   0x555555555135 <callee+12>:	mov    DWORD PTR [rbp-0x1c],esi
   0x555555555138 <callee+15>:	mov    DWORD PTR [rbp-0x20],edx
   0x55555555513b <callee+18>:	mov    DWORD PTR [rbp-0x24],ecx
   0x55555555513e <callee+21>:	mov    DWORD PTR [rbp-0x28],r8d
   0x555555555142 <callee+25>:	mov    DWORD PTR [rbp-0x2c],r9d
pwndbg> si
pwndbg> si
0x000055555555512e in callee ()
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555129 <callee>       endbr64
   0x55555555512d <callee+4>     push   rbp
 ► 0x55555555512e <callee+5>     mov    rbp, rsp
   0x555555555131 <callee+8>     mov    qword ptr [rbp - 0x18], rdi
...
pwndbg> x/4gx $rsp
0x7fffffffe2e8: 0x00007fffffffe300  0x00005555555551bc
0x7fffffffe2f8: 0x0000000000000007  0x00007fffffffe310
pwndbg> print $rbp
$1 = (void *) 0x7fffffffe300

SYSV - 스택 프레임 할당

pwndbg> x/5i $rip
=> 0x55555555512e <callee+5>: mov    rbp,rsp
   0x555555555131 <callee+8>: mov    QWORD PTR [rbp-0x18],rdi
   0x555555555135 <callee+12>:  mov    DWORD PTR [rbp-0x1c],esi
   0x555555555138 <callee+15>:  mov    DWORD PTR [rbp-0x20],edx
   0x55555555513b <callee+18>:  mov    DWORD PTR [rbp-0x24],ecx

pwndbg> print $rbp
$2 = (void *) 0x7fffffffe300
pwndbg> print $rsp
$3 = (void *) 0x7fffffffe2e8

pwndbg> si

pwndbg> print $rbp
$4 = (void *) 0x7fffffffe2e8
pwndbg> print $rsp
$5 = (void *) 0x7fffffffe2e8

SYSV - 반환값 전달

pwndbg> b *callee+79
Breakpoint 3 at 0x555555555178
pwndbg> c
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555178 <callee+79>    add    rax, rdx
   0x55555555517b <callee+82>    mov    qword ptr [rbp - 8], rax
   0x55555555517f <callee+86>    mov    rax, qword ptr [rbp - 8]
   0x555555555183 <callee+90>    pop    rbp
   0x555555555184 <callee+91>    ret

pwndbg> b *callee+91
Breakpoint 4 at 0x555555555184
pwndbg> c
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555178 <callee+79>    add    rax, rdx
   0x55555555517b <callee+82>    mov    qword ptr [rbp - 8], rax
   0x55555555517f <callee+86>    mov    rax, qword ptr [rbp - 8]
   0x555555555183 <callee+90>    pop    rbp
 ► 0x555555555184 <callee+91>    ret                                  <0x5555555551bc; caller+55>
    ↓
...

pwndbg> print $rax
$1 = 123456789123456816

SYSV - 반환

pwndbg> d
pwndbg> b *callee+90
Breakpoint 1 at 0x1183
pwndbg> r
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555183 <callee+90>                     pop    rbp
   0x555555555184 <callee+91>                     ret
    ↓
...

pwndbg> si
pwndbg> si
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555183 <callee+90>                     pop    rbp
   0x555555555184 <callee+91>                     ret
    ↓
 ► 0x5555555551bc <caller+55>                     add    rsp, 8
   0x5555555551c0 <caller+59>                     nop
   0x5555555551c1 <caller+60>                     leave
   0x5555555551c2 <caller+61>                     ret
    ↓
...
pwndbg> print $rbp
$1 = (void *) 0x7fffffffe300
pwndbg> print $rip
$2 = (void (*)()) 0x5555555551bc <caller+55>

x86호출 규약 : cdecl

cdecl

  • x86아키텍처는 레지스터의 수가 적어 스택을 통해 인자를 전달함
  • 인자를 전달하기 위해 사용한 스택을 호출자가 정리하는 특징이 있음
  • 스택을 통해 인자를 전달할 때는, 마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 push

Memory Corruption - Stack Buffer Overflow

Buffer Overflow

  • 스택 버퍼 오버플로우 : 스택의 버퍼에서 발생하는 오버플로우
  • 버퍼 : 데이터가 목적지로 이동되기 전 보관되는 임시 저장소
    + 수신 측과 송신 측 사이에 버퍼를 두고 간접적으로 데이터를 전달
    • 스택 버퍼 : 스택에 있는 지역 변수
    • 힙 버퍼 : 힙에 할당된 메모리 영역

버퍼 오버플로우

  • 문자 그대로 버퍼가 넘치는 것
  • 일반적으로 버퍼는 메모리상 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험 有

중요 데이터 변조

  • ex 스택 버퍼 오버플로우 예제
// Name: sbof_auth.c
// Compile: gcc -o sbof_auth sbof_auth.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_auth(char *password) {
    int auth = 0;
    char temp[16];
    
    strncpy(temp, password, strlen(password));
    
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
    
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\n");
        exit(-1);
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}
  • 스택 버퍼 오버플로우 실습
// Name: sbof_auth.c
// Compile: gcc -o sbof_auth sbof_auth.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_auth(char *password) {
    int auth = 0;
    char temp[16];
    
    strncpy(temp, password, strlen(password));
    
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
    
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\n");
        exit(-1);
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}

데이터 유출

  • C언어에서 정상적인 문자열은 널바이트로 종결되며, 표준 문자열 출력 함수들은 널바이트를 문자열의 끝으로 인식

실행 흐름 조작

  • 실제 함수의 반환 주소를 조작하면 프로세스의 실행 흐름을 바꿀 수 있음

Return Address Overwrite

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

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

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}
 

분석

취약점 분석

  • 버퍼를 다루면서 길이를 입력하지 않는 함수들은 대부분 위험함
    ex. strcpy, strcat, sprintf 대신
    -> strncpy, strncat, snprintf, fgets, memcpy 사용
  • src에 널바이트 없을 경우, 문자열 함수는 널바이트를 찾을 때까지 배열을 참조하므로, 코드를 작성할 때 정의한 배열의 크기를 넘어서도 계속해서 인덱스 증가
    -> 참조하려는 인덱스 값이 배열의 크기보다 커지는 현상 : Index Out-Of-Bound (OOB)
    => 해당 버그를 발생시키는 취약점 : Out-Of-Bound(OOB)취약점

트리거

  • 취약점을 발현시킨다는 의미 trigger
// 정상 종료
$ gcc -o rao rao.c -fno-stack-protector -no-pie
$ ./rao
Input: AAAAA
$
// 비정상 종료
$ ./rao
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1]    1828520 segmentation fault (core dumped)  ./rao

코어 파일 분석

  • gdb : 코어 파일 분석하는 기능
$ gdb rao -c core.1828876
...
Could not check ASLR: Couldn't get personality
Core was generated by `./rao'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400729 in main ()
...
pwndbg>
 
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x400729 <main+65>    ret    <0x4141414141414141>
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rsp 0x7fffc86322f8 ◂— 'AAAAAAAA'
01:0008│     0x7fffc8632300 ◂— 0x0
02:0010│     0x7fffc8632308 —▸ 0x4006e8 (main) ◂— push rbp
03:0018│     0x7fffc8632310 ◂— 0x100000000
04:0020│     0x7fffc8632318 —▸ 0x7fffc8632408 —▸ 0x7fffc86326f0 ◂— 0x434c006f61722f2e /* './rao' */
05:0028│     0x7fffc8632320 ◂— 0x0
06:0030│     0x7fffc8632328 ◂— 0x14b87e10e2771087
07:0038│     0x7fffc8632330 —▸ 0x7fffc8632408 —▸ 0x7fffc86326f0 ◂— 0x434c006f61722f2e /* './rao' */

-> segfalut 발생 시 프로그램 상태

익스플로잇

스택 프레임 구조 파악

pwndbg> nearpc
   0x400706             call   printf@plt 

   0x40070b             lea    rax, [rbp - 0x30]
   0x40070f             mov    rsi, rax
   0x400712             lea    rdi, [rip + 0xab]
   0x400719             mov    eax, 0
 ► 0x40071e             call   __isoc99_scanf@plt <__isoc99_scanf @plt>
        format: 0x4007c4 ◂— 0x3b031b0100007325 /* '%s' */
        vararg: 0x7fffffffe2e0 ◂— 0x0
...
pwndbg> x/s 0x4007c4
0x4007c4:       "%s"__isoc99_scanf

-> scanf() 함수를 호출하는 어셈블리 코드

profile
Whatever I want | Interested in DFIR, Security, Infra, Cloud

0개의 댓글