首页
社区
课程
招聘
[原创]ELF GOT Hook 实战
发表于: 2天前 735

[原创]ELF GOT Hook 实战

2天前
735

本文仅用于安全研究与技术学习,请勿用于任何违法用途。

最近沉迷某游戏,排位连跪,手机正好有 root,于是开始研究内核G。找到一个好用的G,但卡密高达 一天 8 元。本着「能动手就别动钱包」的原则,决定研究如何破解卡密验证。

拿到目标文件(一个 .sh 脚本),直接拖入 IDA Pro,提示不是 ELF 格式图片描述用十六进制编辑器打开 图片描述发现文件头部特征是经典的 gzexe 压缩加密 —— 这是一种 Linux 下常见的可执行文件压缩方式,本质上是把 ELF 用 gzip 压缩后包裹在一个 shell 脚本里。

解压后得到真正的 ELF 二进制,重新拖入 IDA Pro。

IDA 分析结果显示:

既然静态分析困难,转向动态分析。手机端抓包发现卡密验证请求指向: 图片描述

关键参数:app=51642 —— 这是该挂在卡密验证平台上的 App ID。

该挂使用的是微卡验证平台。恰好我也有这个平台的账号,可以自建后台、自定义配置。

经过大量动态调试(Frida + IDA 远程调试),成功还原了目标挂的后台配置数据,并将其复制到了自己的微卡验证后台。

⚠️ 本文主题是 GOT Hook 静态补丁技术,动态调试过程不在此展开。重点是为大家提供一个面对 OLLVM 混淆 + 字符串加密的 ELF 时的解题思路

现在问题变得很简单:

把二进制中发送的 App ID 从 51642 改成我自己的 ID,卡密验证就会走我自己的后台。

但是,由于 OLLVM 字符串加密,51642 这个字符串在二进制中根本找不到 —— 它是运行时解密后才出现在内存中的。

传统的十六进制补丁?不行。修改解密逻辑?OLLVM 混淆下太复杂。

那怎么办?

不需要理解混淆逻辑,不需要解密字符串 —— 在数据发送出去的那一刻拦截并修改

程序最终要把数据发送到服务器,必然要调用网络发送函数。通过分析,该程序使用 sendto() 发送 UDP 数据报。我们只需要:

这就是 GOT Hook 的威力:绕过所有混淆,直接在数据出口动手

ELF 动态链接二进制调用外部函数(如 sendto())时,不会直接跳转到 libc,而是通过 GOT 表间接调用:

关键点:GOT 表位于 rw-(可读写) 内存段,可以在运行时被修改。

sendto() 是发送 UDP 数据报的系统调用封装,签名如下:

我们的 hook 只需要在 buf 中搜索 App ID 并替换,然后原样调用真实 sendto() 即可。

不只是 sendto —— 根据目标程序的实际情况,你也可以 hook send()write()SSL_write() 等任何通过 GOT 调用的函数。核心思路完全一致。

这个二进制受 OLLVM 保护,GOT 中存储的不是真实函数地址,而是混淆后的值

我们的工具通过 got_addend 配置项处理这种情况,安装 hook 时保持混淆方式一致,程序完全无感知。

玩过 SimpleHook 的应该都知道,SimpleHook 可以自定义类名和方法名来 hook Java 层函数。sendto 在这里就相当于「方法名」—— 只不过这是 native 层的函数,运行在 ARM64 ELF 中,我们需要获取它在 GOT 表中的实际地址

步骤:

图片描述

got_sendto 就是 GOT 表中存储 sendto 函数指针的那个槽位地址。got_addend 是 OLLVM 用来混淆 GOT 值的 64 位常量(如果目标没有 OLLVM 混淆,填 0 即可)。

这和 SimpleHook 修改方法参数是同一个思路,只不过这里是在 native 层、二进制级别操作。

知道了这些基本信息之后,就需要自己写 GOT Hook 了。为了方便大家理解和使用,我开源了一个工具:

** elf-got-patcher**

这个工具的核心思想:纯 C 编写 shellcode,编译成裸 ARM64 二进制,通过 patcher 注入到 ELF 的 code cave 中

ELF 有一个 .init_array 段,里面存放程序启动时自动调用的初始化函数。我们的注入方式:

原理图:

scan_and_replace 会遍历配置中的所有 needle/replace 对,在 buf 中逐字节搜索匹配,找到就原地替换:

shellcode 中所有地址都用 0xCAFEBABE 开头的哨兵值占位:

Patcher 在注入时扫描这些哨兵,替换为实际地址。这样同一份 hook.bin 可以适配任何目标,只需要更换 JSON 配置。

cave_vacave_offrela_addend_offorig_initsaved_sendto 均支持自动检测,大多数情况下不需要手动填写。

输出示例:

如果开启了 debug_flag,可以导出日志查看修改后的请求:

用 Python 解析日志:

如果看到请求中 app=35029(你的 ID)而不是 app=51642(原始 ID),说明 hook 成功 ✓

在正式修改之前,建议先开启 debug 模式观察原始流量,确认目标字符串确实存在于 sendto 的发送缓冲区中。

配置中设置:

图片描述

查看日志内容,发现确实捕获到了 35029 这个 App ID —— 这就是程序运行时解密后、即将发送的真实请求体。字符串加密在发送那一刻已经被解密了,全部暴露在 buf 中。

确认目标字符串存在后,配置 needle/replace 进行替换,把 35029 改成自己的 App ID,重新 patch 即可。

> ⚡ 实际场景提醒:本文以 sendto + App ID 替换为例,但现实中每个目标的情况都不一样 —— 可能用的是 send()write()、甚至自定义的加密通信函数;要替换的可能是 URL、Token、设备指纹等任何字段。核心方法论是通用的,具体操作需要根据你的实际逆向分析结果随机应变。

面对 OLLVM 混淆 + 字符串加密的 ELF 二进制,传统的静态补丁几乎不可能。本文的思路是:

这个思路不仅适用于卡密验证绕过,也适用于任何需要修改 native 层网络请求的场景甚至于修改方法参数里的各种字符串实现你想实现的效果。希望对大家有帮助。

工具开源地址:elf-got-patcher

欢迎 Star ⭐ 和提 Issue!

你看到的位置 对应配置字段 含义
data 段中引用 sendto 的位置 got_sendto GOT 表项的虚拟地址
MOVZ+MOVK×3 指令中的 64 位常量 got_addend GOT 混淆常量(OLLVM 特有)
gzexe -d MG

[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2天前 被LeoChen..编辑 ,原因:
收藏
免费 24
支持
分享
最新回复 (6)
雪    币: 1549
活跃值: (4718)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
为什么不试一试eBPF或者KPM模块拦截__NR_sendto系统调用呢,libc走的还是svc。静态patch确实有说法,但是现在都是svc调用了,不走GOT表
2天前
0
雪    币: 2041
活跃值: (375)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
3
iBa0 为什么不试一试eBPF或者KPM模块拦截__NR_sendto系统调用呢,libc走的还是svc。静态patch确实有说法,但是现在都是svc调用了,不走GOT表
能解决就行了,这个无所谓
2天前
0
雪    币: 4476
活跃值: (5185)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
6
2天前
0
雪    币: 104
活跃值: (8377)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
tql
2天前
0
雪    币: 233
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
look
2天前
0
雪    币: 267
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
看看
2天前
0
游客
登录 | 注册 方可回帖
返回