查看: 137|回复: 0

[Reverse] 详解变形金刚

[复制链接]

22

主题

23

帖子

0

精华

VIP

Rank: 16

学币
10
荣耀
0
rank
0
违规
0

VIP

    发表于 2020-6-15 01:03:58 | 显示全部楼层 |阅读模式

    前言  Android逆向,最近一次写android逆向相关的文章已经是一年前了。。
      难受
      本题虽然简单,但是也值得深挖一下。在做题的时候我较为详细的记录了,在此我也会比较详细的讲讲,主要面向像我这样的小白,同大家分享。
    工具  需要使用到的工具:
    IDA7.0
    JEB.android.decompiler.3.0.0.201808031948Pro
    jni.h
    BDOpener.apk 或者 mprop
      调试环境:
    已经Root的Google Nexues 6p (android 8.0)
    安装Xposed框架
    我的环境是在mac下
      当然有些工具不是必须的。可以使用类似的工具替换。搞android的话,手机最好是买个原生的吧。
    步骤  大体介绍一下做此题的流程。
    • 找到主要判断逻辑
    • 找到关键的eq函数
    • 分析、识别算法
    • 写出解密脚本
      步骤并不难,每一步都有许多方法可以达到目的,同时需要处理一些细节的地方。
    寻找判断逻辑方法一  Android逆向较为常用的工具,应该是jeb了,我这用的最近泄漏的版本。
      疑难解决:
      用jeb时非常容易运行不了,主要是因为JDK的版本问题,这里使用JEB.android.decompiler.3.0.0.201808031948Pro需要JDK11+
      通过结果可以比较清楚的看到程序的逻辑,看起来似乎只要将用户名逆序即可!?

    详解变形金刚

    详解变形金刚

    详解变形金刚

    详解变形金刚
      这部分代码比较简单。但是当我尝试使用34567890931进行输入,提示error ??答案明显不对,而且从逻辑上来看没有看到提示error的代码。因此这里肯定是有猫腻。
      因此猜想程序执行的应该不是此Activity。
    如果做过android开发,并且眼神比较好,也比较细心的话,肯定能看出问题。
      此 MainActivity 继承自 AppCompiatActivity,而Activity的基类应该是AppCompatActivity,在jeb中直接双击AppCompiatActivity便可查看该类的定义,发现原来这里才是程序开始执行的位置。

    详解变形金刚

    详解变形金刚
      此时再来看一下代码逻辑。

    详解变形金刚

    详解变形金刚

    详解变形金刚

    详解变形金刚
      获取用户输入,调用native函数eq进行判断,然后判断长度是否24位,不足则补齐,并且对输入进行AES解密,最后打印结果。
      整个的关键便在于native层的eq函数。
    方法二  主要思路就是根据报错信息进行字符串搜索。当然最后也需要用到jeb。
      我用apktool,当然使用jeb直接搜索来的更方便一些,只是我在做的时候尝试了,也就记录了。
      命令apktool d Transformers.apk,之后在本地生成该apk的文件夹,在vscode下全局搜索字符串error

    详解变形金刚

    详解变形金刚
      之后便可以在jeb中进行定位了。
      jeb下直接搜索时,使用ctrl + f,更加方便快捷。

    详解变形金刚

    详解变形金刚
    2. 找到关键的eq函数  通过jeb脱出so文件,IDA打开,发现没有找到想要的eq函数

    详解变形金刚

    详解变形金刚
    Native函数注册  参考文章
      在Android中通过jni调用本地方法(c/c++),通常使用javah生成规范的头文件,定义一个函数实现本地方法,函数名必须使用本地方法的全类名的规范来写。
      Java_ + 包名 + 类名+ 接口名
      示例如下:
    JNIEXPORT jstring Java_com_example_test_MainActivity_helloworld(JNIEnv *, jclass );
      jni还提供RegisterNatives方法进行注册Native函数。
    jclass clazz;
    clazz = env->FindClass("com/example/test/MainActivity");
    if (clazz == NULL) {
    return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) {
    return JNI_FALSE;
    }
    
    return JNI_TRUE;
      RegisterNatives中第二个参数gMethods是一个二维数组,代表着这个class里的每一个native方法所对应的实现的方法。写法如下示例:
    static JNINativeMethod gMethods[] = {
    {"helloworld", "()Ljava/lang/String;", (void*)Jni_helloworld}};
      第三个参数代表要指定的native的数量。此时将前面在jni中声明可以改为
    jstring helloworld(JNIEnv *, jclass);
    寻找eq函数  了解完JNI注册Native函数的几种方法后回到此题,由于在导出表中没有找到eq函数,因此可以知道此时是通过手动注册的。
      因此我们可以定位到JNI_OnLoad函数

    详解变形金刚

    详解变形金刚
      像这里v3 + 860其实是jni的方法,为了使IDA能够识别,需要手动导入jni.h文件头,有关的操作可以参考这
      然后将int a1修改为JNIEnv * a1,其他有关变量也是类似。

    详解变形金刚

    详解变形金刚
      经过修改之后可以清晰的看到Native函数的注册过程。其中关键的就是off_CC0E5014
      有以上的铺垫,我们知道这个位置存放的就是JNINativeMethod结构

    详解变形金刚

    详解变形金刚
      并且其中第三个变量sub_CC0E1784指向的就是要注册的函数地址。

    详解变形金刚

    详解变形金刚
      当然通过一些经验我们也可以快速的找到eq函数,比如就此题来说通过字符串{9*8ga*[email protected]#fj'j$\\g;;可以猜测sub_CC0E1784函数可能是目标函数或者是目标函数的一部分。

    详解变形金刚

    详解变形金刚
      也可以挨个查看一下,根据经验去寻找,猜测的方法太多不列举了。
    分析、识别算法静态分析  就像其他很多师傅说的那样,主要就是修改的RC4以及修改的base64加密。
      我在假期写过关于RC4算法识别,可以参考下
      RC4是因为我看到了密钥,初始化S-Box以及乱序的操作。Base64是因为看到了base64的Table。
      这里简要的说一下加密过程。
    0. 生成密钥。

    详解变形金刚

    详解变形金刚
      大致过程是先去除-,然后倒序。
    这里生成的密钥是固定的,所以没有必要深入的分析,我是通过动态调试识别出这部分功能。
    1. 初始化S—Box

    详解变形金刚

    详解变形金刚
      这里是用dword_CC0E33E8中的数据作为初始数据。
    2. 根据密钥生成临时数据K3. 依据K将S-Box乱序4. 将S-Box经过伪随机子密码生成算法得到子密钥序列  对于RC4算法来说最后一步是将子密钥序列同明文进行异或,就此题来说以上过程都是固定的,因此我们无需关注此算法经过何种修改,只要将最后的子密钥序列dump下来即可。

    详解变形金刚

    详解变形金刚
      在进行异或之前他还对子密钥序列做了一次交换,不过这个过程是可逆的。
      而后将异或的结果进行了base64加密。

    详解变形金刚

    详解变形金刚
      这个判断起来不难,通过byte_CC0E5050数据就可以猜到
      最后将结果同byte_CC0E34E8逐字节进行比较,分析到此算是结束了。
      现在理一下解题思路,先还原base64,然后同RC4的子密钥序列进行异或。
      那么最后我们还需要dump出子密钥序列。这就需要动态调试一番。
      题外话,其实不需要调试也完全可以解决,只要照着加密过程,自己将RC4改改,同样能得到子密钥序列,我感觉应该只有初始化S-Box不同其他应该没有变动。我做的时候是动调的。
    动态调试  参考WIKI
      调试是逆向中不可少的。不过有时总会因为各种环境问题导致无法调试。我觉得如果打算做android逆向的话,最好还是准备只Google手机,能少遇点坑。
      调试手机apk有几种方法,我大致总结了下。
    1. 动态调试Java层APK  这里通常使用jeb进行动调,动调java主要是调试smail,当然这里也可以用AS进行动调,不过太麻烦了。JEB比较简单。
    将需要调试的APK拖入jeb打开,在smail下断点,确保手机打开了开发者模式。
    这里我用的手机是Google Nexues 6P,不知道为啥我用小米8,JEB没显示。
    为了进行调试,需要对应的APK设置debuggable=true
      这里可以使用mprop工具,如果手机上安装Xposed框架,那么可以直接使用BDOpener.apk模块
      具体怎么使用找教程。
    2. 动态调试android原生程序  手机ROOT,处于开发者模式,打开USB调试
    上传android_server 并运行
    同时进行端口转发
    adb push android_server /data/local/tmp
    chmod 775 /data/local/tmp/android_server
    adb shell
    su
    ./data/local/tmp/android_server
    adb forward tcp:23946 tcp:23946
      这是为了将手机的23946端口转发到本地的23946端口上,以便IDA进行通信。
    将需要调试的原生程序上传至手机,并赋予可执行权限
    adb push debugnativeapp /data/local/tmp/
    adb shell chmod 755 /data/local/tmp/debugnativeapp
      IDA选择Debugger-Run-RemoteArmLinux/Android debugger
    然后在IDA中设置程序,路径,配置HostName以及Port
    同时设置Deubg Option,使IDA能在 entry ,load, start  处断下。
      容易遇到的问题。
    如果遇到 error: only position independent executables (PIE) are supported. 一般是因为 android 手机的版本大于 5,可以尝试
    使用 android_server 的 PIE 版本
    利用 010Editor 将可执行 ELF 文件的 header 中的 elf header 字段中的 e_type 改为 ET_DYN(3)。
    3.原生SO动态调试(直接在so处下断)  这是比较简单的方式,这其实和SO运行和加载的时机有关,如果需要在加载SO之前也就是.init
      因为so是依附于apk运行的,所以相对来说会比较复杂。
      运行android_server 并且进行端口转发
    在手机上运行apk,然后在IDA中attach程序,此时IDA便会在libc中断下。这时便可以调试native层的函数。
      由于此方法需要apk在运行的时候附加调试,因此如果程序有加固或者在.init_array有解密,则无法进行调试。
      使用这种方法时同样也能在libc和linker处断下,但是这个断点没有什么意义,因为程序本身已经加载完毕了。
    4.原生SO动态调试(.init_array 以及 JNI_OnLoad)  为了理解,我们需要对so文件的加载过程有比较清晰的了解。
      参考文章
      能自己阅读下linker的源码那是最好的了。
      android最基本的so是libc.so,通过libc加载linker,然后通过linker的call_function加载lib库。
      就像文中总结的一样,当Java层调用static。。。时,系统加载so,首先执行.init和.init_array段的代码,之后如果存在JNI_OnLoad就调用该函数。后面就需要具体问题具体分析了。
      同三类似,运行android_server,进行端口转发。
    su
    ./data/local/tmp/android_server
    adb forward tcp:23946 tcp:23946
      不同的是需要以调试模式启动程序。我通过aapt可以快速的获取目标apk的一些信息。(习惯命令行)
    /Users/jeb/Library/Android/sdk/build-tools/27.0.3/aapt dump badging Transformers.apk

    详解变形金刚

    详解变形金刚
    adb shell am start -D -n com.zhuotong.crackme/.MainActivity

    详解变形金刚

    详解变形金刚
      此时可以打开IDA进行附加了。
      此时会断在libc,然后根据需要设置Debugger Option
      我是辣么设置的。

    详解变形金刚

    详解变形金刚
      此时在IDA中F9运行,是不会有反应的。因为此时还需要恢复app的运行。
      Wiki上说打开ddms,估计那个SDK的版本比较老,我是SDK-27,ddms改为monitor
      在如下路径:/Users/jeb/Library/Android/sdk/tools/monitor

    详解变形金刚

    详解变形金刚
      此时我们需要选中目标进程,这样就相当于是将app转发到电脑的jdb的调试端口默认是8700,而后使用jdb附加。
    jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700

    详解变形金刚

    详解变形金刚
      此时在输出窗口可以看到加载了liboo000oo.so,然后他会在Jni_OnLoad处的断点停下。

    详解变形金刚

    详解变形金刚
      这里需要注意一个顺序,先IDA附加,然后jdb附加使app恢复运行。
      此时可以看到RegisterNatives函数的四个参数,其实是三个参数,第一个代表this,其中第三个参数可以很清楚的看到函数名,函数类型,以及地址。

    详解变形金刚

    详解变形金刚

    详解变形金刚

    详解变形金刚
      我们找到eq函数,然后下断。根据前面的静态分析,我们在sub_CC0D6784函数下断,然后随便输入24个字符,最后将子密钥序列dump下来即可。

    详解变形金刚

    详解变形金刚
      最后整理一下即可写出解密脚本。
      这里其实还有一个点不知道各位有木有发现。
      在静态分析时RegisterNatives函数的第二个参数。

    详解变形金刚

    详解变形金刚
      其中byte_CC0DA0CA和byte_CC0DA0D0指向的都是乱码数据,只有最后一个地址是正确的,但当我们动态调试的时候,这两个指针却指向了eq和(Ljava/lang/String;)Z这是为什么呢?
      还记得so的加载流程吗?
      如果不记得请在此阅读一遍这篇参考文章
      那就是.init_array段。

    详解变形金刚

    详解变形金刚
      此处对应的就是datadiv_decode5009363700628197108函数

    详解变形金刚

    详解变形金刚
      大概功能就是对so的部分数据进行解密。
      我们可以直接在此函数下断,对解密部分代码进行调试,有时候反调试就会在这里设置。

    详解变形金刚

    详解变形金刚
      如果有必要其实也可以在linker的call_function处下断,我们可以将/system/bin/linker pull 到本地进行分析。

    详解变形金刚

    详解变形金刚
      有兴趣的可以试试。总之在何处下断,需要对so的加载流程非常的熟悉,以及合适需要IDA附加,程序运行到哪一步都需要自己把握清楚。
    写出解密脚本  有了前面的分析,解密脚本也就非常的好写。
    贴一下我的代码:
    table="!:#$%&()+-*/`~_[]{}?<>,[email protected]^abcdefghijklmnopqrstuvwxyz0123456789\\'"
    r="\x20{9*8ga*[email protected]#fj'j$\\g;;"
    s = ""
    for i in range(6):
    s += chr(ord(r[i*4])^7)
    s += chr(ord(r[i*4+1]))
    s += chr(ord(r[i*4+2])^0xf)
    s += chr(ord(r[i*4+3]))
    
    def mydecodeb64(enc,padding):
    enc=enc.replace(padding,"")
    x="".join(map(lambda x:bin(table.index(x))[2:].zfill(6),enc))
    for ap in range(8-(len(x)%8)):
    x+='0'
    plain=[]
    for i in range((len(x))/8):
    plain.append(chr(eval('0b'+x[i*8:(i+1)*8])))
    return "".join(plain).replace("\x00","")
    s_box = [0xF0,0x37,0xE1,0x9B,0x2A,0x15,0x17,0x9F,0xD7,0x58,0x4D,0x6E,0x33,0xA0,0x39,0xAE,0x04,0xD0,0xBE,0xED,0xF8,0x66,0x5E,0x00,0xD6,0x91,0x2F,0xC3,0x10,0x4C,0xF7,0xA6,0xC1,0xEC,0x6D,0x0B,0x50,0x65,0xBB,0x34,0xFA,0xA4,0x2D,0x3B,0x23,0xA1,0x96,0xD5,0x1D,0x38,0x56,0x0A,0x5D,0x4F,0xE4,0xCC,0x24,0x0D,0x12,0x87,0x35,0x85,0x8E,0x6F,0xC6,0x13,0x9A,0xD3,0xFC,0xE7,0x08,0xAC,0xB7,0xE9,0xB0,0xE8,0x41,0xAA,0x55,0x53,0xC2,0x42,0xBC,0xE6,0x0F,0x8A,0x86,0xA8,0xCF,0x84,0xC5,0x48,0x74,0x36,0x07,0xEB,0x88,0x51,0xF6,0x7F,0x57,0x05,0x63,0x3E,0xFE,0xB8,0xC9,0xF5,0xAF,0xDF,0xEA,0x82,0x44,0xF9,0xCD,0x06,0xBA,0x30,0x47,0x40,0xDE,0xFD,0x1C,0x7C,0x11,0x5C,0x02,0x31,0x2C,0x9C,0x5F,0x46,0x27,0xC4,0x83,0x73,0x16,0x90,0x20,0x76,0x7B,0xF2,0xE3,0xF3,0x77,0x52,0x80,0x25,0x09,0x26,0x3F,0xC7,0x18,0x1B,0xA3,0xFF,0xFB,0xCB,0xA9,0x8C,0x54,0x7A,0x68,0xB4,0x70,0x4B,0xE2,0x49,0x22,0x7E,0xA5,0xB6,0x81,0x9D,0x4E,0x67,0xF1,0xA7,0x3C,0xD9,0x94,0xEF,0x32,0x6B,0x1F,0xB1,0x60,0xB9,0x64,0x59,0x01,0xB3,0x7D,0xE0,0x6C,0xAD,0x97,0x19,0xB5,0x3A,0xF4,0xD8,0x8D,0x98,0x03,0x93,0x1A,0xDC,0x1E,0x4A,0xC0,0x5A,0xE5,0xD1,0x3D,0x14,0xC8,0x79,0xBD,0x43,0xDB,0x69,0xD2,0x61,0x95,0x9E,0x21,0x45,0x89,0x2B,0xAB,0x29,0xA2,0x8B,0x2E,0xD4,0x0E,0x62,0xCA,0x28,218, 91, 114, 143, 153, 117, 238, 120, 12, 113, 191, 221, 206, 146, 106, 178]
    dec_one =  mydecodeb64(s,padding = ";")
    print len(s_box)
    v30 = 0
    v28 = 0
    flag = ""
    for i in range(16):
    v28 = (v28+1)%256
    v35 = s_box[v28]
    v30 = (v30+v35)%256
    s_box[v28] = s_box[v30]
    s_box[v30] = v35
    v17 = s_box[v28]
    index = (v35+v17)%256
    flag+=chr(s_box[index]^ord(dec_one[i]))
    print flag
    总结  抽空做了几题,自己也回顾一下。不过还是太菜了。
      pizza tql



    温馨提示:
    1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
    2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
    3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
    学逆向论坛-免费的逆向学习论坛
    微信公众号
    快速回复 返回顶部 返回列表