4.8 x64dbg 学会扫描应用堆栈

简介: LyScript 插件中提供了针对堆栈的操作函数,对于堆的开辟与释放通常可使用`create_alloc()`及`delete_alloc()`在之前的文章中我们已经使用了堆创建函数,本章我们将重点学习针对栈的操作函数,栈操作函数有三种,其中`push_stack`用于入栈,`pop_stack`用于出栈,而最有用的还属`peek_stack`函数,该函数可用于检查指定堆栈位置处的内存参数,利用这个特性就可以实现,对堆栈地址的检测,或对堆栈的扫描等。

堆栈是计算机中的两种重要数据结构 堆(Heap)和栈(Stack)它们在计算机程序中起着关键作用,在内存中堆区(用于动态内存分配)和栈区(用于存储函数调用、局部变量等临时数据),进程在运行时会使用堆栈进行参数传递,这些参数包括局部变量,临时空间以及函数切换时所需要的栈帧等。

  • 栈(Stack)是一种遵循后进先出(LIFO)原则的线性数据结构。它主要用于存储和管理程序中的临时数据,如函数调用和局部变量。栈的主要操作包括压栈(添加元素)和弹栈(移除元素)。
  • 堆(Heap)是一种树形数据结构,通常用于实现优先队列。堆中的每个节点都有一个键值(key),并满足特定性质。最常见的堆类型是二叉堆(包括最大堆和最小堆)。堆在计算机程序中的应用包括堆排序算法和内存管理等。

而针对栈地址的分析在漏洞挖掘中尤为重要,栈溢出(Stack Overflow)是一种计算机程序中的运行时错误,通常发生在缓冲区(buffer)中。缓冲区是一段内存空间,用于临时存储数据。当程序试图向栈中写入过多数据时,可能导致栈溢出,从而破坏其他内存区域或导致程序崩溃,严重的则可能会导致黑客控制EIP指针,而执行恶意代码。

栈溢出的原因主要有以下几点:

  • 递归调用过深:当函数递归调用自身的层次过深时,可能导致栈溢出。这是因为每次函数调用都会在栈中分配内存,用于存储函数的局部变量和返回地址。如果递归层数太多,可能导致栈空间不足,从而引发栈溢出。

  • 局部变量占用过多栈空间:如果函数中的局部变量(尤其是数组和结构体)占用过多栈空间,可能导致栈溢出。这种情况下,可以考虑将部分局部变量移到堆内存中,以减小栈空间的压力。

  • 缓冲区溢出:当程序向缓冲区写入的数据超过其分配的空间时,可能发生缓冲区溢出。这种溢出可能导致栈空间中的其他数据被破坏,从而引发栈溢出。

LyScript 插件中提供了针对堆栈的操作函数,对于堆的开辟与释放通常可使用create_alloc()delete_alloc()在之前的文章中我们已经使用了堆创建函数,本章我们将重点学习针对栈的操作函数,栈操作函数有三种,其中push_stack用于入栈,pop_stack用于出栈,而最有用的还属peek_stack函数,该函数可用于检查指定堆栈位置处的内存参数,利用这个特性就可以实现,对堆栈地址的检测,或对堆栈的扫描等。

读者注意:由于peek_stack命令传入的堆栈下标位置默认从0开始,而输出的结果则一个十进制有符号长整数,一般而言有符号数会出现复数的情形,读者在使用时应更具自己的需求自行转换。

而针对有符号与无符号数的转换也很容易实现,long_to_ulong函数用于将有符号整数转换为无符号整数(long_to_ulong)而与之对应的ulong_to_long函数,则用于将无符号整数转换为有符号整数(ulong_to_long)。这些函数都接受一个整数参数(inter)和一个布尔参数(is_64)。当 is_64False 时,函数处理32位整数;当 is_64True 时,函数处理64位整数。

  • 有符号整数转无符号数(long_to_ulong):通过将输入整数与相应位数的最大值执行按位与操作(&)来实现转换。对于32位整数,使用 (1 << 32) - 1 计算最大值;对于64位整数,使用 (1 << 64) - 1 计算最大值。

  • 无符号整数转有符号数(ulong_to_long):通过计算输入整数与相应位数的最高位的差值来实现转换。首先,它使用按位与操作(&)来计算输入整数与最高位之间的关系。对于32位整数,使用 (1 << 31) - 1 和 (1 << 31);对于64位整数,使用(1 << 63) - 1(1 << 63)。然后,将这两个结果相减以获得有符号整数。

from LyScript32 import MyDebug

# 有符号整数转无符号数
def long_to_ulong(inter,is_64 = False):
    if is_64 == False:
        return inter & ((1 << 32) - 1)
    else:
        return inter & ((1 << 64) - 1)

# 无符号整数转有符号数
def ulong_to_long(inter,is_64 = False):
    if is_64 == False:
        return (inter & ((1 << 31) - 1)) - (inter & (1 << 31))
    else:
        return (inter & ((1 << 63) - 1)) - (inter & (1 << 63))

