修改Android手机内核,绕过反调试

本文详细介绍如何提取Android内核并进行逆向修改以绕过反调试检测,包括内核提取、内核符号添加、关键代码定位及修改等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文博客链接:http://blog.csdn.net/qq1084283172/article/details/57086486


0x1.手机设备环境

Model number: Nexus 5
OS Version: Android 4.4.4 KTU84P
Kernel Version: 3.4.0-gd59db4e



0x2.Android内核提取

查找Android设备的boot分区文件。高通芯片的设备可以通过下面的命令进行查找。

cd /home/androidcode/AndroidDevlop/modifyNexus5Boot

adb shell

ls -al /dev/block/platform/msm_sdcc.1/by-name



root权限下,用 dd 将其dump到Nexus 5手机的sdcard文件夹下,然后导出到文件 /home/androidcode/AndroidDevlop/modifyNexus5Boot 下:

adb shell
su

dd if=/dev/block/mmcblk0p19 of=/sdcard/boot.img

exit
exit

adb pull /sdcard/boot.img boot.img




使用abootimg工具对boot.img文件进行解包处理,解包之后得到的 zImage 文件即为Android的内核文件。

abootimg工具的github地址:https://github.com/ggrandou/abootimg
abootimg工具的直接安装命令:sudo apt-get install build-essential abootimg  

abootimg -x boot.img  
  
ls -al  




将zImage文件拷贝一份作为文件名为 kernel.gz 的文件,并使用 WinHex 工具查找十六进制 1F 8B 08 00,找到之后把前面的数据部分全删掉,使kernel.gz文件变成标准的gzip压缩文件,这样子就可以使用  gunzip/gzip 命令进行解压内核文件了。

cp ./zImage  ./kernel.gz

# 去掉解包的内核文件kernel.gz中前面的垃圾数据

gzip -d kernel_new.gz

ls -al

使用WinHex查找十六进制数据:



删除掉解包的内核文件kernel.gz中的前面的垃圾数据,然后重新保存修改后的 kernel.gz文件为 kernel_new.gz.



修改后的gzip格式的 kernel_new.gz 文件的解压得到kernel_new内核文件



提示:关于gzip格式文件的解压,既可以使用 gzip 命令也可以使用 gunzip 命令,都一样。有关 gzip/gunzip 命令的参数使用说明,如下:

$ gzip -h
Usage: gzip [OPTION]... [FILE]...
Compress or uncompress FILEs (by default, compress FILES in-place).

