Frida反调试对抗系列(二)
有需要过frida反调试的可以去联系博主主页V
let V = '15232101239';
今天我们主要学习上一篇提到的优秀Frida魔改项目,分析其修改的具体位置。在实际操作中,针对每个Frida脚本去找到所有检测点成本过高,因此我们先要识别Frida中容易被检测的地方并进行修改。这样做的思路是先借鉴前人的经验,站在他们的肩膀上进行扩展,遇到无法绕过的检测时,再具体分析。这样的方法会更加高效。
一、学习优秀项目Florida魔改点
下面我们简单看一些patchs的修改点:
1.0001-Florida-string_frida_rpc.patch
getRpcStr()
方法的新增
public string getRpcStr(bool quote){
string result = (string) GLib.Base64.decode((string) GLib.Base64.decode("Wm5KcFpHRTZjbkJq"));
if(quote){
return "\"" + result + "\"";
} else {
return result;
}
}
-
功能:
getRpcStr()
方法解码一个经过两次 Base64 编码的字符串,并返回解码后的结果。如果quote
参数为true
,则在结果前后加上双引号("
),如果quote
为false
,则返回纯字符串。 -
具体过程:
这个字符串实际上是
frida:rpc
,这是 RPC 请求的标识符。通过getRpcStr()
,你可以在不同的上下文中获得这个标识符,并可以选择是否加上引号GLib.Base64.decode()
用于对给定的 Base64 编码的字符串进行解码。"Wm5KcFpHRTZjbkJq"
是一个经过 Base64 编码的字符串,它经过两次解码后将返回一个原始字符串。
修改 call()
方法
public async Json.Node call (string method, Json.Node[] args, Cancellable? cancellable) throws Error, IOError {
string request_id = Uuid.string_random ();
var request = new Json.Builder ();
request
.begin_array ()
.add_string_value (getRpcStr(false))
.add_string_value (request_id)
.add_string_value ("call")
.add_string_value (method)
.add_array_value (args)
.end_array ();
return await send_request(request, cancellable);
}
- 变化:在
call()
方法中,将原来硬编码的"frida:rpc"
字符串替换成了getRpcStr(false)
。这意味着 RPC 调用的标识符不再是固定的字符串,而是通过getRpcStr(false)
动态获取。
修改try_handle_message()
方法
public bool try_handle_message (string json) {
if (json.index_of (getRpcStr(true)) == -1)
return false;
var parser = new Json.Parser ();
if (!parser.load_from_data(json, -1)) {
return false;
}
var rpc_message = parser.get_root();
if (rpc_message.get_type() != Json.NodeType.ARRAY) {
return false;
}
string? type = rpc_message.get_element(0).get_string ();
if (type == null || type != getRpcStr(false))
return false;
var request_id_value = rpc_message.get_element(1);
// 处理其他消息逻辑...
}
修改 rpc_message
类型检查
string? type = rpc_message.get_element(0).get_string ();
if (type == null || type != getRpcStr(false))
return false;
- 变化:原本硬编码的
"frida:rpc"
字符串也被替换为getRpcStr(false)
,用来验证消息中的类型是否匹配。如果类型不匹配,则返回false
。
总结:
这个补丁的主要目的是引入了一个新的 getRpcStr()
方法,用于动态生成或获取 "frida:rpc"
字符串。通过这种方式,代码变得更加灵活,减少了硬编码字符串的使用,从而提升了代码的可维护性和可扩展性。
具体修改点:
getRpcStr(bool quote)
:解码Wm5KcFpHRTZjbkJq
并返回"frida:rpc"
字符串,支持是否加引号。call()
方法:将"frida:rpc"
字符串替换为getRpcStr(false)
。try_handle_message()
方法:在判断消息是否为 RPC 类型时,使用getRpcStr(true)
来匹配带引号的字符串,并确保消息符合预期的格式。
这些修改有助于提高代码的灵活性,使其能够更容易地适应未来可能的变化,例如修改 RPC 标识符的方式。
2.0002-Florida-frida_agent_so.patch
具体修改内容分析:
-
增加随机前缀:
var random_prefix = GLib.Uuid.string_random();
这行代码调用
GLib.Uuid.string_random()
生成一个随机的 UUID 字符串,作为前缀。这是为了避免多个不同实例之间发生冲突。 -
修改代理文件的路径和资源名称: 代码中的
PathTemplate ("frida-agent-<arch>.so")
被修改为:PathTemplate (random_prefix + "-<arch>.so")
这意味着
frida-agent-<arch>.so
的路径模板现在包含一个随机前缀,例如:a1b2c3d4-arm.so
,使得每个实例的路径具有唯一性。 -
修改
AgentResource
的文件名: 之前的文件名是"frida-agent-arm.so"
和"frida-agent-arm64.so"
,而现在它们被修改为:、random_prefix + "-arm.so" random_prefix + "-arm64.so"
这些文件名也包含了随机前缀,使得每个资源的文件名独一无二。
3.0008-Florida-pool-frida.patch:
From 0f3391327c044f6c2ab0ee3322185904b0afa2c5 Mon Sep 17 00:00:00 2001
From: Ylarod <me@ylarod.cn>
Date: Thu, 20 Jul 2023 10:01:20 +0800
Subject: [PATCH 8/9] Florida: pool-frida
---
src/frida-glue.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/frida-glue.c b/src/frida-glue.c
index ee8f0737..43cc8167 100644
--- a/src/frida-glue.c
+++ b/src/frida-glue.c
@@ -40,6 +40,8 @@ frida_init_with_runtime (FridaRuntime rt)
g_io_module_openssl_register ();
#endif
+ g_set_prgname ("ggbond");
+
if (runtime == FRIDA_RUNTIME_OTHER)
{
main_context = g_main_context_ref (g_main_context_default ());
--
2.42.0
这个代码提交 (PATCH 8/9
) 是对 frida-glue.c
文件的修改,主要添加了一行代码来设置程序的名称。
具体修改内容
- 文件路径:
src/frida-glue.c
- 修改内容:在
frida_init_with_runtime
函数中,新增了g_set_prgname ("ggbond");
这一行。
代码详细分
g_set_prgname ("ggbond");
g_set_prgname
是 GLib 库中的一个函数,通常用于设置程序的名字(可用于显示或日志记录等)。程序的名称通常在调试信息、日志输出或者进程管理时很有用。- 这里将程序名设置为
"ggbond"
,意味着这个应用程序在运行时,其进程名称会被标记为ggbond
,即使它实际的文件名可能不同。
4.0007-Florida-update-python-script.patch
From 19026255ad926b166e7ffb8759e896fd6b5bff94 Mon Sep 17 00:00:00 2001
From: Ylarod <me@ylarod.cn>
Date: Tue, 18 Jul 2023 19:55:59 +0800
Subject: [PATCH 7/9] Florida: update python script
---
src/anti-anti-frida.py | 59 +++++++++++++++++++++++++++++-------------
1 file changed, 41 insertions(+), 18 deletions(-)
diff --git a/src/anti-anti-frida.py b/src/anti-anti-frida.py
index b4b8dca6..d1ce5f62 100644
--- a/src/anti-anti-frida.py
+++ b/src/anti-anti-frida.py
@@ -2,36 +2,59 @@ import lief
import sys
import random
import os
-
+
+def log_color(msg):
+ print(f"\033[1;31;40m{msg}\033[0m")
+
if __name__ == "__main__":
input_file = sys.argv[1]
- print(f"[*] Patch frida-agent: {input_file}")
- random_name = "".join(random.sample("ABCDEFGHIJKLMNO", 5))
- print(f"[*] Patch `frida` to `{random_name}``")
-
+ random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ log_color(f"[*] Patch frida-agent: {input_file}")
binary = lief.parse(input_file)
-
+
if not binary:
+ log_color(f"[*] Not elf, exit")
exit()
+
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `frida` to `{random_name}`")
for symbol in binary.symbols:
if symbol.name == "frida_agent_main":
symbol.name = "main"
-
+
if "frida" in symbol.name:
symbol.name = symbol.name.replace("frida", random_name)
-
+
if "FRIDA" in symbol.name:
symbol.name = symbol.name.replace("FRIDA", random_name)
-
+
+ all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"] # 字符串特征修改 尽量与源字符一样
+ for section in binary.sections:
+ if section.name != ".rodata":
+ continue
+ for patch_str in all_patch_string:
+ addr_all = section.search_all(patch_str) # Patch 内存字符串
+ for addr in addr_all:
+ patch = [ord(n) for n in list(patch_str)[::-1]]
+ log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}")
+ binary.patch_address(section.file_offset + addr, patch)
+
binary.write(input_file)
-
- # gum-js-loop thread
- random_name = "".join(random.sample("abcdefghijklmn", 11))
- print(f"[*] Patch `gum-js-loop` to `{random_name}`")
+
+ # thread_gum_js_loop
+ random_name = "".join(random.sample(random_charset, 11))
+ log_color(f"[*] Patch `gum-js-loop` to `{random_name}`")
os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}")
-
- # gmain thread
- random_name = "".join(random.sample("abcdefghijklmn", 5))
- print(f"[*] Patch `gmain` to `{random_name}`")
- os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")
\ No newline at end of file
+
+ # thread_gmain
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `gmain` to `{random_name}`")
+ os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")
+
+ # thread_gdbus
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `gdbus` to `{random_name}`")
+ os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")
+
+ log_color(f"[*] Patch Finish")
\ No newline at end of file
--
2.42.0
这段代码是一个用于修改可执行文件中的符号名称以及某些字符串特征的 Python 脚本。其主要用途似乎是在进行逆向工程或者保护某些程序免受分析工具(比如 Frida)干扰。下面是对这段代码的详细解释:
1. 导入依赖库
import lief import sys import random import os
lief
:用于解析和修改 ELF、PE 和 Mach-O 文件格式。它被用来加载和修改二进制文件。sys
:提供对 Python 解释器的一些访问,主要用于处理命令行参数。random
:用来生成随机数,特别是用来生成随机字符串。os
:与操作系统交互,主要用于执行系统命令(如使用sed
命令修改文件内容)。
2. 定义辅助函数 log_color
def log_color(msg): print(f"\033[1;31;40m{msg}\033[0m")
- 该函数用于将输出消息以红色打印到控制台。
\033[1;31;40m
是 ANSI 转义序列,用于修改终端文字的颜色,这里是设置为红色。\033[0m
用来重置颜色,确保后续输出不受影响。
3. 主程序入口
if __name__ == "__main__": input_file = sys.argv[1]
- 该部分是脚本的主逻辑部分,首先通过
sys.argv[1]
获取命令行传递的输入文件路径。
4. 加载二进制文件
binary = lief.parse(input_file)
- 使用
lief
库来解析给定的二进制文件,返回一个binary
对象,表示这个文件。如果文件无法解析,binary
将是None
。
5. 检查是否为 ELF 文件
if not binary: log_color(f"[*] Not elf, exit") exit()
- 如果解析失败,即
binary
为None
,则打印警告并退出程序。这部分检查文件是否为 ELF 格式。
6. 修改二进制中的符号名称
random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" log_color(f"[*] Patch frida-agent: {input_file}")
- 该代码片段设置了用于生成随机名称的字符集。
- 打印正在处理的输入文件。
random_name = "".join(random.sample(random_charset, 5)) log_color(f"[*] Patch `frida` to `{random_name}`")
- 随机选择 5 个字符来生成新的名称,用于替换文件中与
frida
相关的符号。
for symbol in binary.symbols: if symbol.name == "frida_agent_main": symbol.name = "main" if "frida" in symbol.name: symbol.name = symbol.name.replace("frida", random_name) if "FRIDA" in symbol.name: symbol.name = symbol.name.replace("FRIDA", random_name)
- 遍历二进制文件中的所有符号。
- 如果符号名称包含
frida
或FRIDA
,则替换成随机生成的名称。 - 还将
frida_agent_main
修改为main
。
7. 修改 .rodata
区段中的字符串
all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"] for section in binary.sections: if section.name != ".rodata": continue for patch_str in all_patch_string: addr_all = section.search_all(patch_str) for addr in addr_all: patch = [ord(n) for n in list(patch_str)[::-1]] log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}") binary.patch_address(section.file_offset + addr, patch)
- 该部分代码遍历
.rodata
区段并查找某些字符串(如FridaScriptEngine
,GLib-GIO
,GDBusProxy
,GumScript
)。 - 在找到这些字符串后,进行反转替换并输出日志信息。
- 然后使用
binary.patch_address()
方法将修改后的字符串写入到内存中的相应位置。
8. 使用 sed
命令替换文件中的特定字符串
os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}") os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}") os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")
- 这些
sed
命令在文件中查找gum-js-loop
,gmain
, 和gdbus
等字符串,并将其替换为随机生成的名称。 -i
选项表示直接在文件中修改,-b
选项表示使用二进制模式。
9. 脚本完成
log_color(f"[*] Patch Finish")
- 最后,打印出脚本执行完成的日志。
总结
这个 Python 脚本的目的是修改一个二进制文件,特别是针对某些特定的符号和字符串(如与 frida
相关的内容)进行随机化和替换。这样做可能是为了防止 Frida 等工具的动态分析,或者是为了保护二进制文件不被轻易逆向工程分析。