從Android設備中提取內核和逆向分析


轉載自http://blog.csdn.net/qq1084283172/article/details/57074695

一、手機設備環境

 

[cpp]  view plain  copy
 
  1. Model number: Nexus 5  
  2. OS Version: Android 4.4.4 KTU84P  
  3. Kernel Version: 3.4.0-gd59db4e  



 

二、Android內核提取

 

[cpp]  view plain  copy
 
  1. adb shell  
  2. su  
  3. cd /dev/block/platform/msm_sdcc.1/by-name  
  4. ls -l boot  


 

boot 是個系統符號軟鏈接,/dev/block/mmcblk0p19 就是boot分區

 

用 dd 將其dump到Nexus 5手機的sdcard文件夾下:

 

[cpp]  view plain  copy
 
  1. dd if=/dev/block/mmcblk0p19 of=/sdcard/boot.img  

 

adb pull 將dump出來的boot.img文件導出到 /home/androidcode/AndroidDevlop/Nexus5Boot 文件夾下

 

[cpp]  view plain  copy
 
  1. adb pull /sdcard/boot.img /home/androidcode/AndroidDevlop/Nexus5Boot  

 

 

用 Binwalk 工具分析boot.img文件

1.Binwalk工具的詳細使用說明:Binwalk:后門(固件)分析利器

2.Binwalk工具的github地址:https://github.com/devttys0/binwalk

3.Binwalk工具的官方網址:http://binwalk.org/

4.Binwalk工具的wiki使用說明的地址:https://github.com/devttys0/binwalk/wiki

5.Binwalk工具作者收集的IDA插件和腳本:https://github.com/devttys0/ida

6.Binwalk工具的安裝說明:https://github.com/devttys0/binwalk/blob/master/INSTALL.md

 

安裝Binwalk工具並分析boot.img文件

 

[cpp]  view plain  copy
 
  1. cd /home/androidcode/AndroidDevlop/Nexus5Boot/binwalk-master  
  2.   
  3. # 按照binwalk工具的說明安裝binwalk  
  4. sudo python setup.py install  
  5.   
  6. # 分析boot.img文件  
  7. sudo binwalk ../boot.img >log  

 

分析的結果截圖:



boot.img文件跳過2k的文件頭之后,包括有兩個gz包,一個是boot.img-kernel.gz即Linux內核,一個是boot.img-ramdisk.cpio.gz,
大概的組成結構如下圖,詳細的信息可以參考Android源碼的 android/platform/system/core/master/mkbootimg/bootimg.h 文件,在線查看 booting.h 文件地址:https://android.googlesource.com/platform/system/core/+/master/mkbootimg/bootimg.h 。

 

