首页
社区
课程
招聘
[原创]2015年AliCrackMe第二题的分析之人肉过反调试
发表于: 2020-4-2 23:04 7377

[原创]2015年AliCrackMe第二题的分析之人肉过反调试

2020-4-2 23:04
7377

0x00前言:

我是一个利用下班空余时间学习了一个月Android逆向的萌新,有多年的C/C++/Android开发经验,学习逆向的过程中,每当达成一个阶段的学习目标,都想找一个样本来试试手。这不,为了巩固一下前面学习的知识,找了很久找到这个样本,但是在百度上搜索的时候,我发现这个样本很出名,到处都有文章提到他的解法,我刻意避开了各位大佬的解法,下好样本,迅速关闭网页,然后自己动手开搞。
注:本帖中使用到的工具的基本使用方法我就不过多的讲述了,直接说思路,可能大佬只需要简单的一个调试或者Hook,马上就会知道答案,但是,原谅我,我是新人,我暂时只能用最基础的方法来解,没有抄袭,绝对原创,希望对新手起到启发作用,相互交流,有不正确的地方,大佬们还请斧正。

0x01流程:

先安装APP看看是个什么妖魔鬼怪,如下图:

很有意境,左下角有一个看雪的logo,阿里和看雪联合出品,我立马意识到,这件事并没有那么简单。
接下来先进行一波静态分析,直接把Apk拖入到jadx中,如下图:

代码没有混淆,很清晰的逻辑,点击“输入密码”按钮之后,直接调用native接口securityCheck,加载的是libcrackme.so这个库,看来密码校验是在native里面了,那就只有请出我们的神器IDA了。
先用Apktool反编译APK,用IDA打开APK反编译出来的目录下的lib/armeabi/libcrackme.so,发现native接口没有在JNI_OnLoad函数里面注册,直接用“Java_包名_function”的方式定义,这就是告诉你,点这个函数就行了,那就点开这个函数,如下图:

浏览了一遍汇编代码,发现大致能看懂,但是不直观,那就F5直接反汇编,看C语言伪代码,如下图:

参数a1的类型应该是JNIEnv*,它被赋值给了v3,(*(_DWORD)v3+676)又作为了一个函数指针被调用,那么应该很快想到JNIEnv这个结构体,里面存放的是它所在JavaVM所包含的所有函数指针;参数a3是jstring类型,被付给了v4,v4又是作为(*(_DWORD)v3+676)函数的第二个变量,大致隐约能感觉到这个函数是在转换字符串,我们选中a1和a3变量,点击“Y”,修改a1的类型为JNIEnv*,修改a3类型为jstring,修改之后的函数就一目了然了,如下图:
v5是a3变量通过JNIEnv.GetStringUTFChars这个函数转成一个C型的字符串,再和v6做比较,那么v6不出意外就是密码了,我们双击off_628C这个变量,如下图:

WWWhat? “wojiushidaan”? 这就是密码,这么简单,心里一阵狂喜,直接把“wojiushidaan”输入到输入框,点击“输入密码”,一个让人沮丧的Toast弹出了,“验证码校验失败”,看来,这玩意儿果真没有想象中那么简单,那就只有动态调试了,巴拉巴拉一阵IDA动态调试基础操作之后,每次Attach到进程之后,进程马上就闪退,可能有反调试,那就继续静态分析啊,看哪里在做反调试的操作,本能式的先找到JNI_OnLoad函数,函数反汇编如下:
signed int __fastcall JNI_OnLoad(int a1)
{
  int v1; // r4
  _DWORD *v2; // r5
  int v3; // r6
  _DWORD *v4; // r6
  signed int v5; // r6
  int v7; // [sp-8h] [bp-28h]
  char v8; // [sp+0h] [bp-20h]

  v1 = a1;
  dword_62C8 = 0;
  v2 = (_DWORD *)dword_62C4;
  if ( dword_62C4 )
  {
    do
    {
      if ( *v2 >= 1 )
      {
        v3 = 0;
        do
          ((void (*)(void))v2[v3++ + 1])();
        while ( v3 < *v2 );
      }
      v4 = (_DWORD *)v2[11];
      free(v2);
      v2 = v4;
    }
    while ( v4 );
    dword_62C4 = 0;
  }
  dword_62B4(&v8, 0, sub_16A4, 0, 0);
  sub_17F4();
  v5 = 65540;
  if ( (*(int (__fastcall **)(int, int *, signed int))(*(_DWORD *)v1 + 24))(v1, &v7, 65540) )
    v5 = -1;
  return v5;
}
通篇看了代码,没有一个见过的函数名,逐层浏览函数调用之后发现很多的函数调用都是一些普通int型变量在作为函数指针调用,似乎有点像函数指针的调用方法,那这些函数指针都是哪里赋值的呢?思考2秒之后,反应过来,.init_array段。
直接IDA+Ctrl+S找到so的.init_array段,点击进入,发现一个sub_2378的代码段,如下图:

点击进入,F5反汇编之后发现是如下的调用关系:
int sub_2378()
{
  return sub_22AC((int)sub_1CA8);
}
sub_1CA8先调用那就先看sub_1CA8代码:
int sub_1CA8()
{
  int (__fastcall *v0)(signed int, void *); // r4
  int result; // r0

  if ( !byte_635F )
  {
    sub_24F4(&unk_62D7, 6, &unk_4509, "9HbB", 4, 197);
    byte_635F = 1;
  }
  v0 = (int (__fastcall *)(signed int, void *))dlsym((void *)0xFFFFFFFF, (const char *)&unk_62D7);
  if ( !byte_6360 )
  {
    sub_24F4(&unk_62EF, 7, &unk_448B, &unk_4488, 2, 213);
    byte_6360 = 1;
  }
  dword_6294 = v0(-1, &unk_62EF);
  if ( !byte_6361 )
  {
    sub_239C(&unk_630C, 8, (char *)&unk_44F3, (int)"LNAt", 4u);
    byte_6361 = 1;
  }
  dword_6298 = v0(-1, &unk_630C);
  if ( !byte_6362 )
  {
    sub_239C(&unk_62DD, 6, (char *)&unk_44AC, (int)"cOXt", 4u);
    byte_6362 = 1;
  }
  dword_629C = v0(-1, &unk_62DD);
  if ( !byte_6363 )
  {
    sub_24F4(&unk_62E3, 6, &unk_4481, "BMT", 3, 1);
    byte_6363 = 1;
  }
  dword_62A0 = v0(-1, &unk_62E3);
  if ( !byte_6364 )
  {
    sub_239C(&unk_62F6, 7, (char *)&unk_44C4, (int)&unk_44C1, 2u);
    byte_6364 = 1;
  }
  dword_62A4 = v0(-1, &unk_62F6);
  if ( !byte_6365 )
  {
    sub_254C(&unk_62FD, 7, &unk_44CC, &unk_44FC, 0, 1);
    byte_6365 = 1;
  }
  dword_62A8 = v0(-1, &unk_62FD);
  if ( !byte_6366 )
  {
    sub_254C(&unk_62CC, 5, &unk_44A1, &unk_44FC, 0, 1);
    byte_6366 = 1;
  }
  dword_62AC = v0(-1, &unk_62CC);
  if ( !byte_6367 )
  {
    sub_254C(&unk_62E9, 6, &unk_44FD, &unk_44FC, 0, 1);
    byte_6367 = 1;
  }
  dword_62B0 = v0(-1, &unk_62E9);
  if ( !byte_6368 )
  {
    sub_258C(&unk_633A, 15, "3:*8(-&\x1B\"@%%7)B", "BMAE", 4, 1);
    byte_6368 = 1;
  }
  dword_62B4 = v0(-1, &unk_633A);
  if ( !byte_6369 )
  {
    sub_258C(&unk_631C, 9, &unk_44E4, &unk_44E0, 3, 201);
    byte_6369 = 1;
  }
  dword_62B8 = v0(-1, &unk_631C);
  if ( !byte_636A )
  {
    sub_254C(&unk_632F, 11, &unk_44D4, &unk_44FC, 0, 1);
    byte_636A = 1;
  }
  dword_62BC = v0(-1, &unk_632F);
  if ( !byte_636B )
  {
    sub_258C(&unk_6314, 8, &unk_44B8, ".6gq", 4, 187);
    byte_636B = 1;
  }
  result = v0(-1, &unk_6314);
  dword_62C0 = result;
  return result;
}
初看这个函数很懵逼,但是看到dlsym函数之后,便豁然开朗,这就是通过dlsym获取动态库的函数指针,将所有用到的函数都用函数指针保存起来,隐藏函数本身的样式,加大过反调试的难度。
注:这里有个知识点,dlsym函数第一个参数handle可以带dlopen库的返回值,也可以带0xFFFFFFFF,带0xFFFFFFFF表示从当前内存加载的所有so中查找某个symbol
既然知道了这个套路,那就得开始人肉解密了,因为现在还不知道具体调用了哪些C库的函数,也还不知道具体是怎么做的反调试,必须明确知道各个函数调用之后才能更清晰的看清楚程序的逻辑。分析了程序之后我们知道整个函数内各个函数指针的获取都调用了v0这个函数,那我们隐约感觉到v0可能就是dlsym的函数指针,验证一下,直接把解密函数的伪代码粘贴到任何一个你喜欢的C/C++ IDE里面,稍微修改一下参数的类型,代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int sub_24F4(char* result, int a2, unsigned char* a3, unsigned char* a4, unsigned int a5, int a6)
{
  char* v6; // r7
  unsigned int v7; // r4

  v6 = result;
  if ( a2 )
  {
    v7 = 0;
    do
    {
      result = (*(unsigned char*)(a3 + v7) ^ a6) - *(unsigned char *)(a4 + v7 % a5);
      *(char* *)(v6 + v7++) = result;
    }
    while ( a2 != v7 );
  }
  return result;
}

int main()
{
    char unk_62D7[128] = {0};
    unsigned char unk_4509[7] = {0x58,0x71,0x10,0x7E,0x63,0x8D,0x00};
    sub_24F4(unk_62D7, 6, unk_4509, "9HbB", 4u, 197);
    printf("unk_62D7:%s\n",(char*)unk_62D7);
    return 0;
}
代码运行结果如下:

unk_62D7:dlsym
果然不出所料啊,v0就是dlsym函数的指针,添加所有的解密函数,做一个解密小程序代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#if defined(__GNUC__)
typedef          long long ll;
typedef unsigned long long ull;
#define __int64 long long
#define __int32 int
#define __int16 short
#define __int8  char
#define MAKELL(num) num ## LL
#define FMT_64 "ll"
#elif defined(_MSC_VER)
typedef          __int64 ll;
typedef unsigned __int64 ull;
#define MAKELL(num) num ## i64
#define FMT_64 "I64"
#elif defined (__BORLANDC__)
typedef          __int64 ll;
typedef unsigned __int64 ull;
#define MAKELL(num) num ## i64
#define FMT_64 "L"
#else
#error "unknown compiler"
#endif
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;