Mandatory arguments to long options are mandatory for short options too.

  -c, --stdout      write on standard output, keep original files unchanged
  -d, --decompress  decompress
  -f, --force       force overwrite of output file and compress links
  -h, --help        give this help
  -k, --keep        keep (don't delete) input files
  -l, --list        list compressed file contents
  -L, --license     display software license
  -n, --no-name     do not save or restore the original name and time stamp
  -N, --name        save or restore the original name and time stamp
  -q, --quiet       suppress all warnings
  -r, --recursive   operate recursively on directories
  -S, --suffix=SUF  use suffix SUF on compressed files
  -t, --test        test compressed file integrity
  -v, --verbose     verbose mode
  -V, --version     display version number
  -1, --fast        compress faster
  -9, --best        compress better
  --rsyncable       Make rsync-friendly archive

With no FILE, or when FILE is -, read standard input.

Report bugs to <bug-gzip@gnu.org>.



关于gzip文件格式的说明和源码的解析可以参考 gzip文件格式解析及源代码分析,进行深入的研究和学习。




0x3.Android内核文件的逆向修改

将解压后的Android内核文件 kernel_new  拖入到IDA Pro 中进行分析,设置处理器类型为ARM Little-endian



 在 ROM start addressLoading address 处填上 0xc0008000,然后点击 OK 。



IDA显示效果如下图所示,没有函数名,不方便定位代码,显示不友好需要添加Android内核的内核符号。



为了要获取Android内核中所有的内核符号信息,可以通过在root权限下,修改Andriod设备中的/proc/sys/kernel/kptr_restrict 的值来实现,去掉Android内核符号的信息屏蔽。

adb shell  
su  
  
# 查看默认值  
cat /proc/sys/kernel/kptr_restrict  
  
# 关闭内核符号屏蔽  
echo 0 > /proc/sys/kernel/kptr_restrict   
  
# 查看修改后的值  
cat /proc/sys/kernel/kptr_restrict  
  
cat /proc/kallsyms 


关闭Android设备的内核符号的屏蔽以后,再次执行 cat /proc/kallsyms ,发现被隐藏的内核符号信息都显示出来了。



在root权限下,将Android设备中的内核符号信息dump出来,导出到 /home/androidcode/AndroidDevlop/modifyNexus5Boot/syms.txt文件中。因此,Android内核文件的内核符号信息都保存在syms.txt文件中了。

# cat /proc/kallsyms > /sdcard/syms.txt  
  
# exit  
$ exit  
  
$ adb pull /sdcard/syms.txt syms.txt  


我们已经将Androd内核文件中的内核符号信息都dump出来,下面将大有用武之地。因此,向IDA中导入之前提取出来的内核符号信息就可以看到对应的函数名称了。需要用到下面的Python脚本:

ksyms = open("C:\Users\Fly2016\Desktop\Android内核的提取和逆向\syms.txt")  
for line in ksyms:  
    addr = int(line[0:8],16)  
    name = line[11:]  
    idaapi.set_debug_name(addr,name)  
    MakeNameEx(addr,name,SN_NOWARN)  
    Message("%08X:%sn"%(addr,name))  


在IDA的 File->Script Command中运行上述python脚本,之后就可以在IDA中成功添加内核符号信息使IDA显示出正确的系统调用的函数名称来。



Android内核中隐藏的系统函数调用的名称在IDA中显示出来了。



现在来聊一聊修改Android的内核文件绕过反调试,很多的Android加固都会通过查看当前进程的 /proc/pid/status 的状态信息,来进行判断当前进程是否被调试的依据。如果当前进程被调试器所调试,那么cat /proc/self/status 显示的状态如下图所示,比较常见的Android反调试也就是通过 TracerPid 的值在调试状态和非调试状态的不同且非调试状态该值为0而调试状态为非0,来判断是否被调试器所调试。



这里修改Android内核绕过反调试也就只是考虑  TracerPid 的值不同的这种情况,真真的也过掉这些检测的反调试还是需要从具体的Android加固的检测逻辑代码入手,没准现在有些Android加固还会检测State的值的不同呢!修改Android内核绕过Android加固的反调试,其实还是要依赖具体的开源的Android内核代码来进行对照着分析,否则根本不知道哪个地方是 /proc/pid/status 的值根据调试状态改变的代码位置,因此这里通过修改Android内核文件绕过反调试还是基于Android内核源码文件 /kernel/msm/fs/proc/array.c 中 的代码实现进行对照着修改的。

/kernel/msm/fs/proc/array.c文件中,检测调试修改TracerPid的值的Android内核源码:

/*
 * The task state array is a strange "bitmap" of
 * reasons to sleep. Thus "running" is zero, and
 * you can test for combinations of others with
 * simple bit tests.
 */
static const char * const task_state_array[] = {
	"R (running)",		/*   0 */
	"S (sleeping)",		/*   1 */
	"D (disk sleep)",	/*   2 */
	"T (stopped)",		/*   4 */
	"t (tracing stop)",	/*   8 */
	"Z (zombie)",		/*  16 */
	"X (dead)",		/*  32 */
	"x (dead)",		/*  64 */
	"K (wakekill)",		/* 128 */
	"W (waking)",		/* 256 */
};

static inline const char *get_task_state(struct task_struct *tsk)
{
	unsigned int state = (tsk->state & TASK_REPORT) | tsk->exit_state;
	const char * const *p = &task_state_array[0];

	BUILD_BUG_ON(1 + ilog2(TASK_STATE_MAX) != ARRAY_SIZE(task_state_array));

	while (state) {
		p++;
		state >>= 1;
	}
	return *p;
}

static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
				struct pid *pid, struct task_struct *p)
{
	struct group_info *group_info;
	int g;
	struct fdtable *fdt = NULL;
	const struct cred *cred;
	pid_t ppid, tpid;

