Android CrackMe[0]:TEA加密算法的逆向

有空找些CrackMe来让脑子转一转还是挺有意思的。
这道题据说是一道Android逆向工程师的面试题。给的APK是一个服务端程序,要求写一个客户端来解密来自服务端的数据。

把APK拖到JEB,直接看MainActivity,如图:


Android CrackMe[0]:TEA加密算法的逆向_第1张图片
image.png

MainActivity启动后,就会创建一个子线程去做一些事,而且这里加载了一个动态库,估计线程做的事情是在Native层实现的。


Android CrackMe[0]:TEA加密算法的逆向_第2张图片
image.png

如上图,果然,子线程调用了MainActivity的Native函数init()。

用IDA打开so库,找到对应的jni函数Java_com_example_crack0_MainActivity_init() 的实现,如图:


Android CrackMe[0]:TEA加密算法的逆向_第3张图片
image.png

该函数主要做的事情如下:

  1. 创建socket,并绑定监听本地8080端口(代码中没有指定,默认是8080,可在adb shell
    中使用netstat -ano 命令进行确认);


    image.png
  2. 读取客户端发来的数据,将读取到的数据通过xxx()函数进行加密;
  3. 将加密后的数据发送给客户端。

这里要注意一个地方,就是上图中xxx()函数调用的第三个参数,传入的是v6的这个整型变量的地址。另外,可以看到,v6、v7、v8和v9都是很有规律的数,就是一串连续的16进制数,但看起来xxx()函数只用到了v6,这貌似不太对,我猜想这4个整型变量其实是连续的...于是我将代码视图跳转到ARM汇编视图,发现:


image.png

双击跳转到unk_2364地址处查看,如图:


Android CrackMe[0]:TEA加密算法的逆向_第4张图片
image.png

果然这串十六进制数是紧挨着的,也就是说,这16个字节的数都会参与到加密。我猜想这串数应该是用作加解密的密钥。

好,下面来分析xxx()函数的实现,如图:


Android CrackMe[0]:TEA加密算法的逆向_第5张图片
image.png

可以看到,xxx()函数是先对原始数据缓冲区srcData的每个字节进行取反,然后再对原始数据的字节长度进行各种算术运算,从而得到加密后数据的长度,以及后续do-while循环所需要的循环次数,同时还将一个地址值(srcDataLen的地址)放到缓冲区srcData中,。后面这个do-while循环就是再次对经过初步处理的原始数据进行加密,真正关键的加密实现在xxxx()函数中,如图:


Android CrackMe[0]:TEA加密算法的逆向_第6张图片
image.png

从xxxx()函数的实现中,可以发现TEA的显著特征,那就是0xC6EF3720,和 -0x61C88647(即0x9E3779B9)。搜索一下TEA的C语言实现,如下图所示【详见链接】:

Android CrackMe[0]:TEA加密算法的逆向_第7张图片
image.png

仔细对比一下该图的encrypt()和上面题目中的xxxx()函数,就能确定这个xxxx()函数就是对传入的数据进行了TEA加密。

识别出算法后,就可以通过对比TEA加密和解密实现的不同,来写出对应的解密函数xxxx_decrypt()了:

#define _DWORD unsigned int
#define HIDWORD(x) (*((_DWORD *)&(x) + 1))
#define LODWORD(x) (*((_DWORD *)&(x)))

//key="\x67\x45\x23\x01\xEF\xCD\xAB\x89\x98\xBA\xDC\xFE\x10\x32\x54\x76"
//TEA解密算法
long long xxxx_decrypt(int a1, int a2)
{
    int v2;
    int v3;
    int v4;
    unsigned int v5;
    unsigned int v6;
    int v7;
    long long v9;

    v2 = *(int *)(a2 + 8); //v2=0xFEDCBA98
    v3 = *(int *)a2;       //v3=0x1234567
    v4 = *(int *)(a2 + 4); //v4=0x89ABCDEF
    v5 = *(int *)a1;
    v6 = *(int *)(a1 + 4);
    HIDWORD(v9) = *(int *)(a2 + 12);
    v7 = 0xC6EF3720;
    LODWORD(v9) = v2; //v9=0x76543210FEDCBA98
    do
    {
        v6 -= ((v5 >> 5) + HIDWORD(v9)) ^ (16 * v5 + v2) ^ (v5 + v7);
        v5 -= ((v6 >> 5) + v4) ^ (16 * v6 + v3) ^ (v6 + v7);
        v7 += 0x61C88647;
    } while (v7 != 0);
    *(int *)a1 = v5;
    *(int *)(a1 + 4) = v6;
    return v9;
}

