您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 企业文化 > Android逆向之旅
Android逆向之旅Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)2016-05-270个评论收藏我要投稿一、前言今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为了安全或者效率问题,会把一些重要的功能放到native层,那么这样一来,我们前篇说到的Eclipse调试smali源码就显得很无力了,因为核心的都在native层,Android中一般native层使用的是so库文件,所以我们这篇就来介绍如何调试so文件的内容,从而让我们破解成功率达到更高的一层。二、知识准备我们在介绍如何调试so文件的时候,先来看一下准备知识:第一、IDA工具的使用早在之前的一篇文章:Android中通过静态分析技术破解apk中使用IDA工具静态分析so文件,通过分析arm指令,来获取破解信息,比如打印的log信息,来破解apk的,在那时候我们就已经介绍了如何使用IDA工具:这里有多个窗口,也有多个视图,用到最多的就是:1、FunctionWindow对应的so函数区域:这里我们可以使用ctrl+f进行函数的搜索2、IDAView对应的so中代码指令视图:这里我们可以查看具体函数对应的arm指令代码3、HexView对应的so的十六进制数据视图:我们可以查看arm指令对应的数据等当然在IDA中我们还需要知道一些常用的快捷键:1、强大的F5快捷键可以将arm指令转化成可读的C语言,帮助分析首先选中需要翻译成C语言的函数,然后按下F5:看到了,立马感觉清爽多了,这些代码看起来应该会好点了。下面我们还需要做一步,就是还原JNI函数方法名一般JNI函数方法名首先是一个指针加上一个数字,比如v3+676。然后将这个地址作为一个方法指针进行方法调用,并且第一个参数就是指针自己,比如(v3+676)(v3…)。这实际上就是我们在JNI里经常用到的JNIEnv方法。因为Ida并不会自动的对这些方法进行识别,所以当我们对so文件进行调试的时候经常会见到却搞不清楚这个函数究竟在干什么,因为这个函数实在是太抽象了。解决方法非常简单,只需要对JNIEnv指针做一个类型转换即可。比如说上面提到a1和v4指针:我们可以选中a1变量,然后按一下y键:然后将类型声明为:JNIEnv*。确定之后再来看:修改之后,是不是瞬间清晰了很多?另外有人(貌似是看雪论坛上的)还总结了所有JNIEnv方法对应的数字,地址以及方法声明:2、Shirt+F12快捷键,速度打开so中所有的字符串内容窗口有时候,字符串是一个非常重要的信息,特别是对于破解的时候,可能就是密码,或者是密码库信息。3、Ctrl+S快捷键,有两个用途,在正常打开so文件的IDAView视图的时候,可以查看so对应的Segement信息可以快速得到,一个段的开始位置和结束位置,不过这个位置是相对位置,不是so映射到内存之后的位置,关于so中的段信息,不了解的同学可以参看这篇文章:Android中so文件格式详解这篇文章介绍的很很清楚了,这里就不在作介绍了。当在调试页面的时候,ctrl+s可以快速定位到我们想要调试的so文件映射到内存的地址:因为一般一个程序,肯定会包含多个so文件的,比如系统的so就有好多的,一般都是在/system/lib下面,当然也有我们自己的so,这里我们看到这里的开始位置和结束位置就是这个so文件映射到内存中:这里我们可以使用cat命令查看一个进程的内存映射信息:cat/proc/[pid]/maps我们看到映射信息中有多so文件,其实这个不是多个so文件,而是so文件中对应的不同Segement信息被映射到内存中的,一般是代码段,数据段等,因为我们需要调试代码,所以我们只关心代码段,代码段有一个特点就是具有执行权限x,所以我们只需要找到权限中有x的那段数据即可。4、G快捷键:在IDA调试页面的时候,我们可以使用S键快速跳转到指定的内存位置这里的跳转地址,是可以算出来的,比如我现在想跳转到A函数,然后下断点,那么我们可以使用上面说到的ctrl+s查找到so文件的内存开始的基地址,然后再用IDAView中查看A函数对应的相对地址,相加就是绝对地址,然后跳转到即可,比如这里的:Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals函数的IDAView中的相对地址(也就是so文件的地址):E9C上面看到so文件映射到内存的基地址:74FE4000那么跳转地址就是:74FE4000+E9C=74FE4E9C注意:一般这里的基地址只要程序没有退出,在运行中,那么他的值就不会变,因为程序的数据已经加载到内存中了,基地址不会变的,除非程序退出,又重新运行把数据加载内存中了,同时相对地址是永远不会变的,只有在修改so文件的时候,文件的大小改变了,可能相对地址会改变,其他情况下不会改变,相对地址就是数据在整个so文件中的位置。这里我们可以看到函数映射到内存中的绝对地址了。注意:有时候我们发现跳转到指定位置之后,看到的全是DCB数据,这时候我们选择函数地址,点击P键就可以看到arm指令源码了:5、调试快捷键:F8单步调试,F7单步进入调试上面找到函数地址之后,我们可以下断点了,下断点很简单,点击签名的绿色圈点,变成红色条目即可,然后我们可以点击F9快捷键,或者是点击运行按钮,即可运行程序:其中还有暂停和结束按钮。我们运行之后,然后在点击so的native函数,触发断点逻辑:这时候,我们看到进入调试界面,点击F8可以单步调试,看到有一个PC指示器,其实在arm中PC是一个特殊的寄存器,用来存储当前指令的地址,这个下面会介绍到。好了到这里,我们就大致说了一下关于IDA在调试so文件的时候,需要用到的快捷键:1、Shift+F12快速查看so文件中包含的字符串信息2、F5快捷键可以将arm指令转化成可读的C代码,这里同时可以使用Y键,修改JNIEnv的函数方法名3、Ctrl+S有两个用途,在IDAView页面中可以查看so文件的所有段信息,在调试页面可以查看程序所有so文件映射到内存的基地址4、G键可以在调试界面,快速跳转到指定的绝对地址,进行下断点调试,这里如果跳转到目的地址之后,发现是DCB数据的话,可以在使用P键,进行转化即可,关于DCB数据,下面会介绍的。5、F7键可以单步进入调试,F8键可以单步调试第二、常用的ARM指令集知识我们在上面看到IDA打开so之后,看到的是纯种的汇编指令代码,所以这就要求我们必须会看懂汇编代码,就类似于我们在调试Java层代码的时候一样,必须会smali语法,庆幸的是,这两种语法都不是很复杂,所以我们知道一些大体的语法和指令就可以了,下面我们来看看arm指令中的寻址方式,寄存器,常用指令,看完这三个知识点,我们就会对arm指令有一个大体的了解,对于看arm指令代码也是有一个大体的认知了。1、arm指令中的寻址方式1>.立即数寻址也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。例如:MOVR0,#64;R0←642>.寄存器寻址寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址。例如:ADDR0,R1,R2;R0←R1+R23>.寄存器间接寻址寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中。例如:LDRR0,[R1];R0←[R1]4>.寄存器偏移寻址这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。例如:MOVR0,R2,LSL#3;R0←R2*8,R2的值左移3位,结果赋给R0。5>.寄存器基址变址寻址寄存器基址变址寻址又称为基址变址寻址,它是在寄存器间接寻址的基础上扩展来的。它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。例如:LDRR0,[R1,#4];R0←[R1+4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。6>.多寄存器寻址这种寻址方式可以一次完成多个寄存器值的传送。例如:LDMIAR0,{R1,R2,R3,R4};R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]7>.堆栈寻址堆栈是一种数据结构,按先进后出(FirstInLastOut,FILO)的方式工作,使用堆栈指针(StackPointer,SP)指示当前的操作位置,堆栈指针总是指向栈顶。堆栈寻址举例如下:STMFDSP!,{R1-R7,LR};将R1-R7,LR压入堆栈。满递减堆栈。LDMEDSP!,{R1-R7,LR};将堆栈中的数据取回到R1-R7,LR寄存器。空递减堆栈。2、ARM中的寄存器R0-R3:用于函数参数及返回值的传递R4-R6,R8,R10-R11:没有特殊规定,就是普通的通用寄存器R7:栈帧指针(FramePointer).指向前一个保存的栈帧(stackframe)和链接寄存器(linkregister,lr)在栈上的地址。R9:操作系统保留R12:又叫IP(intra-procedurescratch)R13:又叫SP(stackpointer),是栈顶指针R14:又叫LR(linkregister),存放函数的返回地址。R15:又叫PC(programcounter),指向当前指令地址。3、ARM中的常用指令含义ADD加指令SUB减指令STR把寄存器内容存到栈上去LDR把栈上内容载入一寄存器中.W是一个可选的指令宽度说明符。它不会影响为此指令的行为,它只是确保生成32位指令。Infocenter.arm.com的详细信息BL执行函数调用,并把使lr指向调用者(caller)的下一条指令,即函数的返回地址BLX同上,但是在ARM和thumb指令集间切换。CMP指令进行比较两个操作数的大小4、ARM指令简单代码段分析C代码:#includeintfunc(inta,intb,intc,intd,inte,intf){intg=a+b+c+d+e+f;returng;}对应的ARM指令:addr0,r1将参数a和参数b相加再把结果赋值给r0ldr.wr12,[sp]把最的一个参数f从栈上装载到r12寄存器addr0,r2把参数c累加到r0上ldr.wr9,[sp,#4]把参数e从栈上装载到r9寄存器addr0,r3累加d累加到r0addr0,r12累加参数f到r0addr0,r9累加参数e到r0三、构造so案例好了,关于ARM指令的相关知识,就介绍这么多了,不过我们在调试分析的时候,肯定不能做到全部的了解,因为本身ARM指令语法就比较复杂,不过幸好大学学习了汇编语言,所以稍微能看懂点,如果不懂汇编的同学那就可能需要补习一下了,因为我们在使用IDA分析so文件的时候,不会汇编的话,那是肯定行不通的,所以我们必须要看懂汇编代码的,如果遇到特殊指令不了解的同学,可以网上搜一下即可。上面我们的准备知识做完了,一个是IDA工具的时候,一个是ARM指令的了解,下面我们就来开始操刀了,为了方便开始,我们先自己写一个简单的Androidnative层代码,然后进行IDA进行分析即可。这里可以使用AndroidStudio中进行新建一个简单工程,然后创建JNI即可:这里顺便简单说一下AndroidStudio中如何进行NDK的开发吧:第一步:在工程中新建jni目录第二步:使用javah生成native的头文件注意:javah执行的目录,必须是类包名路径的最上层,然后执行:javah类全名注意没有后缀名java哦第三步:配置项目的NDK目录选择模块的设置选线:OpenModuleSettings:设置NDK目录即可第四步:copy头文
本文标题:Android逆向之旅
链接地址:https://www.777doc.com/doc-4233143 .html