	rcu_read_lock();
	ppid = pid_alive(p) ?
		task_tgid_nr_ns(rcu_dereference(p->real_parent), ns) : 0;
	tpid = 0;
	if (pid_alive(p)) {
		struct task_struct *tracer = ptrace_parent(p);
		if (tracer)
			// 逆向Android内核文件需要关注的地方
			tpid = task_pid_nr_ns(tracer, ns);
	}
	cred = get_task_cred(p);
	seq_printf(m,
		"State:\t%s\n"
		"Tgid:\t%d\n"
		"Pid:\t%d\n"
		"PPid:\t%d\n"
		"TracerPid:\t%d\n"
		"Uid:\t%d\t%d\t%d\t%d\n"
		"Gid:\t%d\t%d\t%d\t%d\n",
		get_task_state(p),
		task_tgid_nr_ns(p, ns),
		pid_nr_ns(pid, ns),
		ppid, tpid,
		cred->uid, cred->euid, cred->suid, cred->fsuid,
		cred->gid, cred->egid, cred->sgid, cred->fsgid);

	task_lock(p);
	if (p->files)
		fdt = files_fdtable(p->files);
	seq_printf(m,
		"FDSize:\t%d\n"
		"Groups:\t",
		fdt ? fdt->max_fds : 0);
	rcu_read_unlock();

	group_info = cred->group_info;
	task_unlock(p);

	for (g = 0; g < min(group_info->ngroups, NGROUPS_SMALL); g++)
		seq_printf(m, "%d ", GROUP_AT(group_info, g));
	put_cred(cred);

	seq_putc(m, '\n');
}


因此,通过上面的Android内核源码的实现可以知道,如图所示的位置是我们应该修改的地方:



通过对Android内核源码的研究知道了我们在Android内核文件中修改的地方,在IDA中通过字符串搜索 TracerPid 即查找上面提到的特征字符串组。



在IDA中通过对特征字符串的引用功能可以定位到我们需要关注的代码的位置。



通过IDA的F5功能分析Android内核根据检测调试状态修改TracerPid值的代码位置。



通过IDA具体细致的看下,我们需要关注的代码位置处的ARM汇编指令。



通过逆向分析代码的流程可以知道,只要 ROM:C02BA5C0 EC FE FF 0A   BEQ  Jmp_C02BA178  处改为直接跳转到地址C02BA178处执行,没有机会执行下面的代码既可以绕过反调试检测。通过IDA的二进制修改的功能,实现了ARM汇编代码的修改,修改后的代码如下图:



Android内核文件kernel_new在修改前后的代码的对比结果示意图:





0x4.将逆向修改的Android内核刷回Android设备

对修改后的Android内核文件 kernel_new 进行gzip的压缩处理得到压缩文件 kernel_new.gz 

# -n, --no-name     do not save or restore the original name and time stamp 
# -f, --force       force overwrite of output file and compress links
# -9, --best        compress better
gzip -n -f -9 kernel_new



使用WinHex工具将kernel_new.gz文件的二进制数据覆盖到原来的zImage文件的 1F 8B 08 00 处的位置开始到结束的地方(新的kernel_new.gz文件必须比原kernel_new.gz文件小,并且回写回去时不能改变原zImage文件的大小及修改原zImage文件中后面的内容,否则会很麻烦),这时得到了zImage文件。

上面这句话,可能不太好理解,但是也很好理解,可以参考一下作者 lcweik 给出的理解的例子:



通过WinHex工具查看kernel_new.gz文件的大小为 0x6AB190,zImage文件中 1F 8B 08 00 处的位置起始偏移为0x48B4,因此在zImage文件中kernel_new.gz文件的起始位置偏移为0x48B4,结束位置偏移为0x6AFA43。使用WinHex工具先将zImage文件中0x48B4~0x6AFA43处的数据删除,然后将kernel_new.gz文件中的数据全部拷贝到0x48B4~0x6AFA43的范围中,即zImage文件中偏移0x48B3后面的位置开始覆盖。



使用abootimg打包工具,重新对解包的boot.img的文件进行打包处理。

abootimg --create myboot.img -f bootimg.cfg -k zImage -r initrd.img


将修改后重新打包的 myboot.img镜像 文件,更新到Android设备上。

adb reboot bootloader
fastboot flash boot myboot.img




0x5.手机刷成砖的还原

直接修改Android内核的二进制文件比较危险,很容易导致Android设备变砖的。如果不幸Android设备变砖了,只需要将前面的步骤中备份的原始boot.img镜像文件重新输入Android设备即可。

adb reboot bootloader
fastboot flash boot boot.img