再回头看xxx()函数再到xxxx()函数的这个过程,首先xxxx函数的参数1是待加密数据的缓冲区首地址,参数2是key的首地址。且xxxx()函数每一次的调用,参数1所传入的缓冲区首地址,是原始缓冲区首地址+(i*8)个字节的偏移,这里的 i 表示循环计数。而循环次数 = 加密后数据的字节数 / 8,这个可以通过分析xxx()函数那几行代码得知,很容易的。

最后我写了一个客户端APK来解密服务端的数据,关键代码如下:

#define _DWORD unsigned int
#define HIDWORD(x) (*((_DWORD *)&(x) + 1))
#define LODWORD(x) (*((_DWORD *)&(x)))

//key="\x67\x45\x23\x01\xEF\xCD\xAB\x89\x98\xBA\xDC\xFE\x10\x32\x54\x76"
//TEA解密算法
long long xxxx_decrypt(int a1, int a2)
{
    int v2;
    int v3;
    int v4;
    unsigned int v5;
    unsigned int v6;
    int v7;
    long long v9;

    v2 = *(int *)(a2 + 8); //v2=0xFEDCBA98
    v3 = *(int *)a2;       //v3=0x1234567
    v4 = *(int *)(a2 + 4); //v4=0x89ABCDEF
    v5 = *(int *)a1;
    v6 = *(int *)(a1 + 4);
    HIDWORD(v9) = *(int *)(a2 + 12);
    v7 = 0xC6EF3720;
    LODWORD(v9) = v2; //v9=0x76543210FEDCBA98
    do
    {
        v6 -= ((v5 >> 5) + HIDWORD(v9)) ^ (16 * v5 + v2) ^ (v5 + v7);
        v5 -= ((v6 >> 5) + v4) ^ (16 * v6 + v3) ^ (v6 + v7);
        v7 += 0x61C88647;
    } while (v7 != 0);
    *(int *)a1 = v5;
    *(int *)(a1 + 4) = v6;
    return v9;
}

JNIEXPORT void Java_me_falcon_exam02_1crack_MainActivity_decryptData(JNIEnv *env, jobject obj, jbyteArray dataArr)
{
    int dataBytesLen = (*env)->GetArrayLength(env, dataArr);
    char key[] = "\x67\x45\x23\x01\xEF\xCD\xAB\x89\x98\xBA\xDC\xFE\x10\x32\x54\x76";
    jbyte *dataBytes = (*env)->GetByteArrayElements(env, dataArr, NULL);

    int nCnt = dataBytesLen / 8;
    for (int i = 0; i < nCnt; i++) {
        xxxx_decrypt(dataBytes + i * 8, key);
    }

    char srcDataBuf[1024] = {};
    for (int i = 0; dataBytes[i] != 0; i++) {
        srcDataBuf[i] = ~dataBytes[i];
    }

    LOGD("[From server] After decrypt, srcData=%s, srcDataLen=%d", srcDataBuf, strlen(srcDataBuf));
    (*env)->ReleaseByteArrayElements(env, dataArr, dataBytes, 0);
}

相关的代码、APK等有兴趣可通过下面的地址获取:
链接:https://pan.baidu.com/s/1pufbdPb6y1fnEm3W6lnXRg 密码:9jqs

你可能感兴趣的:(Android CrackMe[0]:TEA加密算法的逆向)