typedef          char   int8;
typedef   signed char   sint8;
typedef unsigned char   uint8;
typedef          short  int16;
typedef   signed short  sint16;
typedef unsigned short  uint16;
typedef          int    int32;
typedef   signed int    sint32;
typedef unsigned int    uint32;
typedef ll              int64;
typedef ll              sint64;
typedef ull             uint64;

// Partially defined types:
#define _BYTE  uint8
#define _WORD  uint16
#define _DWORD uint32
#define _QWORD uint64
#if !defined(_MSC_VER)
#define _LONGLONG __int128
#endif

#ifndef _WINDOWS_
typedef int8 BYTE;
typedef int16 WORD;
typedef int32 DWORD;
typedef int32 LONG;
#endif
typedef int64 QWORD;
#ifndef __cplusplus
typedef int bool;       // we want to use bool in our C programs
#endif

// Some convenience macros to make partial accesses nicer
// first unsigned macros:
#define LOBYTE(x)   (*((_BYTE*)&(x)))   // low byte
#define LOWORD(x)   (*((_WORD*)&(x)))   // low word
#define LODWORD(x)  (*((_DWORD*)&(x)))  // low dword
#define HIBYTE(x)   (*((_BYTE*)&(x)+1))
#define HIWORD(x)   (*((_WORD*)&(x)+1))
#define HIDWORD(x)  (*((_DWORD*)&(x)+1))
#define BYTEn(x, n)   (*((_BYTE*)&(x)+n))
#define WORDn(x, n)   (*((_WORD*)&(x)+n))
#define BYTE1(x)   BYTEn(x,  1)         // byte 1 (counting from 0)
#define BYTE2(x)   BYTEn(x,  2)
#define BYTE3(x)   BYTEn(x,  3)
#define BYTE4(x)   BYTEn(x,  4)
#define BYTE5(x)   BYTEn(x,  5)
#define BYTE6(x)   BYTEn(x,  6)
#define BYTE7(x)   BYTEn(x,  7)
#define BYTE8(x)   BYTEn(x,  8)
#define BYTE9(x)   BYTEn(x,  9)
#define BYTE10(x)  BYTEn(x, 10)
#define BYTE11(x)  BYTEn(x, 11)
#define BYTE12(x)  BYTEn(x, 12)
#define BYTE13(x)  BYTEn(x, 13)
#define BYTE14(x)  BYTEn(x, 14)
#define BYTE15(x)  BYTEn(x, 15)
#define WORD1(x)   WORDn(x,  1)
#define WORD2(x)   WORDn(x,  2)         // third word of the object, unsigned
#define WORD3(x)   WORDn(x,  3)
#define WORD4(x)   WORDn(x,  4)
#define WORD5(x)   WORDn(x,  5)
#define WORD6(x)   WORDn(x,  6)
#define WORD7(x)   WORDn(x,  7)

// now signed macros (the same but with sign extension)
#define SLOBYTE(x)   (*((int8*)&(x)))
#define SLOWORD(x)   (*((int16*)&(x)))
#define SLODWORD(x)  (*((int32*)&(x)))
#define SHIBYTE(x)   (*((int8*)&(x)+1))
#define SHIWORD(x)   (*((int16*)&(x)+1))
#define SHIDWORD(x)  (*((int32*)&(x)+1))
#define SBYTEn(x, n)   (*((int8*)&(x)+n))
#define SWORDn(x, n)   (*((int16*)&(x)+n))
#define SBYTE1(x)   SBYTEn(x,  1)
#define SBYTE2(x)   SBYTEn(x,  2)
#define SBYTE3(x)   SBYTEn(x,  3)
#define SBYTE4(x)   SBYTEn(x,  4)
#define SBYTE5(x)   SBYTEn(x,  5)
#define SBYTE6(x)   SBYTEn(x,  6)
#define SBYTE7(x)   SBYTEn(x,  7)
#define SBYTE8(x)   SBYTEn(x,  8)
#define SBYTE9(x)   SBYTEn(x,  9)
#define SBYTE10(x)  SBYTEn(x, 10)
#define SBYTE11(x)  SBYTEn(x, 11)
#define SBYTE12(x)  SBYTEn(x, 12)
#define SBYTE13(x)  SBYTEn(x, 13)
#define SBYTE14(x)  SBYTEn(x, 14)
#define SBYTE15(x)  SBYTEn(x, 15)
#define SWORD1(x)   SWORDn(x,  1)
#define SWORD2(x)   SWORDn(x,  2)
#define SWORD3(x)   SWORDn(x,  3)
#define SWORD4(x)   SWORDn(x,  4)
#define SWORD5(x)   SWORDn(x,  5)
#define SWORD6(x)   SWORDn(x,  6)
#define SWORD7(x)   SWORDn(x,  7)


// Helper functions to represent some assembly instructions.

#ifdef __cplusplus

// Fill memory block with an integer value
inline void memset32(void *ptr, uint32 value, int count)
{
    uint32 *p = (uint32 *)ptr;
    for ( int i=0; i < count; i++ )
        *p++ = value;
}

