U+E000 私用領域

しがない緑茶好きのメモ的なの。

国産スマホをroot化するには LSM解除編(前半)

ようやっと続き&本筋です。
前回は、root化の基本と、国産スマホでroot化がしにくい理由がLSMにある事を説明しました。
で、今回はこれを解除していきます。

ツールのビルド

今回の作業で使っていくツールをビルドします。
ビルドにはAndroid NDKを使っていきます。
が、古いツールですので、最新版だとビルドで引っかかります。
なので古いAndroid NDKを使う必要があります。だいたいr9bあたりで通ります。
ふるいのはここから落とせるようになってますが、r9bとなると、もはや載っていません()
なのでリンクを以下に貼っておきます。

2019/12/23:[修正]全部リンクが死んでた上に、アーカイブの最後のr10eでもビルド、動作OKでした。
Windows (64bit)
Windows (32bit)
OS X (64bit)
Linux(64bit)
導入方法は説明してたら長くなるので、使ってるOSに合わせて調べてください。
あと、Gitも必要ですが、こちらも長くなるので、使ってるOSに合わせて調べてください。
これ以降はUbuntu(19.04)環境で話を進めていきます。
Android NDKもGitも準備できたら、ツールのソースを落としていきます。
以下のソースをgit clone --recursive <URL>して落としてください。
backdoor_mmap_tools
android_get_essential_address
落としたらcd <git cloneしたフォルダ>で移動します。
移動したらndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mkでビルドできます。
スペックによりますが、少し時間がかかります。
ビルドが終わったら、./libs/armeabiにバイナリがあるので、どこかわかりやすい場所にまとめましょう。
2つともビルドしたら、android_get_essential_addressの./device_database/device.dbもわかりやすい場所に置いておいてください。
これで必要なツールのビルドは終わりです。

実践

前回書いてた通り、今回はURBANO L02(KYY22)で実践していきます。
ビルド番号は102.0.0f00です。
前代の似たやつでL01(KYY21)がいますが、あれは若干アドレスが違うだけなので、KYY21でも代用できます。

とりあえずツール類を端末内に入れていきます。
今回はツールを/home/fan/toolsにまとめた提で書くので、状況によって読み替えてください。

