LyScript 插件中针对内存读写函数的封装功能并不多,只提供了最基本的内存读取内存写入系列函数的封装,本章将继续对API接口进行封装,实现一些在软件逆向分析中非常实用的功能,例如ShellCode代码写出与置入,内存交换,内存区域对比,磁盘与内存镜像比较,内存特征码检索等功能,学会使用这些功能对于后续漏洞分析以及病毒分析都可以起到事半功倍的效果,读者应重点关注这些函数的使用方式。

4.9.1 实现ShellCode的灵活注入

Shellcode 是一种特殊类型的恶意代码,通常用于利用系统漏洞、执行恶意软件等攻击性行为。Shellcode 通常是一段二进制代码,没有可执行文件头,并且设计用于利用操作系统的特定漏洞,从而使攻击者能够获得对系统的控制或执行其他恶意操作。

由于 Shellcode 通常是在内存中执行的,因此它不需要像可执行文件一样拥有完整的文件结构。攻击者通常通过利用漏洞将Shellcode注入到受攻击系统的进程中,并使其在内存中执行,从而达到攻击目的。由于Shellcode是一种非常灵活的攻击工具,攻击者可以使用它来执行各种攻击行为,例如提权、执行远程命令、下载恶意软件等。因此,Shellcode已成为黑客和攻击者的常用工具之一。

通常读者应该自行准备好如下文本案例中所规范的ShellCode格式,这类格式通常经过处理后即可直接注入到远程进程内。

"\xfc\xe8\x8f\x00\x00\x00\x60\x31\xd2\x89\xe5\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x0f\xb7\x4a\x26\x31\xff\x8b\x72\x28"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\x49"
"\x75\xef\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78"
"\x85\xc0\x74\x4c\x01\xd0\x8b\x58\x20\x01\xd3\x50\x8b\x48\x18"
"\x85\xc9\x74\x3c\x31\xff\x49\x8b\x34\x8b\x01\xd6\x31\xc0\xac"

接着笔者将带大家实现一个将文件内的ShellCode注入到进程远程堆空间内的案例,既然要注入到远程堆中,那么第一步则是通过create_alloc(1024)在对端开辟一段堆空间,如果读者需要让该空间可被执行则需要调用set_local_protect(address,32,1024)将该地址设置为32也就是读写执行,设置长度为1024字节,接着通过read_shellcode()函数从文本中读取ShellCode代码,并作压缩处理,最后通过循环write_memory_byte写内存的方式将其逐字节写出,总结起来核心代码如下所示;

from LyScript32 import MyDebug

# 将shellcode读入内存
def read_shellcode(path):
shellcode_list = []
with open(path, "r", encoding="utf-8") as fp:
for index in fp.readlines():
shellcode_line = index.replace('"', "").replace(" ", "").replace("\n", "").replace(";", "")
for code in shellcode_line.split("\\x"):
if code != "" and code != "\\n":
shellcode_list.append("0x" + code)
return shellcode_list if __name__ == "__main__":
dbg = MyDebug()
dbg.connect() # 开辟堆空间
address = dbg.create_alloc(1024)
print("开辟堆空间: {}".format(hex(address)))
if address == False:
exit() # 设置内存可执行属性
dbg.set_local_protect(address, 32, 1024) # 从文本中读取shellcode
shellcode = read_shellcode("c://shellcode.txt") # 循环写入到内存
for code_byte in range(0, len(shellcode)):
bytef = int(shellcode[code_byte], 16)
dbg.write_memory_byte(code_byte + address, bytef) # 设置EIP位置
dbg.set_register("eip", address) input()
dbg.delete_alloc(address) dbg.close()

运行这段程序,则读者应该能看到如下图所示的输出结果,这说明我们的数据已经写出到对端堆中了;

而有时我们还需要将这段代码反写,将一段我们挑选好的指令集保存到本地,此时就需要使用read_memory_byte依次循环读入数据,并动态写出到文件中,代码如下所示;

from LyScript32 import MyDebug