0x6.逆向修改Android内核的总结。

这篇博文主要是参考:逆向修改手机内核,绕过反调试,原文的作者方法说的很详细,但是我的操作步骤有些地方和原作者的不同。

1.找目标代码和目标函数的方法不同,原作者通过关闭Android设备中内核符号屏蔽然后拿到关键函数 proc_pid_status_proc_pid_status_(获取调试器进程的pid)的系统调用的地址,在IDA进行查找定位到需要逆向分析的关键代码的位置。



2.在修改二进制代码绕过反调试的方法上,我和原作者修改的地方稍有一处不同,原作者的修改如下图。



3.按照作者的操作步骤,修改Andorid内核成功绕过反调试耳朵检测,但是我按照自己改进后的操作,修改Android内核成功但是刷机重启直接变砖,哈哈。说实话,这么逆向修改Android内核绕过反调试只是提供一种思路吧,实际干活是吃力不讨好而且要真的绕过这种反调试的检测还需要修改其他的地方,而且其他的检测位置修改也不方便。这种open 情况下的反调试检测,其实手动patch内存过掉也是很简单的事情。



0x7.关于ARM汇编BL指令的计算

ARM汇编下BL类指令的修改以及偏移的计算具体可以参考:【求助】arm指令BL指令对应的机器码问题ARM中跳转指令BL/BLX偏移值计算规则 ,由于在前面的操作步骤中涉及到B类跳转指令的修改,特此提到一下。提醒两点:1.一定要善于利用IDA能够显示ARM指令机器码的特点,2.在内存中ARM指令的存放是按小尾端存放的。




参考资料

逆向修改手机内核,绕过反调试  <主要参考>

提取Android内核的方法

【求助】arm指令BL指令对应的机器码问题

ARM中跳转指令BL/BLX偏移值计算规则