if __name__ == "__main__":
    dbg = MyDebug()

    connect_flag = dbg.connect()
    print("连接状态: {}".format(connect_flag))

    for index in range(0,10):

        # 默认返回有符号数
        stack_address = dbg.peek_stack(index)

        # 使用转换
        print("默认有符号数: {:15} --> 转为无符号数: {:15} --> 转为有符号数: {:15}".
              format(stack_address, long_to_ulong(stack_address),ulong_to_long(long_to_ulong(stack_address))))

    dbg.close()

如上代码中我们在当前堆栈中向下扫描10条,并通过转换函数以此输出该堆栈信息的有符号与无符号形式,这段代码输出效果如下图所示;

我们继续完善这个功能,通过使用get_disasm_one_code()获取到堆栈的反汇编代码,并以此来进行更多的判断形势,如下代码中只需要增加反汇编一行功能即可。

if __name__ == "__main__":
    dbg = MyDebug()

    connect_flag = dbg.connect()
    print("连接状态: {}".format(connect_flag))

    for index in range(0,10):

        # 默认返回有符号数
        stack_address = dbg.peek_stack(index)

        # 反汇编一行
        dasm = dbg.get_disasm_one_code(stack_address)

        # 根据地址得到模块基址
        if stack_address <= 0:
            mod_base = 0
        else:
            mod_base = dbg.get_base_from_address(long_to_ulong(stack_address))

        print("stack => [{}] addr = {:10} base = {:10} dasm = {}".format(index, hex(long_to_ulong(stack_address)),hex(mod_base), dasm))

    dbg.close()

运行上代码,将自动扫描前十行堆栈中的反汇编指令,并输出如下图所示的功能;

如上图我们可以得到堆栈处的反汇编参数,但如果我们需要检索堆栈特定区域内是否存在返回到模块的地址,该如何实现呢?

该功能的实现其实很简单,首先需要得到程序全局状态下的所有加载模块的基地址,然后得到当前堆栈内存地址内的实际地址,并通过实际内存地址得到模块基址,对比全局表即可拿到当前模块是返回到了哪个模块的。

if __name__ == "__main__":
    dbg = MyDebug()

    connect_flag = dbg.connect()
    print("连接状态: {}".format(connect_flag))

    # 得到程序加载过的所有模块信息
    module_list = dbg.get_all_module()

    # 向下扫描堆栈
    for index in range(0,10):

        # 默认返回有符号数
        stack_address = dbg.peek_stack(index)

        # 反汇编一行
        dasm = dbg.get_disasm_one_code(stack_address)

        # 根据地址得到模块基址
        if stack_address <= 0:
            mod_base = 0
        else:
            mod_base = dbg.get_base_from_address(long_to_ulong(stack_address))

        # print("stack => [{}] addr = {:10} base = {:10} dasm = {}".format(index, hex(long_to_ulong(stack_address)),hex(mod_base), dasm))
        if mod_base > 0:
            for x in module_list:
                if mod_base == x.get("base"):
                    print("stack => [{}] addr = {:10} base = {:10} dasm = {:15} return = {:10}"
                          .format(index,hex(long_to_ulong(stack_address)),hex(mod_base), dasm,
                                  x.get("name")))

    dbg.close()

运行如上代码片段,则会输出如下图所示的堆栈返回位置;

原文地址

https://www.lyshark.com/post/9cc1e0b7.html