# 将特定内存保存到文本中
def write_shellcode(dbg,address,size,path):
with open(path,"a+",encoding="utf-8") as fp:
for index in range(0, size - 1):
# 读取机器码
read_code = dbg.read_memory_byte(address + index) if (index+1) % 16 == 0:
print("\\x" + str(read_code))
fp.write("\\x" + str(read_code) + "\n")
else:
print("\\x" + str(read_code),end="")
fp.write("\\x" + str(read_code)) if __name__ == "__main__":
dbg = MyDebug()
dbg.connect() eip = dbg.get_register("eip")
write_shellcode(dbg,eip,128,"d://shellcode.txt")
dbg.close()

如上代码运行后,并可将EIP位置出的指令集前128字节动态写出到d://shellcode.txt文件内,输出效果图如下图所示;

4.9.2 内存区域交换与对比

区域交换的原理是通过第三方变量依次交换内存两端的数据,例如将如下图中的0x5B00100x5B0070的前四个字节进行交换,则可调用memory_xchage(dbg, 5963792,5963792,4)传递参数实现,在调用前该内存区域如下图所示;

通过运行如下代码片段,则可实现数据交换。

from LyScript32 import MyDebug

# 交换两个内存区域
def memory_xchage(dbg,memory_ptr_x,memory_ptr_y,bytes):
ref = False
for index in range(0,bytes):
# 读取两个内存区域
read_byte_x = dbg.read_memory_byte(memory_ptr_x + index)
read_byte_y = dbg.read_memory_byte(memory_ptr_y + index) # 交换内存
ref = dbg.write_memory_byte(memory_ptr_x + index,read_byte_y)
ref = dbg.write_memory_byte(memory_ptr_y + index, read_byte_x)
return ref if __name__ == "__main__":
dbg = MyDebug()
dbg.connect() eip = dbg.get_register("eip") # 内存交换
flag = memory_xchage(dbg, 5963792,5963888,4)
print("内存交换状态: {}".format(flag))
dbg.close()

交换后的内存区域如下图所示;

4.9.3 内存与磁盘机器码对比

在某些时候我们还需要对比某个特定程序内存与磁盘之间的数据差异,这类需求的实现前提是实现两个特殊的读写函数,一般而言get_memory_hex_ascii函数可用于读出内存中的机器码数据,而get_file_hex_ascii则可用于读出磁盘中的机器码数据,将两者最进一步对比从而获取某些字节是否发生了改变。

首先实现get_memory_hex_ascii函数,该函数用于从给定的内存地址开始,读取指定长度的二进制数据,并将其转换为十六进制形式输出。具体解释如下:

  • 函数接收三个参数:内存地址address,偏移量offset,和要读取的长度len。
  • 定义变量count用于计算已经读取的字节数,并定义ref_memory_list用于存储读取的数据的十六进制形式。
  • 使用for循环读取指定范围内的二进制数据。
  • 调用dbg.read_memory_byte方法读取内存中的每个字节,并将其赋值给变量char。
  • 将读取的字节的十六进制表示输出到控制台。
  • 将读取的字节的十六进制形式存储到ref_memory_list列表中。
  • 如果已经读取了16个字节,就换行输出。
  • 如果字节的十六进制表示只有一位,则在前面添加一个0以保证两个字符宽度。
  • 最后返回ref_memory_list列表,包含了所有读取字节的十六进制形式。
# 得到程序的内存镜像中的机器码
def get_memory_hex_ascii(address,offset,len):
count = 0
ref_memory_list = []
for index in range(offset,len):
# 读出数据
char = dbg.read_memory_byte(address + index)
count = count + 1 if count % 16 == 0:
if (char) < 16:
print("0" + hex((char))[2:])
ref_memory_list.append("0" + hex((char))[2:])
else:
print(hex((char))[2:])
ref_memory_list.append(hex((char))[2:])
else:
if (char) < 16:
print("0" + hex((char))[2:] + " ",end="")
ref_memory_list.append("0" + hex((char))[2:])
else:
print(hex((char))[2:] + " ",end="")
ref_memory_list.append(hex((char))[2:])
return ref_memory_list

