Android逆向之旅—动态方式破解apk进阶篇(IDA调试so源码)

文章内容大纲 (右击展开→→)

  • 本文篇幅可能有点长,所以小编建议,关注公众号之后,可以点击收藏,慢慢看哦~~
  • 一、前言
  • 二、知识准备
  • 三、构造so案例
  • 四、开始破解so文件
  • 五、使用IDA来解决反调试问题
  • 六、技术总结
  • 七、总结
  • 内容预览:

  • Infocenter.arm.com的详细信息BL    执行函数调用...~
  • 但是我们发现,这里没有RX权限的so文件,说明so文件没有加载到内存中,...~
  • IDA可以获取被调试的进程的内存数据,一般是在 /proc/maps 文件中,所以...~
  • 始发于微信公众号: 编码美丽

     

    点击顶部蓝字"Android技术分享"关注公众号


     

     

    本文篇幅可能有点长,所以小编建议,关注公众号之后,可以点击收藏,慢慢看哦~~

     

    一、前言

    今天我们继续来看破解apk的相关知识,在前一篇: 我们今天主要来看如何使用IDA来调试中的native源码,因为现在一些app,为了安全或者效率问题,会把一些重要的功能放到native层,那么这样一来,我们前篇说到的Eclipse调试smali源码就显得很无力了,因为核心的都在native层,Android中一般native层使用的是so库文件,所以我们这篇就来介绍如何调试so文件的内容,从而让我们破解成功率达到更高的一层。

     

    二、知识准备

    我们在介绍如何调试so文件的时候,先来看一下准备知识:

    第一、IDA工具的使用

    早在之前的一篇文章: 中使用IDA工具静态分析so文件,通过分析arm指令,来获取破解信息,比如打印的log信息,来破解apk的,在那时候我们就已经介绍了如何使用IDA工具:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里有多个窗口,也有多个视图,用到最多的就是:

    1、Function Window对应的so函数区域:这里我们可以使用ctrl+f进行函数的搜索

    2、IDA View对应的so中代码指令视图:这里我们可以查看具体函数对应的arm指令代码

    3、Hex View对应的so的十六进制数据视图:我们可以查看arm指令对应的数据等

     

    当然在IDA中我们还需要知道一些常用的快捷键:

    1、强大的F5快捷键可以将arm指令转化成可读的C语言,帮助分析

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    首先选中需要翻译成C语言的函数,然后按下F5:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    看到了,立马感觉清爽多了,这些代码看起来应该会好点了。

     

    下面我们还需要做一步,就是还原JNI函数方法名
    一般JNI函数方法名首先是一个指针加上一个数字,比如v3+676。然后将这个地址作为一个方法指针进行方法调用,并且第一个参数就是指针自己,比如(v3+676)(v3…)。这实际上就是我们在JNI里经常用到的JNIEnv方法。因为Ida并不会自动的对这些方法进行识别,所以当我们对so文件进行调试的时候经常会见到却搞不清楚这个函数究竟在干什么,因为这个函数实在是太抽象了。解决方法非常简单,只需要对JNIEnv指针做一个类型转换即可。比如说上面提到a1和v4指针:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    我们可以选中a1变量,然后按一下y键:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    然后将类型声明为:JNIEnv*。

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    确定之后再来看:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    修改之后,是不是瞬间清晰了很多?另外有人( 貌似是看雪论坛上的)还总结了所有JNIEnv方法对应的数字,地址以及方法声明:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    2、Shirt+F12快捷键,速度打开so中所有的字符串内容窗口

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    有时候,字符串是一个非常重要的信息,特别是对于破解的时候,可能就是密码,或者是密码库信息。

     

    3、Ctrl+S快捷键,有两个用途,在正常打开so文件的IDA View视图的时候,可以查看so对应的Segement信息

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    可以快速得到,一个段的开始位置和结束位置,不过这个位置是相对位置,不是so映射到内存之后的位置,关于so中的段信息,不了解的同学可以参看这篇文章: 这篇文章介绍的很很清楚了,这里就不在作介绍了。

     

    当在调试页面的时候,ctrl+s可以快速定位到我们想要调试的so文件映射到内存的地址:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    因为一般一个程序,肯定会包含多个so文件的,比如系统的so就有好多的,一般都是在/system/lib下面,当然也有我们自己的so,这里我们看到这里的开始位置和结束位置就是这个so文件映射到内存中:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里我们可以使用cat命令查看一个进程的内存映射信息:cat /proc/[pid]/maps

    我们看到映射信息中有多so文件,其实这个不是多个so文件,而是so文件中对应的不同Segement信息被映射到内存中的,一般是代码段,数据段等,因为我们需要调试代码,所以我们只关心代码段,代码段有一个特点就是具有执行权限x,所以我们只需要找到权限中有x的那段数据即可。

     

    4、G快捷键:在IDA调试页面的时候,我们可以使用S键快速跳转到指定的内存位置

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里的跳转地址,是可以算出来的,比如我现在想跳转到A函数,然后下断点,那么我们可以使用上面说到的ctrl+s查找到so文件的内存开始的基地址,然后再用IDA View中查看A函数对应的相对地址,相加就是绝对地址,然后跳转到即可,比如这里的:

    Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals 函数的IDA View中的相对地址(也就是so文件的地址):E9C

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    上面看到so文件映射到内存的基地址:74FE4000

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    那么跳转地址就是:74FE4000+E9C=74FE4E9C

     

    注意:

    一般这里的基地址只要程序没有退出,在运行中,那么他的值就不会变,因为程序的数据已经加载到内存中了,基地址不会变的,除非程序退出,又重新运行把数据加载内存中了,同时相对地址是永远不会变的,只有在修改so文件的时候,文件的大小改变了,可能相对地址会改变,其他情况下不会改变,相对地址就是数据在整个so文件中的位置。

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里我们可以看到函数映射到内存中的绝对地址了。

     

    注意:

    有时候我们发现跳转到指定位置之后,看到的全是DCB数据,这时候我们选择函数地址,点击P键就可以看到arm指令源码了:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    5、调试快捷键:F8单步调试,F7单步进入调试

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    上面找到函数地址之后,我们可以下断点了,下断点很简单,点击签名的绿色圈点,变成红色条目即可,然后我们可以点击F9快捷键,或者是点击运行按钮,即可运行程序:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    其中还有暂停和结束按钮。我们运行之后,然后在点击so的native函数,触发断点逻辑:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这时候,我们看到进入调试界面,点击F8可以单步调试,看到有一个PC指示器,其实在arm中PC是一个特殊的寄存器,用来存储当前指令的地址,这个下面会介绍到。

     

    好了到这里,我们就大致说了一下关于IDA在调试so文件的时候,需要用到的快捷键:

    1>、Shift+F12快速查看so文件中包含的字符串信息

    2>、F5快捷键可以将arm指令转化成可读的C代码,这里同时可以使用Y键,修改JNIEnv的函数方法名

    3>、Ctrl+S有两个用途,在IDA View页面中可以查看so文件的所有段信息,在调试页面可以查看程序所有so文件映射到内存的基地址

    4>、G键可以在调试界面,快速跳转到指定的绝对地址,进行下断点调试,这里如果跳转到目的地址之后,发现是DCB数据的话,可以在使用P键,进行转化即可,关于DCB数据,下面会介绍的。

    5>、F7键可以单步进入调试,F8键可以单步调试


    第二、常用的ARM指令集知识

    我们在上面看到IDA打开so之后,看到的是纯种的汇编指令代码,所以这就要求我们必须会看懂汇编代码,就类似于我们在调试层代码的时候一样,必须会smali语法,庆幸的是,这两种语法都不是很复杂,所以我们知道一些大体的语法和指令就可以了,下面我们来看看arm指令中的寻址方式,寄存器,常用指令,看完这三个知识点,我们就会对arm指令有一个大体的了解,对于看arm指令代码也是有一个大体的认知了。

    1、arm指令中的寻址方式

    1>. 立即数寻址
    也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。例如:
    MOV R0,#64   ;R0  ← 64
    2>. 寄存器寻址
    寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址。例如:

    ADD R0,R1, R2   ;R0  ← R1 + R2
    3>. 寄存器间接寻址
    寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中。例如:
    LDR R0,[R1] ;R0 ←[R1]
    4>. 寄存器偏移寻址
    这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。例如:
    MOV R0,R2,LSL  #3   ;R0 ← R2 * 8 ,R2的值左移3位,结果赋给R0。
    5>. 寄存器基址变址寻址
    寄存器基址变址寻址又称为基址变址寻址,它是在寄存器间接寻址的基础上扩展来的。它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。例如:
    LDR R0,[R1,#4] ;R0 ←[R1 + 4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。
    6>. 多寄存器寻址
    这种寻址方式可以一次完成多个寄存器值的传送。例如:
    LDMIA  R0,{R1,R2,R3,R4} ;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]
    7>. 堆栈寻址
    堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用堆栈指针(Stack Pointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。
    堆栈寻址举例如下:
    STMFD  SP!,{R1-R7, LR} ;将R1-R7, LR压入堆栈。满递减堆栈。
    LDMED  SP!,{R1-R7, LR} ;将堆栈中的数据取回到R1-R7, LR寄存器。空递减堆栈。

     

    2、ARM中的寄存器

    R0-R3:用于函数参数及返回值的传递
    R4-R6, R8, R10-R11:没有特殊规定,就是普通的通用寄存器
    R7:栈帧指针(Frame Pointer).指向前一个保存的栈帧(stack frame)和链接寄存器(link register, lr)在栈上的地址。
    R9:操作系统保留
    R12:又叫IP(intra-procedure scratch )
    R13:又叫SP(stack pointer),是栈顶指针
    R14:又叫LR(link register),存放函数的返回地址。
    R15:又叫PC(program counter),指向当前指令地址。

     

    3、ARM中的常用指令含义

    ADD 加指令
    SUB 减指令
    STR    把寄存器内容存到栈上去
    LDR    把栈上内容载入一寄存器中
    .W    是一个可选的指令宽度说明符。它不会影响为此指令的行为,它只是确保生成 32 位指令。Infocenter.arm.com的详细信息
    BL    执行函数调用,并把使lr指向调用者(caller)的下一条指令,即函数的返回地址
    BLX    同上,但是在ARM和thumb指令集间切换。
    CMP    指令进行比较两个操作数的大小

     

    4、ARM指令简单代码段分析

    C代码:

    #include <stdio.h>
    int func(int a, int b, int c, int d, int e, int f)
    {
        int g = a + b + c + d + e + f;
        return g;
    }

    对应的ARM指令:

    add r0, r1  将参数a和参数b相加再把结果赋值给r0
    ldr.w r12, [sp]  把最的一个参数f从栈上装载到r12寄存器
    add r0, r2  把参数c累加到r0上
    ldr.w r9, [sp, #4]  把参数e从栈上装载到r9寄存器
    add r0, r3  累加d累加到r0
    add r0, r12  累加参数f到r0
    add r0, r9  累加参数e到r0

     

    三、构造so案例

    好了,关于ARM指令的相关知识,就介绍这么多了,不过我们在调试分析的时候,肯定不能做到全部的了解,因为本身ARM指令语法就比较复杂,不过幸好大学学习了汇编语言,所以稍微能看懂点,如果不懂汇编的同学那就可能需要补习一下了,因为我们在使用IDA分析so文件的时候,不会汇编的话,那是肯定行不通的,所以我们必须要看懂汇编代码的,如果遇到特殊指令不了解的同学,可以网上搜一下即可。

     

    上面我们的准备知识做完了,一个是IDA工具的时候,一个是ARM指令的了解,下面我们就来开始操刀了,为了方便开始,我们先自己写一个简单的Android native层代码,然后进行IDA进行分析即可。

     

    这里可以使用AndroidStudio中进行新建一个简单工程,然后创建JNI即可:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    这里顺便简单说一下AndroidStudio中如何进行NDK的开发吧:

    第一步:在工程中新建jni目录

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    第二步:使用javah生成native的头文件

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    注意:

    javah执行的目录,必须是类包名路径的最上层,然后执行:

    javah 类全名

    注意没有后缀名java哦

     

    第三步:配置项目的NDK目录

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    选择模块的设置选线:Open Module Settings

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    设置NDK目录即可

     

    第四步:copy头文件到jni目录下,然后配置gradle中的ndk选项

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里只需要设置编译之后的模块名,就是so文件的名称,需要产生那几个平台下的so文件,还有就是需要用到的lib库,这里我们看到我们用到了Android中打印log的库文件。

     

    第五步:编译运行,在build目录下生成指定的so文件,copy到工程的libs目录下即可

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    好了,到这里我们就快速的在AndroidStudio中新建了一个Native项目,这里关于native项目的代码不想解释太多,就是Java层

    传递了用户输入的密码,然后native做了校验过程,把校验结果返回到Java层即可:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)  Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    具体的校验过程这里不再解释了。我们运行项目之后,得到apk文件,那么下面我们就开始我们的破解旅程了

     

    四、开始破解so文件

    开始破解我们编译之后的apk文件

    第一、首先我们可以使用最简单的压缩软件,打开apk文件,然后解压出他的so文件

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    我们得到libencrypt.so文件之后,使用IDA打开它:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    我们知道一般so中的函数方法名都是:Java_类名_方法名

    那么这里我们直接搜:Java关键字即可,或者使用jd-gui工具找到指定的native方法

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    双击,即可在右边的IDA View页面中看到Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals 函数的指令代码:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    我们可以简单的分析一下这段指令代码:

    1>、PUSH {r3-r7,lr} 是保存r3,r4,r5,r6,r7,lr 的值到内存的栈中,那么最后当执行完某操作后,你想返回到lr指向的地方执行,当然要给pc了,因为pc保留下一条CPU即将执行的指令,只有给了pc,下一条指令才会执行到lr指向的地方

    pc:程序寄存器,保留下一条CPU即将执行的指令
    lr: 连接返回寄存器,保留函数返回后,下一条应执行的指令 

    这个和函数最后面的POP {r3-r7,pc}是相对应的。

     

    2>、然后是调用了strlen,malloc,strcpy等系统函数,在每次使用BLX和BL指令调用这些函数的时候,我们都发现了一个规律:就是在调用他们之前一般都是由MOV指令,用来传递参数值的,比如这里的R5里面存储的就是strlen函数的参数,R0就是is_number函数的参数,所以我们这样分析之后,在后面的动态调试的过程中可以得到函数的入口参数值,这样就能得到一些重要信息

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    3>、在每次调用有返回值的函数之后的命令,一般都是比较指令,比如CMP,CBZ,或者是strcmp等,这里是我们破解的突破点,因为一般加密再怎么牛逼,最后比较的参数肯定是正确的密码(或者是正确的加密之后的密码)和我们输入的密码(或者是加密之后的输入密码),我们在这里就可以得到正确密码,或者是加密之后的密码:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    到这里,我们就分析完了native层的密码比较函数:Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals

    如果觉得上面的ARM指令看的吃力,可以使用F5键,查看他的C语言代码:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

     

    我们这里看到其实有两个函数是核心点:

    1>is_number函数,这个函数我们看名字应该猜到是判断是不是数字,我们可以使用F5键,查看他对应的C语言代码:

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    这里简单一看,主要是看return语句和if判断语句,看到这里有一个循环,然后获取_BYTE*这里地址的值,并且自增加一,然后存到v2中,如果v3为'

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值