// Generate a reference to pair of operands
template<class T>  int16 __PAIR__( int8  high, T low) { return ((( int16)high) << sizeof(high)*8) | uint8(low); }
template<class T>  int32 __PAIR__( int16 high, T low) { return ((( int32)high) << sizeof(high)*8) | uint16(low); }
template<class T>  int64 __PAIR__( int32 high, T low) { return ((( int64)high) << sizeof(high)*8) | uint32(low); }
template<class T> uint16 __PAIR__(uint8  high, T low) { return (((uint16)high) << sizeof(high)*8) | uint8(low); }
template<class T> uint32 __PAIR__(uint16 high, T low) { return (((uint32)high) << sizeof(high)*8) | uint16(low); }
template<class T> uint64 __PAIR__(uint32 high, T low) { return (((uint64)high) << sizeof(high)*8) | uint32(low); }

// rotate left
template<class T> T __ROL__(T value, uint count)
{
    const uint nbits = sizeof(T) * 8;
    count %= nbits;

    T high = value >> (nbits - count);
    value <<= count;
    value |= high;
    return value;
}

// rotate right
template<class T> T __ROR__(T value, uint count)
{
    const uint nbits = sizeof(T) * 8;
    count %= nbits;

    T low = value << (nbits - count);
    value >>= count;
    value |= low;
    return value;
}

// carry flag of left shift
template<class T> int8 __MKCSHL__(T value, uint count)
{
    const uint nbits = sizeof(T) * 8;
    count %= nbits;

    return (value >> (nbits-count)) & 1;
}

// carry flag of right shift
template<class T> int8 __MKCSHR__(T value, uint count)
{
    return (value >> (count-1)) & 1;
}

// sign flag
template<class T> int8 __SETS__(T x)
{
    if ( sizeof(T) == 1 )
        return int8(x) < 0;
    if ( sizeof(T) == 2 )
        return int16(x) < 0;
    if ( sizeof(T) == 4 )
        return int32(x) < 0;
    return int64(x) < 0;
}

// overflow flag of subtraction (x-y)
template<class T, class U> int8 __OFSUB__(T x, U y)
{
    if ( sizeof(T) < sizeof(U) )
    {
        U x2 = x;
        int8 sx = __SETS__(x2);
        return (sx ^ __SETS__(y)) & (sx ^ __SETS__(x2-y));
    }
    else
    {
        T y2 = y;
        int8 sx = __SETS__(x);
        return (sx ^ __SETS__(y2)) & (sx ^ __SETS__(x-y2));
    }
}

// overflow flag of addition (x+y)
template<class T, class U> int8 __OFADD__(T x, U y)
{
    if ( sizeof(T) < sizeof(U) )
    {
        U x2 = x;
        int8 sx = __SETS__(x2);
        return ((1 ^ sx) ^ __SETS__(y)) & (sx ^ __SETS__(x2+y));
    }
    else
    {
        T y2 = y;
        int8 sx = __SETS__(x);
        return ((1 ^ sx) ^ __SETS__(y2)) & (sx ^ __SETS__(x+y2));
    }
}

// carry flag of subtraction (x-y)
template<class T, class U> int8 __CFSUB__(T x, U y)
{
    int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
    if ( size == 1 )
        return uint8(x) < uint8(y);
    if ( size == 2 )
        return uint16(x) < uint16(y);
    if ( size == 4 )
        return uint32(x) < uint32(y);
    return uint64(x) < uint64(y);
}

// carry flag of addition (x+y)
template<class T, class U> int8 __CFADD__(T x, U y)
{
    int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
    if ( size == 1 )
        return uint8(x) > uint8(x+y);
    if ( size == 2 )
        return uint16(x) > uint16(x+y);
    if ( size == 4 )
        return uint32(x) > uint32(x+y);
    return uint64(x) > uint64(x+y);
}

#else
// The following definition is not quite correct because it always returns
// uint64. The above C++ functions are good, though.
#define __PAIR__(high, low) (((uint64)(high)<<sizeof(high)*8) | low)
// For C, we just provide macros, they are not quite correct.
#define __ROL__(x, y) __rotl__(x, y)      // Rotate left
#define __ROR__(x, y) __rotr__(x, y)      // Rotate right
#define __CFSHL__(x, y) invalid_operation // Generate carry flag for (x<<y)
#define __CFSHR__(x, y) invalid_operation // Generate carry flag for (x>>y)
#define __CFADD__(x, y) invalid_operation // Generate carry flag for (x+y)
#define __CFSUB__(x, y) invalid_operation // Generate carry flag for (x-y)
#define __OFADD__(x, y) invalid_operation // Generate overflow flag for (x+y)
#define __OFSUB__(x, y) invalid_operation // Generate overflow flag for (x-y)
#endif

// No definition for rcl/rcr because the carry flag is unknown
#define __RCL__(x, y)    invalid_operation // Rotate left thru carry
#define __RCR__(x, y)    invalid_operation // Rotate right thru carry
#define __MKCRCL__(x, y) invalid_operation // Generate carry flag for a RCL
#define __MKCRCR__(x, y) invalid_operation // Generate carry flag for a RCR
#define __SETP__(x, y)   invalid_operation // Generate parity flag for (x-y)

// In the decompilation listing there are some objects declarared as _UNKNOWN
// because we could not determine their types. Since the C compiler does not
// accept void item declarations, we replace them by anything of our choice,
// for example a char:

#define _UNKNOWN char

#ifdef _MSC_VER
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