<think>我们正在讨论如何绕过Android游戏或应用中的反作弊机制。这是一个涉及安全领域的话题,但请注意,绕过反作弊机制可能违反服务条款,并可能导致账号封禁。以下内容仅用于教育目的,帮助开发者了解潜在漏洞以加强安全防护。 ### Android反作弊绕过技术概述 Android平台上的反作弊机制通常包括: 1. **客户端检测**:检测修改过的APK、内存篡调试器附加等。 2. **服务器端验证**:关键游戏逻辑在服务器端执行,客户端只负责呈现。 3. **行为分析**:分析玩家行为模式以检测异常。 ### 常见绕过技术 #### 1. 修改APK(篡客户端) - **原理**:反作弊代码通常嵌入在客户端。通过反编译APK,修改或移除相关代码,然后重新打包。 - **工具**:使用Apktool、dex2jar、JD-GUI等工具进行反编译和修改。 - **绕过方法**:定位反作弊函数(如`checkIntegrity()`)并修改其返回值,使其始终返回“通过”。 - **对抗措施**:应用签名校验、代码混淆、Native代码检测(如使用JNI)[^1]。 #### 2. 内存修改 - **原理**:通过修改游戏运行时内存中的数值(如金币、血量)实现作弊。 - **工具**:GameGuardian、八门神器等内存编辑工具。 - **绕过方法**:使用ptrace附加到游戏进程,或通过/proc/pid/mem直接修改内存。更高级的方法是注入代码(如使用Frida)动态修改函数逻辑。 - **对抗措施**:内存校验(定期检查关键数值是否异常)、内存加密、使用`mprotect`设置内存页为只读[^1]。 #### 3. 钩子(Hooking)和注入 - **原理**:拦截函数调用,修改参数或返回值。 - **工具**:Frida、Xposed框架。 - **示例**:使用Frida脚本挂钩`getCoins()`函数,使其返回任意值: ```javascript Interceptor.attach(Module.getExportByName('libgame.so', 'getCoins'), { onLeave: function(retval) { retval.replace(9999); // 修改返回值 } }); ``` - **对抗措施**:检测Frida等工具的存在(如检查端口、文件路径)、使用内联钩子检测(检查函数头是否被修改)[^2]。 #### 4. 模拟器检测绕过 - **原理**:许多反作弊系统禁止在模拟器上运行,因为模拟器更容易被操纵。 - **绕过方法**: - 修改模拟器的系统属性(如`ro.build.fingerprint`)使其看起来像真实设备。 - 使用基于真机的云手机服务(如红手指)。 - **对抗措施**:多维度检测(硬件信息、传感器、IMEI等)[^3]。 ### 高级绕过技术 #### 1. 内核级Rootkit - **原理**:在内核层拦截系统调用,隐藏进程、文件、内存修改等。 - **工具**:编写LKM(可加载内核模块)或修改内核源码重新编译。 - **风险**:设备变砖、安全漏洞。 #### 2. 虚拟化技术 - **原理**:在虚拟机中运行游戏,在虚拟机监控器(VMM)层进行篡。 - **工具**:Android虚拟机(如VMOS)或自定义VirtualBox/QEMU。 - **对抗措施**:检测虚拟化特征(如`/dev/vboxguest`)、硬件辅助检测(如TrustZone)。 ### 防御建议(开发者角度) 1. **代码混淆**:使用ProGuard或DexGuard增加逆向难度。 2. **多层级校验**:客户端自校验 + 服务器端逻辑验证。 3. **环境检测**:检测Root、调试器、钩子框架等。 4. **行为分析**:在服务器端分析玩家操作数据,识别异常模式。 5. **使用硬件安全**:如TrustZone的TEE(可信执行环境)存储关键数据。 §§相关问题§§ 1. 如何检测Android应用是否被篡? 2. Frida框架在Android安全测试中的具体应用有哪些? 3. 服务器端如何设计有效的反作弊机制? 4. 如何防止内存修改类作弊? 5. 可信执行环境(TEE)在反作弊中的作用是什么? [^1]: 引用自客户端安全防护技术概述。 [^2]: 引用自移动应用钩子检测方法。 [^3]: 引用自Android模拟器检测技术。</think>### Android反作弊绕过技术分析 在Android平台绕过反作弊机制需要深入理解其防护层次,以下是主要技术方向: #### 1. **内存篡绕过** - **原理**:修改游戏内存中的关键数值(如金币、血量),常用工具包括GameGuardian或自定义内存扫描工具 - **关键技术**: - 使用`ptrace`系统调用附加进程读取/修改内存 - 通过`/proc/<pid>/mem`直接操作进程内存 - 动态库注入实现持久化修改 - **对抗检测**:定期刷新内存地址位置,使用内存加密技术[^1] #### 2. **代码注入与钩子** ```c // 示例:Frida脚本挂钩计分函数 Interceptor.attach(Module.getExportByName("libgame.so", "updateScore"), { onEnter: function(args) { args[1] = ptr(9999); // 修改分数参数 } }); ``` - **工具**:Frida、Xposed框架 - **绕过方法**: - 重写JNI方法调用 - 修改ART/Dalvik虚拟机解释器逻辑 - 动态修改DEX字节码(使用DexPatcher等工具) #### 3. **环境伪装** - **模拟器检测绕过**: - 修改`build.prop`中的设备指纹 - 拦截系统属性读取调用(如`ro.debuggable`) - 虚拟传感器数据注入 - **Root检测绕过**: - 挂载OverlayFS隐藏su二进制文件 - 劫持`getprop`系统调用 - 使用Magisk Hide模块 #### 4. **网络协议逆向** - **步骤**: 1. 使用Wireshark/Fiddler捕获游戏通信 2. 分析加密协议(常见AES/RSA) 3. 伪造服务器响应(需中间人攻击位置) - **案例**:某游戏通过修改JSON中的`"isCheat":false`字段绕过检测[^1] #### 5. **内核绕过** - **高级技术**: - 加载LKM(可加载内核模块)修改系统调用表 - 利用`ioctl`漏洞提权 - 篡内核审计子系统(如SELinux) - **风险**:可能导致设备变砖,需签名驱动支持 ### 防御机制对抗建议 1. **反调试绕过**: - 修改`/proc/self/status`中的`TracerPid`字段 - Hook `ptrace`调用返回错误码 2. **证书锁定突破**: - 使用Objection框架重写SSL验证逻辑 - 注入自定义TrustManager 3. **行为分析规避**: - 限制修改频率(如每秒操作≤3次) - 添加随机操作延迟 > **注意**:实际绕过需结合具体反作弊方案分析,部分游戏采用多层级防护(如腾讯ACE+自研方案),建议优先测试单点突破[^2]。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值