一、目標(biāo)
在做代碼還原的時(shí)候坛梁,經(jīng)常能看到一些奇怪的寄存器和奇怪的指令:
vldr s15, [r1]
vadd.f32 s15, s14, s15
很像某些流量明星而姐,看上去很眼熟,仔細(xì)看看又不認(rèn)識划咐。
它們就是傳說中的浮點(diǎn)數(shù)運(yùn)算拴念,今天我們來點(diǎn)亮一個(gè)很有用的技能樹: Unidbg調(diào)試浮點(diǎn)數(shù)運(yùn)算
二钧萍、步驟
先寫個(gè)floatdemo
有這么一個(gè)祖?zhèn)鞯乃惴ê瘮?shù)。
extern "C" JNIEXPORT jstring JNICALL
Java_com_fenfei_app_floatdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject Obj, jdouble value) {
std::string hello = "Hello from C++";
double p=3.14159;
double s,v,rc;
v = 2*p*value;
s = p*value*value;
rc = v+s;
hello = std::to_string(rc);
return env->NewStringUTF(hello.c_str());
}
算出圓的周長和面積政鼠,然后再把它們相加风瘦。
高級語言就是好,一目了然公般。
IDA一把
可以看出兩個(gè)區(qū)別万搔, 一個(gè)是寄存器不一樣,普通運(yùn)算使用的寄存器是R0-Rx,浮點(diǎn)數(shù)運(yùn)算使用的是D0-Dx (其實(shí)還有 S0-Sx)官帘,另一個(gè)是指令不一樣瞬雹,普通運(yùn)算是MOV、MUL刽虹,而浮點(diǎn)數(shù)運(yùn)算使用的是VMOV酗捌,VMUL,感覺就是普通運(yùn)算的VIP版涌哲。
第一個(gè)知識點(diǎn)就出來了胖缤,V開頭的指令就是浮點(diǎn)數(shù)運(yùn)算指令,Dx Sx Qx 就是浮點(diǎn)數(shù)寄存器阀圾。
Unidbg亮相
按照 Unidbg模擬執(zhí)行某段子so實(shí)操教程(一) 先把框架搭起來 這個(gè)框架把我們剛才編譯的 floatdemo.apk 跑起來哪廓,然后增加一個(gè) stringFromJNI 函數(shù)的調(diào)用。
private String callfun(String methodSign, Object ...args) {
DvmObject mainactivity = MainActivity_dvmclass.newObject(null);
Object value = mainactivity.callJniMethodObject(emulator,methodSign,args).getValue();
return value.toString();
}
由于 stringFromJNI 不是靜態(tài)(static)的類函數(shù),所以我們需要先創(chuàng)建個(gè)一個(gè) MainActivity 對象初烘,才可以調(diào)用它的方法撩独。
先跑一下看看結(jié)果
Find native function Java_com_fenfei_app_floatdemo_MainActivity_stringFromJNI(D)Ljava/lang/String; => RX@0x4000c6c9[libnative-lib.so]0xc6c9
JNIEnv->NewStringUTF("150.796320") was called from RX@0x4000c73d[libnative-lib.so]0xc73d
ret:150.796320
emulator destroy...
我們傳了個(gè)參數(shù)6,半徑是6的圓账月, 周長是 37.699综膀, 面積是113.097 ,它們之和是 150.796局齿。 結(jié)果沒毛病剧劝,那我們開始調(diào)試了。
Unidbg調(diào)試
從剛才運(yùn)行的結(jié)果里我們知道 stringFromJNI 函數(shù)的地址在 0xc6c9抓歼, 那么我們現(xiàn)在需要在這個(gè)地址下個(gè)斷點(diǎn)讥此,讓調(diào)試器停在這個(gè)地址。
Unidbg的調(diào)試功能依然很強(qiáng)大谣妻,它支持三種調(diào)試模式 CONSOLE萄喳、GDB和IDA,目前我用的順手的是 CONSOLE 模式蹋半,今天先介紹這個(gè)他巨。
開啟調(diào)試炒雞簡單,加上這兩行代碼就行
Debugger MyDbg = emulator.attach(DebuggerType.CONSOLE);
MyDbg.addBreakPoint(module.base + 0xc6c9);
運(yùn)行一下,就順利的進(jìn)入到調(diào)試器命令行了染突,直接回車捻爷,會顯示目前支持的調(diào)試命令。
我們是新手嘛份企,先掌握一個(gè)n和s兩個(gè)命令就行了也榄,n是單步步過,就是執(zhí)行一條指令司志,步過函數(shù)調(diào)用甜紫;s是單步步入,就是執(zhí)行一條指令骂远,進(jìn)入函數(shù)調(diào)用囚霸。
n命令跑幾下來到我們要分析的浮點(diǎn)數(shù)運(yùn)算的位置,發(fā)現(xiàn)尷尬了……
Unidbg調(diào)試器只顯示了Rx寄存器吧史,沒有顯示Dx系列的寄存器,這下怎么分析唠雕,不能盲摸呀贸营?
打開Unidbg浮點(diǎn)數(shù)寄存器顯示
Unidbg是支持浮點(diǎn)數(shù)運(yùn)算模擬的,那么一定是有地方去讀取浮點(diǎn)數(shù)寄存器的岩睁,只是沒有顯示出來而已钞脂。
我們先分析下Unidbg調(diào)試時(shí)寄存器顯示部分的代碼。
先搜索 r0= 在哪里處理的捕儒?
showRegs 就是顯示寄存器冰啃, 當(dāng)參數(shù)為null的時(shí)候,通過 ARM.getAllRegisters 來顯示所有的寄存器刘莹。但是為啥沒有顯示浮點(diǎn)寄存器呢阎毅?奇怪。
我們再往下翻点弯,發(fā)現(xiàn)在ARM64的模擬下顯示了Q0-Q31寄存器扇调,通過查閱資料,我們知道了原來它們都是一伙的抢肛。
那ARM32先放一下狼钮,我們把模擬環(huán)境切換到ARM64
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.fenfei.runfloatdemo").build(); // 創(chuàng)建模擬器實(shí)例,要模擬32位或者64位捡絮,在這里區(qū)分
再跑一下熬芜,調(diào)試器沒有激活?
這是為什么福稳? 原來我們把模擬器從Arm32切換到了Arm64涎拉,那么載入的so就是64位的了,所以 stringFromJNI 函數(shù)的地址也變了,需要把斷點(diǎn)下在新的地址 0x12738 上面曼库。
這下不一樣了区岗,浮點(diǎn)寄存器都顯示出來了。
優(yōu)化一下浮點(diǎn)寄存器的顯示
李老板: 奮飛呀毁枯,這個(gè)0x400921f9f01b866e是啥意思呀慈缔,你是不是搞錯(cuò)了,浮點(diǎn)數(shù)寄存器顯示的咋不是 3.14159 ,而是這個(gè)亂七八糟的數(shù)據(jù)种玛?
奮飛: 老板藐鹤,程序員的母語就是16進(jìn)制,沒有一眼把 0x400921f9f01b866e 認(rèn)出是 3.14159的赂韵,晚飯是不配加雞腿的娱节,也不配變禿的。
有理想的同學(xué)請自行搜索 IEEE754 二進(jìn)制浮點(diǎn)數(shù)算術(shù)標(biāo)準(zhǔn)
其他的同學(xué)請和我一起優(yōu)化下浮點(diǎn)寄存器的顯示祭示。
由于飛哥目前為止還沒有變禿肄满,確實(shí)也看不出來這玩意就是 3.14159, 只好另辟蹊徑,給大家傳授一個(gè)神奇的函數(shù):
public static double bytes2Double(byte[] arr) {
long value = 0;
for (int i = 0; i < 8; i++) {
value |= ((long) (arr[i] & 0xff)) << (8 * i);
}
return Double.longBitsToDouble(value);
}
// 在showRegs64函數(shù)里面加個(gè)顯示
case Arm64Const.UC_ARM64_REG_Q0:
byte[] data = backend.reg_read_vector(reg);
double bOut = bytes2Double(data);
if (data != null) {
builder.append("\n>>>");
builder.append(String.format(Locale.US, " q0=0x%s(%.3f)", newBigInteger(data).toString(16),bOut));
}
break;
科學(xué)研究表明:在沒有變禿的前提下质涛,我們依然有機(jī)會變強(qiáng)稠歉。
其實(shí)C/C++ 有個(gè)神奇的玩意叫指針,這種顯示一把梭就行
BYTE dPiByte[8] = {0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40 };
double dPi = *(double*)dPiByte;
好了汇陆,后面的幾步運(yùn)算就是乘乘加加了怒炸,同學(xué)你自己n幾下就ok了。
(此處應(yīng)有掌聲)
三毡代、總結(jié)
為什么要去調(diào)試阅羹,直接F5大法不香嗎?
現(xiàn)在Ollvm肆虐教寂,掌握一些手撕匯編的良方可保你無憂捏鱼。
為什么要用Unidbg去調(diào)試,IDA不香嗎酪耕?
悟空穷躁,等你遇上內(nèi)存防修改,無法下軟件斷點(diǎn)和一些BT的反調(diào)試的時(shí)候因妇,你自會回來和為師唱這首歌的: Only You ......
預(yù)告一下问潭,下一篇是開啟Arm32下的浮點(diǎn)數(shù)寄存器顯示和TraceCode
知道為何自古紅顏多薄命嗎?因?yàn)闆]人在意丑的人活多久婚被。