目录
相关文章
|
2月前
|
Linux
使用backtrace打印程序crash堆栈
使用backtrace打印程序crash堆栈
20 0
|
2月前
|
存储 算法 C++
四则计算机实现(C++)(堆栈的应用)
四则计算机实现(C++)(堆栈的应用)
|
12月前
如何定位strace中系统调用在内核中的位置
要了解内核函数的含义,最好的方法,就是去查询所用内核版本的源代码。
28 0
|
8月前
|
缓存 Linux Go
如何初步使用valgrind工具来检测内存泄露,堆栈空间,未初始化变量问题
如何初步使用valgrind工具来检测内存泄露,堆栈空间,未初始化变量问题
|
12月前
|
存储 安全 Java
4.7 x64dbg 应用层的钩子扫描
所谓的应用层钩子(Application-level hooks)是一种编程技术,它允许应用程序通过在特定事件发生时执行特定代码来自定义或扩展其行为。这些事件可以是用户交互,系统事件,或者其他应用程序内部的事件。应用层钩子是在应用程序中添加自定义代码的一种灵活的方式。它们可以用于许多不同的用途,如安全审计、性能监视、访问控制和行为修改等。应用层钩子通常在应用程序的运行时被调用,可以执行一些预定义的操作或触发一些自定义代码。
95 0
4.7 x64dbg 应用层的钩子扫描
|
12月前
|
编译器 数据安全/隐私保护 开发者
4.6 x64dbg 内存扫描与查壳实现
LyScript 插件中默认提供了多种内存特征扫描函数,每一种扫描函数用法各不相同,在使用扫描函数时应首先搞清楚不同函数之间的差异,本章内容将分别详细介绍每一种内存扫描函数是如何灵活运用,并实现一种内存查壳脚本,可快速定位目标程序加了什么壳以及寻找被加壳程序的入口点。软件查壳的实现原理可以分为静态分析和动态分析两种方式。静态分析是指在不运行被加壳程序的情况下,通过对程序的二进制代码进行解析,识别出程序是否被加壳,以及加壳的种类和方法。动态分析是指通过运行被加壳程序,并观察程序运行时的行为,识别程序是否被加壳,以及加壳的种类和方法。
119 0
|
12月前
|
存储 安全 算法
4.2 x64dbg 针对PE文件的扫描
通过运用`LyScript`插件并配合`pefile`模块,即可实现对特定PE文件的扫描功能,例如载入PE程序到内存,验证PE启用的保护方式,计算PE节区内存特征,文件FOA与内存VA转换等功能的实现,首先简单介绍一下`pefile`模块。pefile模块是一个用于解析Windows可执行文件(PE文件)的Python模块,它可以从PE文件中提取出文件头、节表、导入表、导出表、资源表等信息,也可以修改PE文件的一些属性。可以用于分析针对Windows平台的恶意软件、编写自己的PE文件修改工具等场景。
106 0
4.2 x64dbg 针对PE文件的扫描
|
12月前
|
存储 安全
4.3 x64dbg 搜索内存可利用指令
发现漏洞的第一步则是需要寻找到可利用的反汇编指令片段,在某些时候远程缓冲区溢出需要通过类似于`jmp esp`等特定的反汇编指令实现跳转功能,并以此来执行布置好的`ShellCode`恶意代码片段,`LyScript`插件则可以很好的完成对当前进程内存中特定函数的检索工作。在远程缓冲区溢出攻击中,攻击者也可以利用汇编指令`jmp esp`来实现对攻击代码的执行。该指令允许攻击者跳转到堆栈中的任意位置,并从那里执行恶意代码。
140 0
|
Linux
内核笔记](四)——内核常见调试手段(printf、dump_stack、devmem)
内核笔记](四)——内核常见调试手段(printf、dump_stack、devmem)
206 0
内核笔记](四)——内核常见调试手段(printf、dump_stack、devmem)
LyScript 实现对内存堆栈扫描
LyScript插件中提供了三种基本的堆栈操作方法,其中`push_stack`用于入栈,`pop_stack`用于出栈,而最有用的是`peek_stack`函数,该函数可用于检查指定堆栈位置处的内存参数,利用这个特性就可以实现,对堆栈地址的检测,或对堆栈的扫描等。
226 0
LyScript 实现对内存堆栈扫描

热门文章

最新文章

  • 1
    流量控制系统,用正则表达式提取汉字
    25
  • 2
    Redis09-----List类型,有序,元素可以重复,插入和删除快,查询速度一般,一般保存一些有顺序的数据,如朋友圈点赞列表,评论列表等,LPUSH user 1 2 3可以一个一个推
    26
  • 3
    Redis08命令-Hash类型,也叫散列,其中value是一个无序字典,类似于java的HashMap结构,Hash结构可以将对象中的每个字段独立存储,可以针对每字段做CRUD
    26
  • 4
    Redis07命令-String类型字符串,不管是哪种格式,底层都是字节数组形式存储的,最大空间不超过512m,SET添加,MSET批量添加,INCRBY age 2可以,MSET,INCRSETEX
    27
  • 5
    S外部函数可以访问函数内部的变量的闭包-闭包最简单的用不了,闭包是内层函数+外层函数的变量,简称为函数套函数,外部函数可以访问函数内部的变量,存在函数套函数
    24
  • 6
    Redis06-Redis常用的命令,模糊的搜索查询往往会对服务器产生很大的压力,MSET k1 v1 k2 v2 k3 v3 添加,DEL是删除的意思,EXISTS age 可以用来查询是否有存在1
    30
  • 7
    Redis05数据结构介绍,数据结构介绍,官方网站中看到
    22
  • 8
    JS字符串数据类型转换,字符串如何转成变量,+号只要有一个是字符串,就会把另外一个转成字符串,- * / 都会把数据转成数字类型,数字型控制台是蓝色,字符型控制台是黑色,
    20
  • 9
    JS数组操作---删除,arr.pop()方法从数组中删除最后一个元素,并返回该元素的值,arr.shift() 删除第一个值,arr.splice()方法,删除指定元素,arr.splice,从第一
    20
  • 10
    定义好变量,${age}模版字符串,对象可以放null,检验数据类型console.log(typeof str)
    19