[cpp]  view plain  copy
 
  1. /* tools/mkbootimg/bootimg.h 
  2. ** 
  3. ** Copyright 2007, The Android Open Source Project 
  4. ** 
  5. ** Licensed under the Apache License, Version 2.0 (the "License");  
  6. ** you may not use this file except in compliance with the License.  
  7. ** You may obtain a copy of the License at  
  8. ** 
  9. **     http://www.apache.org/licenses/LICENSE-2.0  
  10. ** 
  11. ** Unless required by applicable law or agreed to in writing, software  
  12. ** distributed under the License is distributed on an "AS IS" BASIS,  
  13. ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  14. ** See the License for the specific language governing permissions and  
  15. ** limitations under the License. 
  16. */  
  17. #include <stdint.h>  
  18. #ifndef _BOOT_IMAGE_H_  
  19. #define _BOOT_IMAGE_H_  
  20. typedef struct boot_img_hdr boot_img_hdr;  
  21. #define BOOT_MAGIC "ANDROID!"  
  22. #define BOOT_MAGIC_SIZE 8  
  23. #define BOOT_NAME_SIZE 16  
  24. #define BOOT_ARGS_SIZE 512  
  25. #define BOOT_EXTRA_ARGS_SIZE 1024  
  26. struct boot_img_hdr  
  27. {  
  28.     uint8_t magic[BOOT_MAGIC_SIZE];  
  29.     uint32_t kernel_size;  /* size in bytes */  
  30.     uint32_t kernel_addr;  /* physical load addr */  
  31.     uint32_t ramdisk_size; /* size in bytes */  
  32.     uint32_t ramdisk_addr; /* physical load addr */  
  33.     uint32_t second_size;  /* size in bytes */  
  34.     uint32_t second_addr;  /* physical load addr */  
  35.     uint32_t tags_addr;    /* physical addr for kernel tags */  
  36.     uint32_t page_size;    /* flash page size we assume */  
  37.     uint32_t unused;       /* reserved for future expansion: MUST be 0 */  
  38.     /* operating system version and security patch level; for 
  39.      * version "A.B.C" and patch level "Y-M-D": 
  40.      * ver = A << 14 | B << 7 | C         (7 bits for each of A, B, C) 
  41.      * lvl = ((Y - 2000) & 127) << 4 | M  (7 bits for Y, 4 bits for M) 
  42.      * os_version = ver << 11 | lvl */  
  43.     uint32_t os_version;  
  44.     uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */  
  45.     uint8_t cmdline[BOOT_ARGS_SIZE];  
  46.     uint32_t id[8]; /* timestamp / checksum / sha1 / etc */  
  47.     /* Supplemental command line data; kept here to maintain 
  48.      * binary compatibility with older versions of mkbootimg */  
  49.     uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];  
  50. } __attribute__((packed));  
  51. /* 
  52. ** +-----------------+  
  53. ** | boot header     | 1 page 
  54. ** +-----------------+ 
  55. ** | kernel          | n pages   
  56. ** +-----------------+ 
  57. ** | ramdisk         | m pages   
  58. ** +-----------------+ 
  59. ** | second stage    | o pages 
  60. ** +-----------------+ 
  61. ** 
  62. ** n = (kernel_size + page_size - 1) / page_size 
  63. ** m = (ramdisk_size + page_size - 1) / page_size 
  64. ** o = (second_size + page_size - 1) / page_size 
  65. ** 
  66. ** 0. all entities are page_size aligned in flash 
  67. ** 1. kernel and ramdisk are required (size != 0) 
  68. ** 2. second is optional (second_size == 0 -> no second) 
  69. ** 3. load each element (kernel, ramdisk, second) at 
  70. **    the specified physical address (kernel_addr, etc) 
  71. ** 4. prepare tags at tag_addr.  kernel_args[] is 
  72. **    appended to the kernel commandline in the tags. 
  73. ** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr 
  74. ** 6. if second_size != 0: jump to second_addr 
  75. **    else: jump to kernel_addr 
  76. */  
  77. #if 0  
  78. typedef struct ptentry ptentry;  
  79. struct ptentry {  
  80.     char name[16];      /* asciiz partition name    */  
  81.     unsigned start;     /* starting block number    */  
  82.     unsigned length;    /* length in blocks         */  
  83.     unsigned flags;     /* set to zero              */  
  84. };  
  85. /* MSM Partition Table ATAG 
  86. ** 
  87. ** length: 2 + 7 * n 
  88. ** atag:   0x4d534d70 
  89. **         <ptentry> x n 
  90. */  
  91. #endif  
  92. #endif  


有關boot.img文件的生成可以參考Android源碼的 android/platform/system/core/master/mkbootimg/bootimg 文件,在線查看 booting文件地址:https://android.googlesource.com/platform/system/core/+/master/mkbootimg/mkbootimg

 

 

