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 <[email protected]>.
關于gzip檔案格式的說明和源碼的解析可以參考 gzip檔案格式解析及源代碼分析,進行深入的研究和學習。
0x3.Android核心檔案的逆向修改
将解壓後的Android核心檔案 kernel_new 拖入到IDA Pro 中進行分析,設定處理器類型為ARM Little-endian。
ROM start address 和Loading 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核心繞過反調試也就隻是考慮 /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偏移值計算規則