其次实现get_memory_hex_ascii函数,该函数用于从给定的文件路径中读取指定长度的二进制数据,并将其转换为十六进制形式输出。具体解释如下:

  • 函数接收三个参数:文件路径path,偏移量offset,和要读取的长度len。
  • 定义变量count用于计算已经读取的字节数,并定义ref_file_list用于存储读取的数据的十六进制形式。
  • 使用with open语句打开指定路径的文件,并使用rb模式以二进制方式读取。
  • 使用fp.seek方法将文件指针移动到指定的偏移量offset处。
  • 使用for循环读取指定长度的二进制数据。
  • 使用fp.read(1)方法读取一个字节的数据,并将其赋值给变量char。
  • 将读取的字节的十六进制表示输出到控制台。
  • 将读取的字节的十六进制形式存储到ref_file_list列表中。
  • 如果已经读取了16个字节,就换行输出。
  • 如果字节的十六进制表示只有一位,则在前面添加一个0以保证两个字符宽度。
  • 最后返回ref_file_list列表,包含了所有读取字节的十六进制形式。
# 读取程序中的磁盘镜像中的机器码
def get_file_hex_ascii(path,offset,len):
count = 0
ref_file_list = [] with open(path, "rb") as fp:
# file_size = os.path.getsize(path)
fp.seek(offset) for item in range(offset,offset + len):
char = fp.read(1)
count = count + 1
if count % 16 == 0:
if ord(char) < 16:
print("0" + hex(ord(char))[2:])
ref_file_list.append("0" + hex(ord(char))[2:])
else:
print(hex(ord(char))[2:])
ref_file_list.append(hex(ord(char))[2:])
else:
if ord(char) < 16:
print("0" + hex(ord(char))[2:] + " ", end="")
ref_file_list.append("0" + hex(ord(char))[2:])
else:
print(hex(ord(char))[2:] + " ", end="")
ref_file_list.append(hex(ord(char))[2:])
return ref_file_list

有了这两个函数读者就可以实现依次输出内存与磁盘中的机器码功能,

import binascii,os,sys
from LyScript32 import MyDebug # 得到程序的内存镜像中的机器码
def get_memory_hex_ascii(address,offset,len):
pass # 读取程序中的磁盘镜像中的机器码
def get_file_hex_ascii(path,offset,len):
pass if __name__ == "__main__":
dbg = MyDebug() connect_flag = dbg.connect()
print("连接状态: {}".format(connect_flag)) module_base = dbg.get_base_from_address(dbg.get_local_base())
print("模块基地址: {}".format(hex(module_base))) # 得到内存机器码
memory_hex_byte = get_memory_hex_ascii(module_base,0,100) # 得到磁盘机器码
file_hex_byte = get_file_hex_ascii("d://lyshark.exe",0,100) # 输出机器码
print("\n内存机器码: ",memory_hex_byte)
print("\n磁盘机器码: ",file_hex_byte) dbg.close()

如上代码片段的输出效果如下图所示,分别得到该进程的内存与磁盘机器码格式,取前100个字节作比较;

至于如何做对比,读者只需要通过for循环输出其参数即可得到,这里就不做截图演示了,效果同理;

    # 输出机器码
for index in range(0,len(memory_hex_byte)):
# 比较磁盘与内存是否存在差异
if memory_hex_byte[index] != file_hex_byte[index]:
# 存在差异则输出
print("\n相对位置: [{}] --> 磁盘字节: 0x{} --> 内存字节: 0x{}".
format(index,memory_hex_byte[index],file_hex_byte[index]))
dbg.close()