[cpp]  view plain  copy
 
  1. #!/usr/bin/env python  
  2. # Copyright 2015, The Android Open Source Project  
  3. #  
  4. # Licensed under the Apache License, Version 2.0 (the "License");  
  5. # you may not use this file except in compliance with the License.  
  6. # You may obtain a copy of the License at  
  7. #  
  8. #     http://www.apache.org/licenses/LICENSE-2.0  
  9. #  
  10. # Unless required by applicable law or agreed to in writing, software  
  11. # distributed under the License is distributed on an "AS IS" BASIS,  
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13. # See the License for the specific language governing permissions and  
  14. # limitations under the License.  
  15. from __future__ import print_function  
  16. from sys import argv, exit, stderr  
  17. from argparse import ArgumentParser, FileType, Action  
  18. from os import fstat  
  19. from struct import pack  
  20. from hashlib import sha1  
  21. import sys  
  22. import re  
  23. def filesize(f):  
  24.     if f is None:  
  25.         return 0  
  26.     try:  
  27.         return fstat(f.fileno()).st_size  
  28.     except OSError:  
  29.         return 0  
  30. def update_sha(sha, f):  
  31.     if f:  
  32.         sha.update(f.read())  
  33.         f.seek(0)  
  34.         sha.update(pack('I', filesize(f)))  
  35.     else:  
  36.         sha.update(pack('I', 0))  
  37. def pad_file(f, padding):  
  38.     pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)  
  39.     f.write(pack(str(pad) + 'x'))  
  40. def write_header(args):  
  41.     BOOT_MAGIC = 'ANDROID!'.encode()  
  42.     args.output.write(pack('8s', BOOT_MAGIC))  
  43.     args.output.write(pack('10I',  
  44.         filesize(args.kernel),                          # size in bytes  
  45.         args.base + args.kernel_offset,                 # physical load addr  
  46.         filesize(args.ramdisk),                         # size in bytes  
  47.         args.base + args.ramdisk_offset,                # physical load addr  
  48.         filesize(args.second),                          # size in bytes  
  49.         args.base + args.second_offset,                 # physical load addr  
  50.         args.base + args.tags_offset,                   # physical addr for kernel tags  
  51.         args.pagesize,                                  # flash page size we assume  
  52.         0,                                              # future expansion: MUST be 0  
  53.         (args.os_version << 11) | args.os_patch_level)) # os version and patch level  
  54.     args.output.write(pack('16s', args.board.encode())) # asciiz product name  
  55.     args.output.write(pack('512s', args.cmdline[:512].encode()))  
  56.     sha = sha1()  
  57.     update_sha(sha, args.kernel)  
  58.     update_sha(sha, args.ramdisk)  
  59.     update_sha(sha, args.second)  
  60.     img_id = pack('32s', sha.digest())  
  61.     args.output.write(img_id)  
  62.     args.output.write(pack('1024s', args.cmdline[512:].encode()))  
  63.     pad_file(args.output, args.pagesize)  
  64.     return img_id  
  65. class ValidateStrLenAction(Action):  
  66.     def __init__(self, option_strings, dest, nargs=None, **kwargs):  
  67.         if 'maxlen' not in kwargs:  
  68.             raise ValueError('maxlen must be set')  
  69.         self.maxlen = int(kwargs['maxlen'])  
  70.         del kwargs['maxlen']  
  71.         super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)  
  72.     def __call__(self, parser, namespace, values, option_string=None):  
  73.         if len(values) > self.maxlen:  
  74.             raise ValueError('String argument too long: max {0:d}, got {1:d}'.  
  75.                 format(self.maxlen, len(values)))  
  76.         setattr(namespace, self.dest, values)  
  77. def write_padded_file(f_out, f_in, padding):  
  78.     if f_in is None:  
  79.         return  
  80.     f_out.write(f_in.read())  
  81.     pad_file(f_out, padding)  
  82. def parse_int(x):  
  83.     return int(x, 0)  
  84. def parse_os_version(x):  
  85.     match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)  
  86.     if match:  
  87.         a = int(match.group(1))  
  88.         b = c = 0  
  89.         if match.lastindex >= 2:  
  90.             b = int(match.group(2))  
  91.         if match.lastindex == 3:  
  92.             c = int(match.group(3))  
  93.         # 7 bits allocated for each field  
  94.         assert a < 128  
  95.         assert b < 128  
  96.         assert c < 128  
  97.         return (a << 14) | (b << 7) | c  
  98.     return 0  
  99. def parse_os_patch_level(x):  
  100.     match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x)  
  101.     if match:  
  102.         y = int(match.group(1)) - 2000  
  103.         m = int(match.group(2))  
  104.         # 7 bits allocated for the year, 4 bits for the month  
  105.         assert y >= 0 and y < 128  
  106.         assert m > 0 and m <= 12  
  107.         return (y << 4) | m  
  108.     return 0  
  109. def parse_cmdline():  
  110.     parser = ArgumentParser()  
  111.     parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),  
  112.                         required=True)  
  113.     parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))  
  114.     parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))  
  115.     parser.add_argument('--cmdline', help='extra arguments to be passed on the '  
  116.                         'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)  
  117.     parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)  
  118.     parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)  
  119.     parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)  
  120.     parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,  
  121.                         default=0x00f00000)  
  122.     parser.add_argument('--os_version', help='operating system version', type=parse_os_version,  
  123.                         default=0)  
  124.     parser.add_argument('--os_patch_level', help='operating system patch level',  
  125.                         type=parse_os_patch_level, default=0)  
  126.     parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)  
  127.     parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,  
  128.                         maxlen=16)  
  129.     parser.add_argument('--pagesize', help='page size', type=parse_int,  
  130.                         choices=[2**i for i in range(11,15)], default=2048)  
  131.     parser.add_argument('--id', help='print the image ID on standard output',  
  132.                         action='store_true')  
  133.     parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),  
  134.                         required=True)  
  135.     return parser.parse_args()  
  136. def write_data(args):  
  137.     write_padded_file(args.output, args.kernel, args.pagesize)  
  138.     write_padded_file(args.output, args.ramdisk, args.pagesize)  
  139.     write_padded_file(args.output, args.second, args.pagesize)  
  140. def main():  
  141.     args = parse_cmdline()  
  142.     img_id = write_header(args)  
  143.     write_data(args)  
  144.     if args.id:  
  145.         if isinstance(img_id, str):  
  146.             # Python 2's struct.pack returns a string, but py3 returns bytes.  
  147.             img_id = [ord(x) for x in img_id]  
  148.         print('0x' + ''.join('{:02x}'.format(c) for c in img_id))  
  149. if __name__ == '__main__':  
  150.     main()  

 

 

 

