修改Android手機內核,繞過反調試

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


0x1.手機設備環境python

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



0x2.Android內核提取

查找Android設備的boot分區文件。高通芯片的設備能夠經過下面的命令進行查找。android

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 下:
git

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的內核文件。
github

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

abootimg -x boot.img  
  
ls -al




將zImage文件拷貝一份做爲文件名爲 kernel.gz 的文件,並使用 WinHex 工具查找十六進制 1F 8B 08 00,找到以後把前面的數據部分全刪掉,使kernel.gz文件變成標準的gzip壓縮文件,這樣子就可使用  gunzip/gzip 命令進行解壓內核文件了。
api

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 命令的參數使用說明,以下:post

$ 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偏移值計算規則

相關文章
相關標籤/搜索