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

本文博客链接:http://blog..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

adfe9b8a67954e5f63720e4ec1710000.png

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

d53892b5e3fbf511a556ea2917503cfc.png

使用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

3bc7fc5f38acd0dcce1fdac59ed9595a.png

将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查找十六进制数据:

203d1f8b0f6fa21a9dfffac5aec6c7b8.png

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

e6c3212595d0291dc8750cc037d2caec.png

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

29ce4b82d9987faf2c8a9535c07302e6.png

提示:关于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 .

88327113ed66413d61d8a51a2bd712d7.png

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

394de20f591c0285bbc9ade615ad6842.png

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

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

a0527847f0a5a7149287cd91ec7512a3.png

在 ROM start address 和Loading address 处填上

0xc0008000,然后点击 OK 。

a23b02b13a47417007a1f035d2507d9d.png

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

c82f177db570ad64504404b3bc2427a2.png

为了要获取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 ,发现被隐藏的内核符号信息都显示出来了。

a0141f463bea35a2c3c767dfc9d69f3e.png

在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

4226ef457997d73d09ac8ee507dce070.png

我们已经将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显示出正确的系统调用的函数名称来。

7bc0c41f2763e91e8838c73702e47632.png

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

96678fb1468a195190d5cfdf1c58f56c.png

现在来聊一聊修改Android的内核文件绕过反调试,很多的Android加固都会通过查看当前进程的/proc/pid/status 的状态信息,来进行判断当前进程是否被调试的依据。如果当前进程被调试器所调试,那么cat /proc/self/status

显示的状态如下图所示,比较常见的Android反调试也就是通过 TracerPid 的值在调试状态和非调试状态的不同且非调试状态该值为0而调试状态为非0,来判断是否被调试器所调试。

f48aaa715dcf781bfa6e6048ec57dc63.png

这里修改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内核源码的实现可以知道,如图所示的位置是我们应该修改的地方:

bada1085c6f61ee362aa83146762f97d.png

通过对Android内核源码的研究知道了我们在Android内核文件中修改的地方,在IDA中通过字符串搜索 TracerPid

即查找上面提到的特征字符串组。

9476e06496f371f20b6a770722b9d875.png

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

67460e8ac5f68fc1369da50ae6221734.png

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

d378f242bfb3126fd0d1ee4866ca88d5.png

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

957eb4c0865dc8c86760c12cc5297bdc.png

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

48abec0646612faa62426f049cef7d05.png

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

a739a6f335ae40665a98c35ebc8f04ea.png

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

482c7b2feaa64873a9061598aaf9eef4.png

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

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

e81e2be109198ca5e329f4309afb0c95.png

通过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后面的位置开始覆盖。

3ca264c8fd0c452619c34e68da75075a.png

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

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

e4b0168a366c7d73480c041c88aab230.png

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

adb reboot bootloader

fastboot flash boot myboot.img

a5e0fcab1d1613a759adcdad6414e6be.png

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进行查找定位到需要逆向分析的关键代码的位置。

acc2f893933555f6b841b5f31d9baaed.png

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

51a300596e8ac97e119f03e9c2b567aa.png

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

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

ARM汇编下BL类指令的修改以及偏移的计算具体可以参考:【求助】arm指令BL指令对应的机器码问题、ARM中跳转指令BL/BLX偏移值计算规则

,由于在前面的操作步骤中涉及到B类跳转指令的修改,特此提到一下。提醒两点:1.一定要善于利用IDA能够显示ARM指令机器码的特点,2.在内存中ARM指令的存放是按小尾端存放的。

a99e82bb95e32fe878299046a3c0a462.png

45dae46fab8846fe9ac1574024e9eea7.png

参考资料:

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

提取Android内核的方法

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

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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值