基於Android arm64 Linux got 除錯
gdb除錯一下got實現,加深理解。涉及一些gdb常用命令,記錄一下。
環境:Ubuntu 15.10
程式碼: Android-6.0.1_r9
直接編譯的arm 64位eng版本。
啟動模擬器:emulator -system system.img -data userdata.img -ramdisk ramdisk.img
adb shell直接可用,Good!
示例程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int flag = 1;
int count = 0;
int main()
{
pid_t pid;
pid = getpid();
printf("sizeof(int) = %ld\n", sizeof(int));
printf("sizeof(long) = %ld\n", sizeof(long));
printf("Target pid = %d\n", pid);
while(flag) {
printf("Target is running:%d\n" , count);
count++;
sleep(10);
}
return 0;
}
反編譯命令,加上-S反編譯帶上原始碼:
/opt/android-6.0.1_r9/out/target/product/generic_arm64/obj/EXECUTABLES/hello_intermediates/LINKED$ aarch64-linux-android-objdump -S hello > hello.dis
hello: file format elf64-littleaarch64
Disassembly of section .plt:
00000000000006 d0 <__libc_init@plt-0x20>:
6d0: a9bf7bf0 stp x16, x30, [sp,#-16]!
6d4: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
6d8: f947d211 ldr x17, [x16,#4000]
6dc: 913e8210 add x16, x16, #0xfa0
6e0: d61f0220 br x17
6e4: d503201f nop
6e8: d503201f nop
6ec: d503201f nop
00000000000006f0 <__libc_init@plt>:
6f0: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
6f4: f947d611 ldr x17, [x16,#4008]
6f8: 913ea210 add x16, x16, #0xfa8
6fc: d61f0220 br x17
0000000000000700 <getpid@plt>:
700: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
704: f947da11 ldr x17, [x16,#4016]
708: 913ec210 add x16, x16, #0xfb0
70c: d61f0220 br x17
0000000000000710 <sleep@plt>:
710: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
714: f947de11 ldr x17, [x16,#4024]
718: 913ee210 add x16, x16, #0xfb8
71c: d61f0220 br x17
0000000000000720 <__cxa_atexit@plt>:
720: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
724: f947e211 ldr x17, [x16,#4032]
728: 913f0210 add x16, x16, #0xfc0
72c: d61f0220 br x17
0000000000000730 <printf@plt>:
730: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
734: f947e611 ldr x17, [x16,#4040]
738: 913f2210 add x16, x16, #0xfc8
73c: d61f0220 br x17
0000000000000740 <__register_atfork@plt>:
740: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
744: f947ea11 ldr x17, [x16,#4048]
748: 913f4210 add x16, x16, #0xfd0
74c: d61f0220 br x17
Disassembly of section .text:
0000000000000750 <main>:
int flag = 1;
int count = 0;
int main()
{
750: a9bd7bfd stp x29, x30, [sp,#-48]!
754: 910003fd mov x29, sp
758: a90153f3 stp x19, x20, [sp,#16]
75c: f90013f5 str x21, [sp,#32]
pid_t pid;
pid = getpid();
760: 97ffffe8 bl 700 <getpid@plt>
764: 2a0003f3 mov w19, w0
printf("sizeof(int) = %ld\n", sizeof(int));
768: 90000000 adrp x0, 0 <abitag-0x250>
76c: d2800081 mov x1, #0x4 // #4
770: 9123a000 add x0, x0, #0x8e8
printf("sizeof(long) = %ld\n", sizeof(long));
printf("Target pid = %d\n", pid);
while(flag) {
774: b0000094 adrp x20, 11000 <__dso_handle>
int main()
{
pid_t pid;
pid = getpid();
printf("sizeof(int) = %ld\n", sizeof(int));
778: 97ffffee bl 730 <printf@plt>
printf("sizeof(long) = %ld\n", sizeof(long));
77c: 90000002 adrp x2, 0 <abitag-0x250>
780: d2800101 mov x1, #0x8 // #8
784: 91240040 add x0, x2, #0x900
788: 97ffffea bl 730 <printf@plt>
printf("Target pid = %d\n", pid);
78c: 90000001 adrp x1, 0 <abitag-0x250>
790: 91246020 add x0, x1, #0x918
794: 2a1303e1 mov w1, w19
798: 97ffffe6 bl 730 <printf@plt>
while(flag) {
79c: b9400a83 ldr w3, [x20,#8]
7a0: 34000203 cbz w3, 7e0 <main+0x90>
printf("Target is running:%d\n", count);
7a4: 90000015 adrp x21, 0 <abitag-0x250>
7a8: b0000084 adrp x4, 11000 <__dso_handle>
7ac: 9124c2b5 add x21, x21, #0x930
7b0: 91003093 add x19, x4, #0xc
printf("sizeof(int) = %ld\n", sizeof(int));
printf("sizeof(long) = %ld\n", sizeof(long));
printf("Target pid = %d\n", pid);
while(flag) {
7b4: 91002294 add x20, x20, #0x8
printf("Target is running:%d\n", count);
7b8: b9400261 ldr w1, [x19]
7bc: aa1503e0 mov x0, x21
7c0: 97ffffdc bl 730 <printf@plt>
count++;
7c4: b9400265 ldr w5, [x19]
sleep(10);
7c8: 52800140 mov w0, #0xa // #10
printf("Target pid = %d\n", pid);
while(flag) {
printf("Target is running:%d\n", count);
count++;
7cc: 110004a6 add w6, w5, #0x1
7d0: b9000266 str w6, [x19]
sleep(10);
7d4: 97ffffcf bl 710 <sleep@plt>
printf("sizeof(int) = %ld\n", sizeof(int));
printf("sizeof(long) = %ld\n", sizeof(long));
printf("Target pid = %d\n", pid);
while(flag) {
7d8: b9400287 ldr w7, [x20]
7dc: 35fffee7 cbnz w7, 7b8 <main+0x68>
printf("Target is running:%d\n", count);
count++;
sleep(10);
}
return 0;
}
7e0: 52800000 mov w0, #0x0 // #0
7e4: f94013f5 ldr x21, [sp,#32]
7e8: a94153f3 ldp x19, x20, [sp,#16]
7ec: a8c37bfd ldp x29, x30, [sp],#48
7f0: d65f03c0 ret
模擬器上輸入:gdbserver :1234 hello
adb forward tcp:1234 tcp:1234
主機端執行:aarch64-linux-android-gdb
gdb命令:target remote :1234
list命令檢視原始碼,提示file命令載入symbol.
android上編譯的動態連結的應用程式類似有位置無關特性,每次載入的地址可能都會變動。靜態編譯的不受此影響。
hello程式碼段載入至0x5586738000(每次執行都會變動).
aarch64-linux-android-readelf -S hello檢視程式碼段偏移0x750
使用如下命令載入symbol,之後可以使用list命令檢視程式碼:
add-symbol-file hello (0x5586738000+0x750)
對應的remove-symbol-file -a addr 可以刪除載入過的符號資訊,加錯了可以重新來過!
使用如下命令可以設定載入庫檔案資訊:
set solib-absolute-prefix /opt/android-6.0.1_r9/out/target/product/generic_arm64/symbols
暫時只有linker64載入了。
設定幾個除錯斷點。
0000000000000700 <getpid@plt>:
700: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
704: f947da11 ldr x17, [x16,#4016]
708: 913ec210 add x16, x16, #0xfb0
70c: d61f0220 br x17
0000000000000710 <sleep@plt>:
710: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
714: f947de11 ldr x17, [x16,#4024]
718: 913ee210 add x16, x16, #0xfb8
71c: d61f0220 br x17
0000000000000720 <__cxa_atexit@plt>:
720: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
724: f947e211 ldr x17, [x16,#4032]
728: 913f0210 add x16, x16, #0xfc0
72c: d61f0220 br x17
0000000000000730 <printf@plt>:
730: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
734: f947e611 ldr x17, [x16,#4040]
738: 913f2210 add x16, x16, #0xfc8
73c: d61f0220 br x17
這幾個庫函式跳轉首地址剛好0x700 0x710 0x720 0x730, 當然不能忘了我們的偏移:0x5586738000
就以sleep為例,x17第一次拿到的地址應該不是正確sleep地址,got修復之後才是正確的。
watch *(0x5586738000+0x10000+0xfb8),來跟蹤檢視。
(gdb) p/x 0x5586738000+0x10000+0xfb8
$1 = 0x5586748fb8
如下列印程式碼也可以得到其地址
(gdb) x/8i 0x5586738710
0x5586738710: adrp x16, 0x5586748000
0x5586738714: ldr x17, [x16,#4024]
0x5586738718: add x16, x16, #0xfb8
0x558673871c: br x17
0x5586738720: adrp x16, 0x5586748000
0x5586738724: ldr x17, [x16,#4032]
0x5586738728: add x16, x16, #0xfc0
0x558673872c: br x17
(gdb) p/x 0x5586748000+4024
$2 = 0x5586748fb8
(gdb) x/8i 0x5586748fb8
0x5586748fb8: .inst 0x000006d0 ; undefined
0x5586748fbc: .inst 0x00000000 ; undefined
0x5586748fc0: .inst 0x000006d0 ; undefined
0x5586748fc4: .inst 0x00000000 ; undefined
0x5586748fc8: .inst 0x000006d0 ; undefined
0x5586748fcc: .inst 0x00000000 ; undefined
0x5586748fd0: .inst 0x000006d0 ; undefined
0x5586748fd4: .inst 0x00000000 ; undefined
(gdb)
b main
再設定一個觀察count值變化的量,方便後續主動暫停。
helllo symbol偏移資訊都白加了?或者只加了程式碼段?管它呢,有了偏移一樣幹活。
(gdb) p/x 0x5586738000+0x1100c
$5 = 0x558674900c
(gdb) watch *0x558674900c
Hardware watchpoint 3: *0x558674900c
(gdb)
就整這三個斷點試試。
第二個斷點先到。
此時還是未顯示其他庫載入。
注意一個細節,檢視下程序的載入庫資訊。
從程序的資訊看到,其實庫已經載入完畢,這些資訊直接從核心呈現出來。gdb載入比這個慢,正常。
執行bt命令:
深入載入細節不是今天重點,有興趣的自行研究。
檢視資訊,確實更新,而且更新後的地址確實有程式碼存在,雖然暫時不能證明就是sleep地址,待庫symbol完整加入之後(從上面可見,此時庫檔案核心已經載入完成)。
繼續全速執行。
這回才停在main首地址的地方,由此可見got段修復在main執行前就成功完成了。
info r 可以顯示常規暫存器的值,記錄一下(x86上不能簡寫, info registers)。
display/8i ($pc-16),這個可以每次停住顯示PC前後的幾條命令,很喜歡用。
此時再看看庫檔案載入資訊,基本都有了。
再檢視剛才sleep修改的地址,現在有資訊顯示了。
再看看其他幾個got是不是也修改了:
大部分都有值了,就看看__register_atfork 以及printf是不是已經設定。
單步執行幾次
此時當然直接就是正確的地址了。
再c兩次,第三個斷點設了,還不知能不能幹活。
還不錯,如預期工作。
其實大家看我很簡單的設定了這三個斷點,這是事後總結此文的時候整的,前期除錯還是走了很多彎路。
主要是記錄下gdb玩法。
0000000000000710 <sleep@plt>:
710: 90000090 adrp x16, 10000 <pthread_atfork+0xf754>
714: f947de11 ldr x17, [x16,#4024]
718: 913ee210 add x16, x16, #0xfb8
71c: d61f0220 br x17
got段的理解,以上面這個程式碼簡單總結下:
main函式呼叫 sleep 不直接跳轉執行庫函式sleep(就是為了能做到動態載入),而是跳到如上程式碼,如上程式碼很加單,x17放的就是執行程式碼地址,只不過第一次不是正確的地址,需要修復下,後續每次跳到這,就是正確的地址,跳轉執行。