4.9 x64dbg 内存处理与差异对比的更多相关文章

  1. LyScript 内存交换与差异对比

    LyScript 针对内存读写函数的封装功能并不多,只提供了内存读取和内存写入函数的封装,本篇文章将继续对API进行封装,实现一些在软件逆向分析中非常实用的功能,例如内存交换,内存区域对比,磁盘与内存 ...

  2. Atitit 硬件 软件 的开源工作 差异对比

    Atitit 硬件 软件 的开源工作 差异对比 1.1. 模块化,标准化,以及修改的便捷性1 1.2. 生产和发布成本 1 1.3.   3. 入行门槛搞2 1.4.  在软件业极度发达的今天,任何具 ...

  3. SeaJS 与 RequireJS 的差异对比

    这篇文章主要介绍了SeaJS 与 RequireJS 的差异对比,本文主要对CMD规范和AMD规范的弊端做了对比,并做出了一个总结,需要的朋友可以参考下 “历史不是过去,历史正在上演.随着 W3C 等 ...

  4. Python 数据库之间差异对比

    参考资料: Python 集合(set)   此脚本用于两个数据库之间的表.列.栏位.索引的差异对比. cat oracle_diff.py #!/home/dba/.pyenv/versions/3 ...

  5. 016——VUE中v-show的使用与v-if的差异对比

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. Python自动化运维——文件内容差异对比

    Infi-chu: http://www.cnblogs.com/Infi-chu/ 模块:difflib 安装:Python版本大于等于2.3系统自带 功能:对比文本之间的差异,而且支持输出可读性比 ...

  7. 16.VUE学习之-v-show的使用与v-if的差异对比

    v-show的使用与v-if的差异对比 相同点: 都可以达到隐藏和显示的效果. 不同点: v-show 会用display:none 来隐藏元素节点,推荐使用这种方式 v-if 会移除节点,可以配合v ...

  8. 类似于SVN的文档内容差异对比工具winmerge

    原文:http://www.jianshu.com/p/99282a4f3870 https://sourceforge.net/projects/winmerge/?source=typ_redir ...

  9. Git 和 SVN 存储方式的差异对比

    Git git 对于一个文件的修改存储的是一个快照,就是说针对文件1,修改之后,生成文件2,文件2中包含文件的1的内容,如果当文件1不存在,版本回退也就不管用了. SVN SVN 存储的是对文件的差异 ...

  10. vue(element)中使用codemirror实现代码高亮,代码补全,版本差异对比

    vue(element)中使用codemirror实现代码高亮,代码补全,版本差异对比 使用的是vue语言,用element的组件,要做一个在线编辑代码,要求输入代码内容,可以进行高亮展示,可以切换各 ...

随机推荐

  1. 勇者游戏(巴什博弈)- HDU 1846

    博弈算法中比较经典的一个博弈问题是巴什博奕,巴什博弈是这样的: 有一堆物品,数量为n,两个人轮流取,规定每次最多取m个,最少取1个. 我们定义先取者为F(first),后取者为S(second) 网上 ...

  2. Redis 也支持全文搜索 了?这也太强了

    在 2021 年我就了解到 RediSearch 这个项目,并已经把它用于我的开源项目 newbee-mall-pro 中. 就我的使用体验来说,简单场景下,用来平替 Elasticsearch 的使 ...

  3. Apache ShardingSphere 实现分库分表及读写分离

    本文为博主原创,未经允许不得转载: 项目demo 源码地址:https://gitee.com/xiangbaxiang/apache-shardingjdbc 1. 创建Maven项目,并配置 po ...

  4. zipkin 与 sleuth 实现链路追踪

    本文为博主原创,转载请注明出处 1.Zipkin 与 Sleuth 简介 zipkin 的官网地址: https://zipkin.io/ Zipkin 和 Sleuth 都是由 Twitter 开源 ...

  5. for-each循环使用iterator进行遍历

    示例如下: public static void main(String args[]) { List list = new LinkedList(); list.add("aa" ...

  6. 【转】嵌入式C语言代码优化方案

    来源:嵌入式C语言代码优化方案(深度好文,建议花时间研读并收藏) (qq.com) 1.选择合适的算法和数据结构 选择一种合适的数据结构很重要,如果在一堆随机存放的数中使用了大量的插入和删除指令,那使 ...

  7. 【rt-thread】SConscript文件添加格式必须是4空格开头

    SConscript文件添加格式必须是4空格开头,TAB或其他数量的空格均错误

  8. STM32 芯片锁死解决方法

    芯片锁死原因: 1.烧进去的工程对应器件与目标器件不一致: 2.烧进去的工程HSE_VALUE与目标板上晶振频率不一致: 3.... 解决方法: 1.工程设置 2.按住复位按键,或短接复位脚电容,点击 ...

  9. FileZilla 连接不上宝塔

    1,修改 pureftp的配置文件 ForcePassiveIP 为服务器的ip , 并去掉 # 2,FileZilla 使用明文连接

  10. JMS微服务架构 - 关于事务提交失败,自动重新提交的机制

    用JMS编写的微服务,由调用端决定了各个微服务执行时,是否需要保持事务的一致性. 也就是RemoteClient在调用微服务方法前,先调用BeginTransaction明确后面所调用的微服务需要保持 ...