根據上面的信息,從boot.img中提取出壓縮的內核文件:

 

[cpp]  view plain  copy
 
  1. cd ../  
  2.   
  3. dd if=boot.img of=kernel.gz bs=1 skip=20660  

 

 



由於Android的內核文件經過了gzip壓縮,因此要拿到最終的Android內核文件還需要進行解壓縮:

 

[cpp]  view plain  copy
 
  1. gzip -d kernel.gz  

 

 

 

補充說明:關於Android的內核文件的提取和解壓方法很多,常用的工具也比較多,也可以使用下面的幾個工具之一來進行boot.img文件的解包和gzip的解壓縮操作。

 

[cpp]  view plain  copy
 
  1. bootimg.exe https://github.com/cofface/android_bootimg   
  2.   
  3. bootimg-tools   https://github.com/pbatard/bootimg-tools.git    
  4.   
  5. unpackbootimg   http://bbs.pediy.com/showthread.php?t=197334  
  6.   
  7. abootimg    https://github.com/ggrandou/abootimg  

 

 

解壓后的Android內核文件kernel中不包含符號信息。所以還要從Android設備中提取符號信息,盡管 /proc/kallsyms 文件中存儲了所有內核符號信息,但是從分析的結果來看,文件中存儲的內存地址值都是0,這是為了防止內核地址泄露。在dump 鏡像文件boot.img的Android設備上執行下面的命令,就會發現Android設備上的所有內核符號都被屏蔽隱藏了。

 

[cpp]  view plain  copy
 
  1. adb shell  
  2.   
  3. cat /proc/kallsyms  

 



 

為了要獲取Android內核中所有的內核符號信息,可以通過在root權限下,修改Andriod設備中的/proc/sys/kernel/kptr_restrict的值來實現,去掉Android內核符號的信息屏蔽。

 

[cpp]  view plain  copy
 
  1. adb shell  
  2. su  
  3.   
  4. # 查看默認值  
  5. cat /proc/sys/kernel/kptr_restrict  
  6.   
  7. # 關閉內核符號屏蔽  
  8. echo 0 > /proc/sys/kernel/kptr_restrict   
  9.   
  10. # 查看修改后的值  
  11. cat /proc/sys/kernel/kptr_restrict  
  12.   
  13. cat /proc/kallsyms  

 

 