fan@thinkpad:~$ adb push /home/fan/tools/* /data/local/tmp
/home/fan/tools/device.db: 1 file pushed. 3.1 MB/s (88064 bytes in 0.027s)
/home/fan/tools/disable_ccsecurity: 1 file pushed. 4.4 MB/s (383204 bytes in 0.084s)
/home/fan/tools/fix_cve_2013_6282: 1 file pushed. 5.2 MB/s (387304 bytes in 0.071s)
/home/fan/tools/get_essential_address: 1 file pushed. 5.3 MB/s (403836 bytes in 0.072s)
/home/fan/tools/install_backdoor: 1 file pushed. 5.6 MB/s (457144 bytes in 0.077s)
/home/fan/tools/kallsymsprint: 1 file pushed. 3.6 MB/s (64516 bytes in 0.017s)
/home/fan/tools/load_mmc_sc01e: 1 file pushed. 4.2 MB/s (383196 bytes in 0.087s)
/home/fan/tools/read_value: 1 file pushed. 3.3 MB/s (63976 bytes in 0.018s)
/home/fan/tools/reset_security_ops: 1 file pushed. 3.8 MB/s (383144 bytes in 0.097s)
/home/fan/tools/run_autoexec: 1 file pushed. 4.0 MB/s (383228 bytes in 0.092s)
/home/fan/tools/run_root_shell: 1 file pushed. 3.3 MB/s (383216 bytes in 0.111s)
/home/fan/tools/unload_mmc_sc01e: 1 file pushed. 4.0 MB/s (383192 bytes in 0.091s)
/home/fan/tools/unlock_lsm_fjsec: 1 file pushed. 4.1 MB/s (383144 bytes in 0.089s)
/home/fan/tools/unlock_lsm_miyabi: 1 file pushed. 4.4 MB/s (388488 bytes in 0.084s)
/home/fan/tools/unlock_mmc_protect: 1 file pushed. 4.2 MB/s (387256 bytes in 0.087s)
/home/fan/tools/unlock_mount_fjsec: 1 file pushed. 3.9 MB/s (383144 bytes in 0.094s)
/home/fan/tools/unlock_sec_sc01e: 1 file pushed. 2.7 MB/s (64496 bytes in 0.023s)
/home/fan/tools/unlock_sec_sc04e: 1 file pushed. 3.1 MB/s (64640 bytes in 0.020s)
/home/fan/tools/unlock_security_module: 1 file pushed. 3.1 MB/s (458032 bytes in 0.143s)
/home/fan/tools/write_value: 1 file pushed. 3.4 MB/s (63976 bytes in 0.018s)
21 files pushed. 3.8 MB/s (8032662 bytes in 2.014s)

端末に突っ込んだら、権限を調整します。

fan@thinkpad:~$ adb shell
shell@android:/ $ cd /data/local/tmp
shell@android:/data/local/tmp $ chmod 755 *
shell@android:/data/local/tmp $ chmod 644 device.db

権限を渡し終えたら、get_essential_addressを実行します。
これは、androidカーネル内からroot権限取得や、カーネルをいじくるのに必要なアドレスを探してくれるツールです。
これなしでも求めようと思えば求めれますが、はっきり言って大変ですし、これが通らない場合はカーネルいじくるのにかなり苦労する羽目になります。

shell@android:/data/local/tmp $ ./get_essential_address                        


Device detected: KYY22 (102.0.0f00)

Try without fb_mem_exploit fist...

Try to find address in memory...
Attempt msm_cameraconfig exploit...
Detected kernel physical address at 0x80208000 from iomem

You need to manage to get remap_pfn_range address.

Try copying kernel memory... It will take a long time.
Attempt pingpong exploit...
No icmp socket available
Attempt futex exploit...
futex_exploit: Server started
Search address in memory...
Using kallsyms_in_memory...
Essential address are:
  prepare_kernel_cred = 0xc009eb68
  commit_creds = 0xc009e654
  remap_pfn_range = 0xc0113c60
  vmalloc_exec = 0xc0120570
  ptmx_fops = 0xc0f6c8f0

すんなりと取れました。
次はこのアドレスを元にroot権限を奪ってみます。

shell@android:/data/local/tmp $ ./install_backdoor                             
Attempt acdb exploit...
KYY22 (102.0.0f00) is not supported.
Attempt put_user exploit...
ioctl: Bad address
Attempt pingpong exploit...
No icmp socket available
Attempt futex exploit...
install_mmap: success
shell@android:/data/local/tmp $ ./run_root_shell                               
shell@android:/data/local/tmp # id
uid=0(root) gid=0(root)

not supportedと言いつつやってのけるinstall_backdoorちゃんは可愛いですね。(錯乱)
install_backdoorはmmapを使用してカーネルの権限を奪うものみたいです。
みたいです。というのは、自分もまだ良く理解できてないからです。勉強不足。
これによってカーネルをいじくれます。
で、run_root_shellはinstall_backdoorが奪った権限を使ってrootを奪う感じみたいです。
余談ですが、run_root_shellには2つ種類があります。
一つ目は今使っているこれで、もう一つはこれです。
どちらもfi01氏作なのは変わりないですが、install_backdoorに依存するかしないかの違いがあります。
この違いはN-05D編で利用するので、少し覚えておいてください。

で、root権限が奪えたわけですが、今の状態はroot化とは言えません。
suバイナリを置くべき場所に置けていないからです。
仮rootと呼ばれてる状態ですね。(仮rootっていう言葉自体、ややおかしい気がするので個人的には嫌い)
というわけでsuバイナリを置くべき場所、今回は/system/xbinに置いてみますが、/systemは書き込み可能でマウントされていません。
なのでmountコマンドを使用して書き込み可能でリマウントしてみましょう。

shell@android:/data/local/tmp # mount -o rw,remount /system
mount: Operation not permitted

はい。前回の最後でやった通りですね。LSMちゃんが邪魔をしてきます。
なのでLSMを解除して、root権限がroot権限らしくなるようにします。
とりあえず、邪魔しているのが何処のどいつか確認します。
dmesgを使ってカーネルのメッセージを出力します。

shell@android:/data/local/tmp # dmesg | grep mount                             
<4>#0[ 4765.660521] no permission in kclsm_sb_mount pid=8340 pname=mount realpath=/system dev_name=/dev/block/platform/msm_sdcc.1/by-name/system

というわけで、犯人はkclsm_sb_mountという事がわかりました。
このkclsm_sb_mount挙動を確認するために、カーネルのソースを落としてソースを見てみましょう。
ソースは http://android-dev.kyocera.co.jp/source/versionSelect_URBANO_L02.html
にあるので、そこから自分のビル番に合ったものを取ってきましょう。
落としてきたら展開して中身を見てみます。
どうやら ./kernel/security/kyocera/kclsm.cに記述されているようです。
104行目あたりに、

#ifdef CONFIG_SECURITY_KCLSM_MOUNT
static int kclsm_sb_mount(char *dev_name, struct path *path,
                char *type, unsigned long flags, void *data)
{
    int i, ret = 0;
    char *ptr, *realpath = NULL;

    ptr = kmalloc(PATH_MAX, GFP_KERNEL);KCLSM_SYSTEM_MOUNT_POINT
    if (!ptr)
        return -ENOMEM;

    realpath = d_path(path, ptr, PATH_MAX);

    if (strncmp(realpath, KCLSM_SYSTEM_MOUNT_POINT,
            strlen(KCLSM_SYSTEM_MOUNT_POINT)) != 0)
        goto out;

    if (strcmp(realpath, KCLSM_SYSTEM_MOUNT_POINT) != 0) {
        if (current->pid == 1)
            goto out;
        else
            goto err;
    }

    if (flags & MS_REMOUNT)
        goto err;

    for (i=0; kclsm_mount_checklist[i] != NULL; i++)
        if (strcmp(dev_name, kclsm_mount_checklist[i]) == 0)
            goto out;

err:
    pr_warn("no permission in %s pid=%d pname=%s realpath=%s dev_name=%s\n",
        __FUNCTION__, current->pid, current->comm, realpath, dev_name);
    ret = -EPERM;
out:
    kfree(ptr);
    return KCLSM_RETURN(ret);
}
#endif

といった感じで記述されています。
で、エラー文から察せる通り、realpathというのがmountの時に渡したパスです。
これとKCLSM_SYSTEM_MOUNT_POINT(/system)を比べて、一緒だったらgoto err、一緒じゃなかったらgoto outして正常に処理する感じです。
この処理の分岐の仕方を覚えておいてください。
次にカーネルを書き換えて、この処理をどうにかします。
流れとしては、
1. カーネルをダンプ
2. シンボル情報から、kclsm_sb_mountの実体があるアドレスを特定
3. そのアドレス部分をディスアセンブル
4. write_valueを使って処理をいい感じに書き換え
といった感じです。
では、カーネルをダンプしていきましょう。
ダンプの方法についてですが、どうやらfi01氏がfutex_dumpというものを作っているようです。
しかし、いくら検索してもリンク切れで落とせないので、今回は魔界塔士氏のdumpバイナリを使用させていただきます。
https://drive.google.com/open?id=0B4HY7rXvjVERN2lrM0FqbzZpdUk
落としたら/data/local/tmpにadb pushして権限を調整し、実行します。

fan@thinkpad:~$ adb push /home/fan/tools/dump /data/local/tmp
/home/fan/tools/dump: 1 file pushed. 2.2 MB/s (64088 bytes in 0.028s)

shell@android:/data/local/tmp # chmod 755 dump
shell@android:/data/local/tmp # ./dump 
ptmx_mmap_fd OK 
Dump kernel memory...
Dumped.

shell@android:/data/local/tmp # ls | grep kernel
kernel.dump

kernel.dumpとしてダンプされていますね。
次にシンボル情報を引っ張り出します。
これはkallsymsprintがやってくれます。

shell@android:/data/local/tmp # ./kallsymsprint > kyy22.txt                
[+]mmap
  mem=20000000 length=02000000 offset=a0008000
[+]kallsyms_addresses=c089af60
  count=00016733
[+]kallsyms_num_syms=00016733
[+]kallsyms_names=c08f4c40
[+]kallsyms_markers=c09f66a0
[+]kallsyms_token_table=c09f6c40
[+]kallsyms_token_index=c09f7000
[+]kallsyms_lookup_name

カーネルとシンボル情報がそろったのでディスアセンブルをしていきます。
とりあえずカーネルのダンプとシンボル情報が書かれたテキストファイルを端末から取り出します。

fan@thinkpad:~$ mkdir kyy22
fan@thinkpad:~$ cd kyy22
fan@thinkpad:~/kyy22$ adb pull /data/local/tmp/kernel.dump
/data/local/tmp/kernel.dump: 1 file pulled. 4.2 MB/s (33554432 bytes in 7.682s)
fan@thinkpad:~/kyy22$ adb pull /data/local/tmp/kyy22.txt
/data/local/tmp/kyy22.txt: 1 file pulled. 2.8 MB/s (2753884 bytes in 0.929s)

で、ディスアセンブラですが、これもまたfi01氏が素晴らしいものを作っています。
arm7-dasm
これを落としてコンパイルします。

fan@thinkpad:~/kyy22$ git clone https://github.com/fi01/arm7-dasm
Cloning into 'arm7-dasm'...
remote: Enumerating objects: 71, done.
remote: Total 71 (delta 0), reused 0 (delta 0), pack-reused 71
Unpacking objects: 100% (71/71), done.
fan@thinkpad:~/kyy22$ cd arm7-dasm/
fan@thinkpad:~/kyy22/arm7-dasm$ gcc -o arm7-dasm arm7-dasm.c
arm7-dasm.c: In function ‘read_kallsyms’:
arm7-dasm.c:268:20: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘size_t’ {aka ‘long unsigned int’} [-Wformat=]
  fprintf(stderr, "%d symbols are loaded.\n", symbol_len);
                   ~^                         ~~~~~~~~~~
                   %ld
fan@thinkpad:~/kyy22/arm7-dasm$ ls
README.md  arm7-dasm  arm7-dasm.c  arm7core.h  arm7dasm.c  emu.h

エラーがでましたが、通ったので良しとします(おい)
使い方についてですが、今回はシンボル情報があるので、README.mdに書いてある通り、
Disassemble with symbol table:
$ ./arm7-dasm [kernel image filename] [image base address] [start address or symbol name] [symbol table file]
といった感じです。
今回の場合、kernel imageはkernel.dumpとしてダンプ済みで、base addressはc0008000、symbol nameはkclsm_sb_mountでsymbol table fileはkyy22.txtとして出力済みなので、

./arm7-dasm kernel.dump c0008000 kclsm_sb_mount kyy22.txt

となります。
実際にやってみます。

fan@thinkpad:~/kyy22$ ./arm7-dasm kernel.dump c0008000 kclsm_sb_mount kyy22.txt > kclsm_sb_mount.dasm
90981 symbols are loaded.
fan@thinkpad:~/kyy22$ head kclsm_sb_mount.dasm 
Disassemble 0xc026bae8 - 0xc026bbf4
c026bae8:             <kclsm_sb_mount>
c026bae8: e9 2d 41 f3     STMPW   [SP], { R0-R1, R4-R8, LR }
c026baec: e1 a0 70 03     MOV     R7, R3 
c026baf0: e5 9f 31 00     LDR     R3, =$c0f53a98 <kmalloc_caches> [$c026bbf8]
c026baf4: e1 a0 50 00     MOV     R5, R0 
c026baf8: e1 a0 60 01     MOV     R6, R1 
c026bafc: e5 93 00 30     LDR     R0, [R3, #$30]
c026bb00: e3 50 00 00     CMPS    R0, #$0
c026bb04: 0a 00 00 05     BEQ     $c026bb20

うまくディスアセンブルできたようです。
で、これからアセンブリを読んでいくわけですが、かなり長くなったので後半にまわします。
後半は処理を書き換えて書き込み可でマウントできるようにして、他のLSMも解除していきます。
シンボル情報を | grep kclsmしてみるとわかりますが、chrootやpivotroot、mknodなんかが使えないです。
これらを解除しないとrootの自由が感じれず、精神衛生上悪いので解除します。

今回始めてマークダウン記法を使ってみましたが、これかなりいい感じです。