int __fastcall sub_239C(_BYTE *a1, int a2, char *a3, int a4, unsigned int a5)
{
  _BYTE *v5; // r11
  char *v6; // r10
  int v7; // r5
  int v8; // r0
  unsigned int v9; // r7
  char v10; // r6
  int v11; // r8
  int result; // r0
  int v13; // r1
  char v14; // r1
  int v15; // r1
  int v16; // r0
  char v17; // r2
  char v18; // t1
  char v19[256]; // [sp+8h] [bp-128h]
  __int16 v20; // [sp+108h] [bp-28h]

  v5 = a1;
  v6 = a3;
  v7 = a2;
  v8 = 0;
  do
  {
    v19[v8] = v8;
    ++v8;
  }
  while ( v8 != 256 );
  v9 = 0;
  v10 = 0;
  v20 = 0;
  do
  {
    v11 = (unsigned __int8)v19[v9];
    result = *(unsigned __int8 *)(a4 + v9 % a5) + v11;
    v13 = (unsigned __int8)(result + v10);
    v19[v9] = v19[v13];
    v19[v13] = v11;
    ++v9;
    v10 += result;
  }
  while ( v9 != 256 );
  if ( v7 )
  {
    LOBYTE(result) = HIBYTE(v20);
    v14 = v20;
    do
    {
      LOBYTE(v15) = v14 + 1;
      --v7;
      LOBYTE(v20) = v15;
      v15 = (unsigned __int8)v15;
      LOBYTE(v16) = result + v19[(unsigned __int8)v15];
      HIBYTE(v20) = v16;
      v16 = (unsigned __int8)v16;
      v17 = v19[(unsigned __int8)v15];
      v19[v15] = v19[(unsigned __int8)v16];
      v19[v16] = v17;
      v14 = v20;
      result = HIBYTE(v20);
      v18 = *v6++;
      *v5++ = v19[(unsigned __int8)(v19[HIBYTE(v20)] + v19[(unsigned __int8)v20])] ^ v18;
    }
    while ( v7 );
  }
  return result;
}

int sub_258C(char* result, int a2, char* a3, char* a4, unsigned int a5, int a6)
{
    char* v6; // r7
    unsigned int v7; // r4

    v6 = result;
    if ( a2 )
    {
        v7 = 0;
        do
        {
            result = (*((unsigned char*)a4 + v7 % a5)) ^ (*((unsigned char*)a3 + v7) - a6);
            *(char *)(v6 + v7++) = result;
        }
        while ( a2 != v7 );
    }
    return result;
}


char* sub_254C(char *result, int a2, unsigned char *a3, char* a4, int a5, char a6)
{
    unsigned int v6; // r3
    unsigned int v7; // t1

    if ( a2 )
    {
        v6 = a3[a2 - 1];
        do
        {
            v7 = *a3++;
            --a2;
            *result++ = ((char)v7 << a6) | (v6 >> (8 - a6));
            v6 = v7;
        }
        while ( a2 );
    }
    return result;
}

int sub_239C(char *a1, int a2, char *a3, char* a4, unsigned int a5)
{
    char *v5; // r11
    char *v6; // r10
    int v7; // r5
    int v8; // r0
    unsigned int v9; // r7
    int v10; // r6
    int v11; // r8
    int result; // r0
    int v13; // r1
    char v14; // r1
    int v15; // r1
    int v16; // r0
    char v17; // r2
    char v18; // t1
    char v19[256]; // [sp+8h] [bp-128h]
    short v20; // [sp+108h] [bp-28h]

    v5 = a1;
    v6 = a3;
    v7 = a2;
    v8 = 0;
    do
    {
        v19[v8] = v8;
        ++v8;
    }
    while ( v8 != 256 );
    v9 = 0;
    v10 = 0;
    v20 = 0;
    do
    {
        v11 = (unsigned char)v19[v9];
        result = *(unsigned char *)(a4 + v9 % a5) + v11;
        v13 = (unsigned char)(result + v10);
        v19[v9] = v19[v13];
        v19[v13] = v11;
        ++v9;
        v10 += result;
    }
    while ( v9 != 256 );
    if ( v7 )
    {
        LOBYTE(result) = HIBYTE(v20);
        v14 = v20;
        do
        {
            LOBYTE(v15) = v14 + 1;
            --v7;
            LOBYTE(v20) = v15;
            v15 = (unsigned char)v15;
            LOBYTE(v16) = result + v19[(unsigned char)v15];
            HIBYTE(v20) = v16;
            v16 = (unsigned char)v16;
            v17 = v19[(unsigned char)v15];
            v19[v15] = v19[(unsigned char)v16];
            v19[v16] = v17;
            v14 = v20;
            result = HIBYTE(v20);
            v18 = *v6++;
            *v5++ = v19[(unsigned char)(v19[HIBYTE(v20)] + v19[(unsigned char)v20])] ^ v18;
        }
        while ( v7 );
    }
    return result;
}

int sub_24F4(char* result, int a2, unsigned char* a3, unsigned char* a4, unsigned int a5, int a6)
{
    char* v6; // r7
    unsigned int v7; // r4

    v6 = result;
    if ( a2 )
    {
        v7 = 0;
        do
        {
            result = (*(unsigned char*)(a3 + v7) ^ a6) - *(unsigned char *)(a4 + v7 % a5);
            *(char* *)(v6 + v7++) = result;
        }
        while ( a2 != v7 );
    }
    return result;
}

int main()
{
    unsigned char unk_44FC[1] = {0x00};

    char unk_62D7[128] = {0};
    unsigned char unk_4509[7] = {0x58,0x71,0x10,0x7E,0x63,0x8D,0x00};
    sub_24F4(unk_62D7, 6, unk_4509, "9HbB", 4u, 197);
    printf("v0:%s\n",(char*)unk_62D7);

    char unk_62EF[128];
    unsigned char unk_448B[8] = {0x01,0x49,0x34,0x72,0x03,0x4E,0xB8,0x00};
    unsigned char unk_4488[3] = {0x6D,0x37,0x00};
    sub_24F4(unk_62EF, 7, unk_448B, unk_4488, 2u, 213);
    printf("dword_6294:%s\n",(char*)unk_62EF);

    char unk_630C[128] = {0};
    unsigned char unk_44F3[9] = {0xE6,0xCF,0xCF,0x89,0xBB,0x16,0x65,0x71,0x00};
    sub_239C(unk_630C, 8, unk_44F3, "LNAt", 4u);
    printf("dword_6298:%s\n",(char*)unk_630C);

    char unk_62DD[128] ={0};
    unsigned char unk_44AC[7] = {0x30,0xB4,0x7D,0x77,0x9C,0xA5,0x00};
    sub_239C(unk_62DD, 6, unk_44AC, "cOXt", 4u);
    printf("dword_629C:%s\n",(char*)unk_62DD);

    char unk_62E3[128];
    unsigned char unk_4481[7] = {0xA9,0xB5,0xB8,0xB7,0xC1,0x55,0x00};
    sub_24F4(unk_62E3, 6, unk_4481, "BMT", 3u, 1);
    printf("dword_62A0 :%s\n",(char*)unk_62E3);

    char unk_62F6[128] ={0};
    unsigned char unk_44C4[8] = {0x45,0xEB,0x5A,0x11,0x75,0x7E,0x4E,0x00};
    unsigned char unk_44C1[3] = {0x47,0x21,0x00};
    sub_239C(unk_62F6, 7, unk_44C4, unk_44C1,2u);
    printf("dword_62A4:%s\n",(char*)unk_62F6);

    char unk_62FD[128];
    unsigned char unk_44CC[8] = {0xB9,0xB9,0xB1,0x30,0x37,0x33,0x80,0x00};
    sub_254C(unk_62FD, 7, unk_44CC, unk_44FC, 0, 1);
    printf("dword_62A8:%s\n",(char*)unk_62FD);

    char unk_62CC[128];
    unsigned char unk_44A1[6] = {0xB5,0x34,0x36,0x36,0x80,0x00};
    sub_254C(unk_62CC, 5, unk_44A1, unk_44FC, 0, 1);
    printf("dword_62AC:%s\n",(char*)unk_62CC);

    unsigned char unk_44FD[7] = {0x39,0xB6,0xB2,0x32,0x38,0x80,0x00};
    char unk_62E9[128];
    sub_254C(unk_62E9, 6, unk_44FD, unk_44FC, 0, 1);
    printf("dword_62B0:%s\n",(char*)unk_62E9);

    char unk_633A[128];
    sub_258C(unk_633A, 15, "3:*8(-&\x1B\"@%%7)B", "BMAE", 4u, 1);
    printf("dword_62B4:%s\n",(char*)unk_633A);

    char unk_631C[128];
    unsigned char unk_44E4[10] = {0x23,0x18,0xCA,0x21,0x14,0xDF,0x1D,0x14,0x3C,0x00};
    unsigned char unk_44E0[4] = {0x37,0x3F,0x73,0x00};
    sub_258C(unk_631C, 9, unk_44E4, unk_44E0, 3u, 201);
    printf("dword_62B8:%s\n",(char*)unk_631C);

    char unk_632F[128];
    unsigned char unk_44D4[12] = {0xB1,0xB0,0x31,0xB4,0x32,0x33,0xB6,0xBA,0x39,0x34,0x80,0x00};
    sub_254C(unk_632F, 11, unk_44D4, unk_44FC, 0, 1);
    printf("dword_62BC:%s\n",(char*)unk_632F);

    char unk_6314[128];
    unsigned char unk_44B8[9] = {0xFD,0xFF,0xC1,0xDA,0x05,0xBD,0x1A,0x2C,0x00};
    sub_258C(unk_6314, 8, unk_44B8, ".6gq", 4u, 187);
    printf("dword_62C0:%s\n",(char*)unk_6314);

    return 0;
}