關閉Android設備的內核符號的屏蔽以后,再次執行 cat /proc/kallsyms ,發現被隱藏的內核符號信息都顯示出來了。

 

在root權限下,將Android設備中的內核符號信息dump出來,導出到 /home/androidcode/AndroidDevlop/Nexus5Boot/syms.txt文件中。因此,Android內核文件的內核符號信息都保存在syms.txt文件中了。

 

[cpp]  view plain  copy
 
  1. # cat /proc/kallsyms > /sdcard/syms.txt  
  2.   
  3. # exit  
  4. $ exit  
  5.   
  6. $ adb pull /sdcard/syms.txt syms.txt  

 



 

三、IDA分析導出的Androd內核文件

將提取出來的Android內核 kernel文件 拖到IDA Pro 6.8中進行分析,設置處理器類型ARM Little-endian

 

 

在 ROM start addressLoading address 處填上0xc0008000,然后點擊 OK 完成 。

 

*至於這里為什么要設置 ROM start address 和 Loading address的地址為 0xc0008000? 具體的可以參考 bootheader這個數據結構,在這里需要關注其中幾個比較重要的值,這些值定義在boot/boardconfig.h中,不同的芯片對應vendor下不同的boardconfig,在這里我們的值分別是(分別是kernel/ramdis/tags 載入ram的物理地址):

 

[cpp]  view plain  copy
 
  1. #define PHYSICAL_DRAM_BASE   0x00200000   
  2. #define KERNEL_ADDR          (PHYSICAL_DRAM_BASE + 0x00008000)  
  3. #define RAMDISK_ADDR         (PHYSICAL_DRAM_BASE + 0x01000000)  
  4. #define TAGS_ADDR            (PHYSICAL_DRAM_BASE + 0x00000100)  
  5. #define NEWTAGS_ADDR         (PHYSICAL_DRAM_BASE + 0x00004000)  

上面這些值分中 KERNEL_ADDR 就是 ZTEXTADDR,RAMDISK_ADDR 就是 INITRD_PHYS,而 TAGS_ADDR 就是PARAMS_PHYS。bootloader會從boot.img的分區中將kernel和ramdisk分別讀入RAM上面定義的內存地址中,然后就會跳到ZTEXTADDR開始執行。

 

詳細的參考:Android boot.img 結構

OK,現在就可以在IDA中查看和分析Android內核文件中的代碼了,但是函數名稱不是很友好,很多系統函數的名稱都沒有顯示出來,只是顯示成IDA中默認的普通函數名稱。

 

前面我們已經將Androd內核文件中的內核符號信息都dump出來,這里大有用武之地。因此,向IDA中導入之前提取出來的內核符號信息就可以看到對應的函數名稱了。需要用到下面的python腳本:

 

[cpp]  view plain  copy
 
  1. ksyms = open("C:\Users\Fly2016\Desktop\Binwalk工具\Nexus5_kernel\syms.txt")  
  2. for line in ksyms:  
  3.     addr = int(line[0:8],16)  
  4.     name = line[11:]  
  5.     idaapi.set_debug_name(addr,name)  
  6.     MakeNameEx(addr,name,SN_NOWARN)  
  7.     Message("%08X:%sn"%(addr,name))  

 

在IDA的 File->Script Command中運行上述python腳本,之后就可以在IDA中成功添加內核符號信息使IDA顯示出正確的系統調用的函數名稱來。

 

大功告成,現在可以愉快的分析Android內核的代碼了:

 

總結:通過這種dump設備固件的方法,可以逆向分析沒有源碼的固件二進制文件,對於Android設備來說又可以通過這種方法修改Android的內核文件來進行反調試或者其他的目的。將修改好的Android內核文件使用boot.img等打包解包工具還原打包回到boot.img文件中,然后 fastboot flash boot boot.img  更新Android設備的內核文件即可達到目的。

 

 

學習鏈接

從Android手機中提取內核 <主要參考>

root技術背后android手機內核提取及逆向分析

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

提取Android內核的方法

android boot.img 結構

boot.img逆向分析

理解boot.img與靜態分析Android/linux內核


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM