Extracting Kernels from Android Devices

Keywords: Android github Python Mobile

This article blog link: http://blog.csdn.net/qq1084283172/article/details/57074695


I. Mobile Equipment Environment

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


2. Android Kernel Extraction

adb shell
su
cd /dev/block/platform/msm_sdcc.1/by-name
ls -l boot


Boot is a system symbol soft link, / dev/block/mmcblk0p19 is boot partition


Use dd to dump it into the sdcard folder of the Nexus 5 phone:

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


adb pull exports the dump boot.img file to the / home / Android code / Android Devlop / Nexus 5Book folder

adb pull /sdcard/boot.img /home/androidcode/AndroidDevlop/Nexus5Boot



Analysis of boot.img file with Binwall tool

1. Detailed instructions for the use of Binwall tools: Binwalk: Backdoor (Firmware) Analysis Tool

2. The github address of the Binwall tool: https://github.com/devttys0/binwalk

3. Binwall Tool's official website: http://binwalk.org/

4. The address of the wiki instructions for the Binwall tool: https://github.com/devttys0/binwalk/wiki

5. IDA plug-ins and scripts collected by Binwall tool authors: https://github.com/devttys0/ida

6. Installation instructions for Binwall tools: https://github.com/devttys0/binwalk/blob/master/INSTALL.md


Install the Binwall tool and analyze the boot.img file

cd /home/androidcode/AndroidDevlop/Nexus5Boot/binwalk-master

# Install binwalk as described in the binwalk tool
sudo python setup.py install

# Analysis of boot.img file
sudo binwalk ../boot.img >log


Screenshots of the results of the analysis:



After skipping the 2k header, the boot.img file includes two GZ packages: boot.img-kernel.gz, the Linux kernel, and boot.img-ramdisk.cpio.gz.
The general structure is as follows. For detailed information, you can refer to the android/platform/system/core/master/mkbootimg/bootimg.h file of Android source code to view the address of the booting.h file online. https://android.googlesource.com/platform/system/core/+/master/mkbootimg/bootimg.h .

/* tools/mkbootimg/bootimg.h
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
#include <stdint.h>
#ifndef _BOOT_IMAGE_H_
#define _BOOT_IMAGE_H_
typedef struct boot_img_hdr boot_img_hdr;
#define BOOT_MAGIC "ANDROID!"
#define BOOT_MAGIC_SIZE 8
#define BOOT_NAME_SIZE 16
#define BOOT_ARGS_SIZE 512
#define BOOT_EXTRA_ARGS_SIZE 1024
struct boot_img_hdr
{
    uint8_t magic[BOOT_MAGIC_SIZE];
    uint32_t kernel_size;  /* size in bytes */
    uint32_t kernel_addr;  /* physical load addr */
    uint32_t ramdisk_size; /* size in bytes */
    uint32_t ramdisk_addr; /* physical load addr */
    uint32_t second_size;  /* size in bytes */
    uint32_t second_addr;  /* physical load addr */
    uint32_t tags_addr;    /* physical addr for kernel tags */
    uint32_t page_size;    /* flash page size we assume */
    uint32_t unused;       /* reserved for future expansion: MUST be 0 */
    /* operating system version and security patch level; for
     * version "A.B.C" and patch level "Y-M-D":
     * ver = A << 14 | B << 7 | C         (7 bits for each of A, B, C)
     * lvl = ((Y - 2000) & 127) << 4 | M  (7 bits for Y, 4 bits for M)
     * os_version = ver << 11 | lvl */
    uint32_t os_version;
    uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
    uint8_t cmdline[BOOT_ARGS_SIZE];
    uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
    /* Supplemental command line data; kept here to maintain
     * binary compatibility with older versions of mkbootimg */
    uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
} __attribute__((packed));
/*
** +-----------------+ 
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages  
** +-----------------+
** | ramdisk         | m pages  
** +-----------------+
** | second stage    | o pages
** +-----------------+
**
** n = (kernel_size + page_size - 1) / page_size
** m = (ramdisk_size + page_size - 1) / page_size
** o = (second_size + page_size - 1) / page_size
**
** 0. all entities are page_size aligned in flash
** 1. kernel and ramdisk are required (size != 0)
** 2. second is optional (second_size == 0 -> no second)
** 3. load each element (kernel, ramdisk, second) at
**    the specified physical address (kernel_addr, etc)
** 4. prepare tags at tag_addr.  kernel_args[] is
**    appended to the kernel commandline in the tags.
** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
** 6. if second_size != 0: jump to second_addr
**    else: jump to kernel_addr
*/
#if 0
typedef struct ptentry ptentry;
struct ptentry {
    char name[16];      /* asciiz partition name    */
    unsigned start;     /* starting block number    */
    unsigned length;    /* length in blocks         */
    unsigned flags;     /* set to zero              */
};
/* MSM Partition Table ATAG
**
** length: 2 + 7 * n
** atag:   0x4d534d70
**         <ptentry> x n
*/
#endif
#endif

For the generation of boot.img file, you can refer to Android/platform/system/core/master/mkbootimg/boot img file of Android source code to view the address of booting file online: https://android.googlesource.com/platform/system/core/+/master/mkbootimg/mkbootimg.

#!/usr/bin/env python
# Copyright 2015, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from sys import argv, exit, stderr
from argparse import ArgumentParser, FileType, Action
from os import fstat
from struct import pack
from hashlib import sha1
import sys
import re
def filesize(f):
    if f is None:
        return 0
    try:
        return fstat(f.fileno()).st_size
    except OSError:
        return 0
def update_sha(sha, f):
    if f:
        sha.update(f.read())
        f.seek(0)
        sha.update(pack('I', filesize(f)))
    else:
        sha.update(pack('I', 0))
def pad_file(f, padding):
    pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)
    f.write(pack(str(pad) + 'x'))
def write_header(args):
    BOOT_MAGIC = 'ANDROID!'.encode()
    args.output.write(pack('8s', BOOT_MAGIC))
    args.output.write(pack('10I',
        filesize(args.kernel),                          # size in bytes
        args.base + args.kernel_offset,                 # physical load addr
        filesize(args.ramdisk),                         # size in bytes
        args.base + args.ramdisk_offset,                # physical load addr
        filesize(args.second),                          # size in bytes
        args.base + args.second_offset,                 # physical load addr
        args.base + args.tags_offset,                   # physical addr for kernel tags
        args.pagesize,                                  # flash page size we assume
        0,                                              # future expansion: MUST be 0
        (args.os_version << 11) | args.os_patch_level)) # os version and patch level
    args.output.write(pack('16s', args.board.encode())) # asciiz product name
    args.output.write(pack('512s', args.cmdline[:512].encode()))
    sha = sha1()
    update_sha(sha, args.kernel)
    update_sha(sha, args.ramdisk)
    update_sha(sha, args.second)
    img_id = pack('32s', sha.digest())
    args.output.write(img_id)
    args.output.write(pack('1024s', args.cmdline[512:].encode()))
    pad_file(args.output, args.pagesize)
    return img_id
class ValidateStrLenAction(Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        if 'maxlen' not in kwargs:
            raise ValueError('maxlen must be set')
        self.maxlen = int(kwargs['maxlen'])
        del kwargs['maxlen']
        super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
    def __call__(self, parser, namespace, values, option_string=None):
        if len(values) > self.maxlen:
            raise ValueError('String argument too long: max {0:d}, got {1:d}'.
                format(self.maxlen, len(values)))
        setattr(namespace, self.dest, values)
def write_padded_file(f_out, f_in, padding):
    if f_in is None:
        return
    f_out.write(f_in.read())
    pad_file(f_out, padding)
def parse_int(x):
    return int(x, 0)
def parse_os_version(x):
    match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
    if match:
        a = int(match.group(1))
        b = c = 0
        if match.lastindex >= 2:
            b = int(match.group(2))
        if match.lastindex == 3:
            c = int(match.group(3))
        # 7 bits allocated for each field
        assert a < 128
        assert b < 128
        assert c < 128
        return (a << 14) | (b << 7) | c
    return 0
def parse_os_patch_level(x):
    match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x)
    if match:
        y = int(match.group(1)) - 2000
        m = int(match.group(2))
        # 7 bits allocated for the year, 4 bits for the month
        assert y >= 0 and y < 128
        assert m > 0 and m <= 12
        return (y << 4) | m
    return 0
def parse_cmdline():
    parser = ArgumentParser()
    parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),
                        required=True)
    parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
    parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
    parser.add_argument('--cmdline', help='extra arguments to be passed on the '
                        'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
    parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
    parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
    parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
    parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
                        default=0x00f00000)
    parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
                        default=0)
    parser.add_argument('--os_patch_level', help='operating system patch level',
                        type=parse_os_patch_level, default=0)
    parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
    parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
                        maxlen=16)
    parser.add_argument('--pagesize', help='page size', type=parse_int,
                        choices=[2**i for i in range(11,15)], default=2048)
    parser.add_argument('--id', help='print the image ID on standard output',
                        action='store_true')
    parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
                        required=True)
    return parser.parse_args()
def write_data(args):
    write_padded_file(args.output, args.kernel, args.pagesize)
    write_padded_file(args.output, args.ramdisk, args.pagesize)
    write_padded_file(args.output, args.second, args.pagesize)
def main():
    args = parse_cmdline()
    img_id = write_header(args)
    write_data(args)
    if args.id:
        if isinstance(img_id, str):
            # Python 2's struct.pack returns a string, but py3 returns bytes.
            img_id = [ord(x) for x in img_id]
        print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
if __name__ == '__main__':
    main()


Based on the above information, extract the compressed kernel file from boot.img:

cd ../

dd if=boot.img of=kernel.gz bs=1 skip=20660




Because Android's kernel file has been compressed by gzip, it needs to be decompressed to get the final Android kernel file:

gzip -d kernel.gz



Supplementary Note: There are many ways to extract and decompress Android's kernel files, and there are many tools in common use. One of the following tools can also be used for unpacking boot.img files and gzip decompression operations.

bootimg.exe	https://github.com/cofface/android_bootimg 

bootimg-tools   https://github.com/pbatard/bootimg-tools.git  

unpackbootimg	http://bbs.pediy.com/showthread.php?t=197334

abootimg	https://github.com/ggrandou/abootimg



The decompressed Android kernel file kernel does not contain symbolic information. So we also need to extract symbol information from Android devices. Although all the kernel symbol information is stored in the / proc/kallsyms file, from the analysis results, the memory address value stored in the file is 0, which is to prevent the leakage of the kernel address. If you execute the following command on the Android device of the dump image file boot.img, you will find that all the kernel symbols on the Android device are masked and hidden.

adb shell

cat /proc/kallsyms




To get all the kernel symbol information in the Android kernel, you can modify / proc/sys/kernel/kptr_restrict in Android devices under root permission The value of Android is implemented to remove the information shielding of Android kernel symbols.

adb shell
su

# View default values
cat /proc/sys/kernel/kptr_restrict

# Turn off Kernel Symbol Shielding
echo 0 > /proc/sys/kernel/kptr_restrict 

# View the modified values
cat /proc/sys/kernel/kptr_restrict

cat /proc/kallsyms



After closing the masking of the Android device's kernel symbols, cat/proc/kallsyms is executed again, and the hidden information of the kernel symbols is displayed.


Under root permission, dump the kernel symbol information in Android device and export it to / home / Android code / Android Devlop / Nexus 5Boot / syms.txt file. Therefore, the kernel symbol information of the Android kernel file is stored in the syms.txt file.

# cat /proc/kallsyms > /sdcard/syms.txt

# exit
$ exit

$ adb pull /sdcard/syms.txt syms.txt





III. IDA Analysis of Derived Androd Kernel Files

Drag the extracted Android kernel file to IDA Pro 6.8 for analysis, and set the processor type to ARM Little-endian.

 


Fill in 0xc0008000 at ROM start address and Loading address and click Done.



OK, you can now view and analyze the code in the Android kernel file in IDA, but the function name is not very friendly, many system function names are not displayed, just as the default common function name in IDA.



We've dump out all the kernel symbols in Androd's kernel file before, and it's very useful here. Therefore, you can see the corresponding function name by importing the kernel symbol information extracted before you import it into IDA. The following python script is required:

ksyms = open("C:\Users\Fly2016\Desktop\Binwalk tool\Nexus5_kernel\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))


After running the python script in File - > Script Command of IDA, the kernel symbol information can be successfully added to IDA to display the correct function name of the system call.



With great success, you can now happily analyze the Android kernel code:



Summary: Through this dump device firmware method, firmware binary files without source code can be retrospectively analyzed. For Android devices, this method can be used to modify Android kernel files for debugging or other purposes. The modified Android kernel file is restored and packaged back into the boot.img file using the package unpacking tools such as boot.img, and then fastboot flash boot boot.img Update the Android device's kernel files to achieve this goal.



Learning Links:

Extracting Kernel from Android Mobile Phone <Major References>

Core Extraction and Reverse Analysis of android Mobile Phone Behind root Technology

Reverse modification of mobile phone kernel to bypass reverse debugging

Method of Extracting Android Kernel

boot.img Reverse Analysis

Understanding boot.img and static analysis of Android/linux kernels

Posted by ankhmor on Fri, 05 Apr 2019 17:15:30 -0700