代码运行如下:
v0:dlsym
dword_6294:getpid
dword_6298:sprintf
dword_629C:fopen
dword_62A0:fgets
dword_62A4:strstr
dword_62A8:sscanf
dword_62AC:kill
dword_62B0:sleep
dword_62B4:pthread_create
dword_62B8:mprotect
dword_62BC:cacheflush
dword_62C0:lrand48
这些解密函数的实现就是F5伪代码,稍微修改一下函数参数或者变量类型,就可以得到,这需要一些C语言的基础。 
依次选中每个dword变量,“N”快捷键将每个dword变量替换成相应的函数名,函数变成这样:
int (*sub_1CA8())(void)
{
  int (__fastcall *dlsym)(signed int, void *); // r4
  int (*lrand48)(void); // r0

  if ( !byte_635F )
  {
    sub_24F4((int)&unk_62D7, 6, (int)&unk_4509, (int)"9HbB", 4u, 197);
    byte_635F = 1;
  }
  dlsym = (int (__fastcall *)(signed int, void *))::dlsym((void *)0xFFFFFFFF, (const char *)&unk_62D7);
  if ( !byte_6360 )
  {
    sub_24F4((int)&unk_62EF, 7, (int)&unk_448B, (int)&unk_4488, 2u, 213);
    byte_6360 = 1;
  }
  getpid = (int (*)(void))dlsym(-1, &unk_62EF);
  if ( !byte_6361 )
  {
    sub_239C(&unk_630C, 8, (char *)&unk_44F3, (int)"LNAt", 4u);
    byte_6361 = 1;
  }
  sprintf = dlsym(-1, &unk_630C);
  if ( !byte_6362 )
  {
    sub_239C(&unk_62DD, 6, (char *)&unk_44AC, (int)"cOXt", 4u);
    byte_6362 = 1;
  }
  fopen = dlsym(-1, &unk_62DD);
  if ( !byte_6363 )
  {
    sub_24F4((int)&unk_62E3, 6, (int)&unk_4481, (int)"BMT", 3u, 1);
    byte_6363 = 1;
  }
  fgets = (int (__fastcall *)(_DWORD, _DWORD, _DWORD))dlsym(-1, &unk_62E3);
  if ( !byte_6364 )
  {
    sub_239C(&unk_62F6, 7, (char *)&unk_44C4, (int)&unk_44C1, 2u);
    byte_6364 = 1;
  }
  strstr = dlsym(-1, &unk_62F6);
  if ( !byte_6365 )
  {
    sub_254C(&unk_62FD, 7, &unk_44CC, &unk_44FC, 0, 1);
    byte_6365 = 1;
  }
  sscanf = dlsym(-1, &unk_62FD);
  if ( !byte_6366 )
  {
    sub_254C(&unk_62CC, 5, &unk_44A1, &unk_44FC, 0, 1);
    byte_6366 = 1;
  }
  kill = dlsym(-1, &unk_62CC);
  if ( !byte_6367 )
  {
    sub_254C(&unk_62E9, 6, &unk_44FD, &unk_44FC, 0, 1);
    byte_6367 = 1;
  }
  sleep = (int (__fastcall *)(_DWORD))dlsym(-1, &unk_62E9);
  if ( !byte_6368 )
  {
    sub_258C(&unk_633A, 15, "3:*8(-&\x1B\"@%%7)B", "BMAE", 4, 1);
    byte_6368 = 1;
  }
  pthread_create = (int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))dlsym(-1, &unk_633A);
  if ( !byte_6369 )
  {
    sub_258C(&unk_631C, 9, &unk_44E4, &unk_44E0, 3, 201);
    byte_6369 = 1;
  }
  mprotect = (int (__fastcall *)(_DWORD))dlsym(-1, &unk_631C);
  if ( !byte_636A )
  {
    sub_254C(&unk_632F, 11, &unk_44D4, &unk_44FC, 0, 1);
    byte_636A = 1;
  }
  cacheflush = (int (*)(void))dlsym(-1, &unk_632F);
  if ( !byte_636B )
  {
    sub_258C(&unk_6314, 8, &unk_44B8, ".6gq", 4, 187);
    byte_636B = 1;
  }
  lrand48 = (int (*)(void))dlsym(-1, &unk_6314);
  ::lrand48 = lrand48;
  return lrand48;
}
看到pthread_create,getpid,kill这些函数之后,当前程序的反调试99%是用的检查/proc/<pid>/status的TracerPid>0来做的反调试,选中pthread_create,IDA+X快捷键,查看这个函数的引用位置:

点击第二个位置,跳转到pthread_create的调用地方,

点进sub_16A4这个函数,

很自然,这是一个死循环,反调试就在sub_130C里面,点进函数:
int sub_130C()
{
  int v0; // r8
  void (__fastcall *v1)(char *, void *, int); // r4
  int (__fastcall *v2)(char *, void *); // r5
  unsigned __int8 *v3; // r6
  char *v4; // r4
  int (__fastcall *v5)(char *, void *); // r5
  char *v6; // r11
  unsigned __int8 *v7; // r4
  char *v8; // r6
  void (__fastcall *v9)(char *, void *, char *, int *); // r11
  int v11; // [sp+34h] [bp-314h]
  int v12; // [sp+38h] [bp-310h]
  char v13; // [sp+3Ch] [bp-30Ch]
  char v14; // [sp+BCh] [bp-28Ch]
  char v15; // [sp+2BCh] [bp-8Ch]
  int v16; // [sp+320h] [bp-28h]

  _aeabi_memset(&v15, 100, 0);
  v0 = getpid();
  v1 = (void (__fastcall *)(char *, void *, int))dword_6298;
  if ( !byte_635B )
  {
    sub_239C(&unk_6349, 16, (char *)&unk_4550, (int)"s!#L", 4u);
    byte_635B = 1;
  }
  v1(&v15, &unk_6349, v0);
  v2 = (int (__fastcall *)(char *, void *))dword_629C;
  if ( !byte_635C )
  {
    sub_254C(&unk_6290, 2, &unk_454A, &unk_44FC, 0, 1);
    byte_635C = 1;
  }
  v11 = v2(&v15, &unk_6290);
  _aeabi_memset(&v14, 512, 0);
  v3 = &stru_2C8.st_info;
  if ( fgets(&v14, 512, v11) )
  {
    v4 = &v13;
    while ( 1 )
    {
      v5 = (int (__fastcall *)(char *, void *))dword_62A4;
      if ( !byte_635D )
      {
        v6 = v4;
        v7 = v3;
        v8 = (char *)&GLOBAL_OFFSET_TABLE_ + (_DWORD)v3;
        sub_24F4((int)(v8 + 149), 10, (int)&unk_4496, (int)&unk_4493, 2u, 157);
        v8[205] = 1;
        v3 = v7;
        v4 = v6;
      }
      if ( v5(&v14, &unk_6325) )
      {
        _aeabi_memset(v4, 128, 0);
        v12 = 0;
        v9 = (void (__fastcall *)(char *, void *, char *, int *))sscanf;
        if ( !byte_635E )
        {
          sub_239C((_BYTE *)&GLOBAL_OFFSET_TABLE_ + (_DWORD)v3 + 65, 6, (char *)&unk_4461, (int)"L79", 3u);
          *((_BYTE *)&GLOBAL_OFFSET_TABLE_ + (_DWORD)v3 + 206) = 1;
        }
        v9(&v14, &unk_62D1, v4, &v12);
        if ( v12 >= 1 )
          break;
      }
      if ( !fgets(&v14, 512, v11) )
        return _stack_chk_guard - v16;
    }
    (*(void (__fastcall **)(int, signed int))((char *)&GLOBAL_OFFSET_TABLE_ + (_DWORD)v3 + (unsigned int)&dword_1C))(
      v0,
      9);
  }
  return _stack_chk_guard - v16;
}
根据代码流程的分析,当看到如下函数的时候:
    (*(void (__fastcall **)(int, signed int))((char *)&GLOBAL_OFFSET_TABLE_ + (_DWORD)v3 + (unsigned int)&dword_1C))(
      v0,
      9);
应该立即想到这个就是在TracerPid>=1的时候调用的kill(发信号)函数,向对应PID发送9(SIGKILL)信号,以干掉进程,实现反调试,那就转到汇编代码,patch掉这个函数调用,如下,将BLX R2指令直接nop掉:

保存好文件之后,回编译apk进行动态调试,在Java_com_yaotong_crackme_MainActivity_securityCheck函数里下断点:
密码框里任意输入字符,点击输入密码,程序到断点停下,这个时候单步调试,到off_5F4E228C这个变量的时候我们发现这个变量变成了“aiyou,bucuoo”:

那这个就是密码了,在APP里输入这个字符串,点击输入密码,出现如下页面:

We got it,成功了~

0x02总结:

通过这次crackme的练手,加深了逆向方法论的认识,也熟练了各种工具的使用,接下来我要开始学习Hook了,可能以后这种破解只需要一个Hook就可以完成了。可能以后来看这个方法就很傻逼了,但是我认为不管方法怎么多怎么先进,“还原逻辑”作为逆向的核心思想和必备技能不会改变,“还原逻辑”也会贯穿整个逆向的学习或者工作过程,例如:还原一个SSL的加密模块;还原一个HTTP访问的参数加密等等。 我希望通过这个简单的例子,能够给新手们一个启发,作为新手的我或者你,去分析流程,去还原逻辑,永远比使用XX工具一键脱壳更有意义。

0x03彩蛋:
后来经过动态调试发现,密码是在jolin函里面被修改了的,jolin函数应该是有加密,动态调试的时候才能清晰的看到函数结构,也可以清晰的看到密码被修改的逻辑, jolin的解密是在sub_17F4这个函数体里面做的,具体的分析就不多讲了,有兴趣的同学可以去自己试着调试一下。

0x04最后:
大佬们请多指教。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-4-7 23:01 被wx_A.R编辑 ,原因: 解密小程序的修改,以便能够正确解密那三个先前未解密的程序;增加了IDA伪代码通用宏的声明;
上传的附件:
收藏
免费 5
支持
分享
最新回复 (12)
雪    币: 12502
活跃值: (3048)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
这个帖子才是值得精华的东西。
2020-4-3 08:05
0
雪    币: 1395
活跃值: (3374)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
66
2020-4-3 10:17
0
雪    币: 6573
活跃值: (3858)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
4
不错啊,这才是真正能力的体现,分析思路一目了然
2020-4-4 07:28
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
希望上传一下apk样本撒
2020-4-4 16:46
0
雪    币: 365
活跃值: (529)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
LowRebSwrd 不错啊,这才是真正能力的体现,分析思路一目了然
谢版主加优
2020-4-4 20:12
0
雪    币: 365
活跃值: (529)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
sayaeffify 希望上传一下apk样本撒
样本已经上传
2020-4-4 20:12
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
666
2020-4-7 17:13
0
雪    币: 158
活跃值: (755)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
这个app被一堆人写成过帖子
2020-4-7 18:39
0
雪    币: 365
活跃值: (529)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
10
Mars. 这个app被一堆人写成过帖子[em_10]
样本摆在那里,至少这个是我自己的思路,没有“天下文章一大抄”,如果我跟他们思路一样或者我抄袭他们的思路我是没脸记这些流水账的,还是一个,思路。
2020-4-7 18:46
0
雪    币: 365
活跃值: (529)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
11
先前漏掉的解密函数已经更新,解密小程序里面增加了IDA C伪代码的通用宏定义和类型定义,在以后分析C伪代码的时候这部分可以直接拿来套用。
2020-4-7 23:04
0
雪    币: 582
活跃值: (317)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
厉害 喜欢这样的帖子
2020-4-8 08:07
0
雪    币: 619
活跃值: (361)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
果然开发是逆向之基本
2020-5-12 09:13
0
游客
登录 | 注册 方可回帖
返回
//