吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 20559|回复: 61
收起左侧

[Android 原创] 2017腾讯游戏安全技术竞赛 Round 1 标准及进阶题解分析

  [复制链接]
Ericky 发表于 2017-12-1 20:37
PS:抱歉,很久没冒泡了。最近太忙了,发几篇之前参加比赛的笔记,分享一下。

前言

为了学习死磕的精神,我也来做题。我选择了APK的题目,题目有两种难度,分别对应标准级别和进阶级别,标准级别是100分,进阶级别是200分。我先看的标准级别,然后看的进阶级别,由于自己对一些算法的熟练程度不够,逆向东西也逆得慢,所以花了不少时间。再说一点题外话,这个进阶题目和之前360移动安全竞赛的APK cm比较相似,都是RSA算法,name和RSA中的e 、n、 d参数绑定,不同之处是360的那道题目的难点在于用了ollvm来进行混淆,增大分析难度,而这一道题目的难点是需要写一个完整的注册机,这个是两道题的不同之处,好了不扯了,下面开始正题。

0x1REG_CHECK函数
Java层比较简单,只是一个reg_check函数,传入输入的key和code,然后进行验证。这个函数是整个程序的核心,reg_check函数如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
int __fastcall CRegCheck::reg_check(int a1, int a2, _DWORD *a3, int a4)
{
  int v4; // r4@1
  char **v5; // r8@1
  int v6; // r6@1
  int v7; // r5@1
  int v8; // r0@2
  unsigned int v9; // r4@5
  int v10; // r4@5
  void *v11; // r4@6
  int v13; // [sp+20h] [bp-CD4h]@5
  void *ptr; // [sp+24h] [bp-CD0h]@2
  int v15; // [sp+28h] [bp-CCCh]@2
  int v16; // [sp+2Ch] [bp-CC8h]@2
  __int64 v17; // [sp+30h] [bp-CC4h]@2
  __int64 v18; // [sp+38h] [bp-CBCh]@7
  __int64 v19; // [sp+40h] [bp-CB4h]@7
  __int64 v20; // [sp+48h] [bp-CACh]@7
  __int64 v21; // [sp+58h] [bp-C9Ch]@5
  __int64 v22; // [sp+60h] [bp-C94h]@7
  int v23; // [sp+68h] [bp-C8Ch]@7
  int v24; // [sp+CE4h] [bp-10h]@1
  
  v4 = a4;
  v5 = (char **)a3;
  v6 = a2;
  v24 = _stack_chk_guard;
  v7 = 0;
  if ( CRegCheck::is_user_name_legal(_stack_chk_guard, (int *)a2) != 1 )
    goto LABEL_13;
  v8 = CRegCheck::get_formula_param(&v17, v6);
  ptr = 0;
  v15 = 0;
  v16 = 0;
  if ( v4 == 1 )
  {
    if ( !CBase64::base64_decode(*v5, (int)&ptr) )
      goto LABEL_8;
  }
  else
  {
    v9 = CRegCheck::get_rsa_key_seed(v8, v6);
    RSA::RSA((RSA *)&v21);
    RSA::RSAKey((RSA *)&v21, v9);
    sub_33440(&v13, (int *)v5);
    v10 = RSA::tdecrypto((int)&v21, (int)&v13, (int)&ptr);
    sub_332FC(v13 - 12);
    RSA::~RSA((RSA *)&v21);
    if ( !v10 )
    {
LABEL_8:
      v7 = 0;
      goto LABEL_9;
    }
  }
  v11 = ptr;
  if ( v15 - (_DWORD)ptr == 16 )
  {
    _aeabi_memclr(&v23, 0);
    _aeabi_memcpy(&v21, v11, 16);
    v7 = is_tangent(v20, v19, v18, v21, v22, v17);
LABEL_9:
    v11 = ptr;
    goto LABEL_11;
  }
  v7 = 0;
LABEL_11:
  if ( v11 )
    operator delete(v11);
LABEL_13:
  if ( _stack_chk_guard != v24 )
    _stack_chk_fail(_stack_chk_guard - v24);
  return v7;
}


0x2 is_user_name_legal函数

在check函数的开端,遇见的是is_user_name_legal函数,它的作用是校验了一下name的长度是否是39位,格式需要如下:
xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx (一共39位,暂且称之为8个段,在之后用segment1~segment8来表示)
x需要在 ‘0’-‘9’ 、 ‘a’-‘f’之间,否则key的合法性就不予通过。

0x3 get_formula_param函数

在get_formula_param函数中,根据输入的name 初始化了4个参数,每2个段生成一个参数,这些参数将在最后的is_tangent函数验证中用到。
具体参数代码如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
a1 = 0;
a1 += (segment1[0] * segment8[0]) << 16 ;
a1 += segment1[1] ^ segment8[1];
a1 += segment1[2] % (segment8[2]+1)+ 1;
a1 += segment1[3] / (segment8[3]+1);
  
a2 =0;
a2 += (segment2[0] ^ segment7[0]) << 16 ;
a2 += (segment2[1] % (segment7[1]+3));
a2 += (segment2[2] / (segment7[2]+1)) + 5;
a2 += (segment2[3] + (segment7[3])) ;
a3 = 0;
a3 += (segment3[0] / (3 + segment6[0]) )<< 16 ;
a3 += (segment3[1] * segment6[1]);
a3 +=  segment3[2] %(segment6[2] + 7) +5;
a3 +=  segment3[3] + segment6[3];
a6 = 0;
a6 += (segment4[0] + segment5[0]) << 16;
a6 *= segment4[1]/(segment5[1]+2);
a6 += segment4[2] %(segment5[2] +5) +7;
a6 += segment4[3] * segment5[3];

好了,这就是最后校验用到的4个参数。

0x4 分水岭 (标准OR进阶?)

检查完输入key的合法性和初始化4个重要的参数之后,这个时候判断模式,判断做题的人是选择的什么难度,如果是选择标准难度,将会对输入的code进行base64解码,再进行计算,判断是否成功。我打算重点说说进阶难度题目的事情,所以这里就给出一组正确的key,其他的过程需要读者自行跟踪一下。
我算出来的一组key:
key:0234-5678-90ab-00ef-0f87-6543-21fe-0cba
code:A6Kf2FVPOOvERSOoIf@5l2==
如果是选择的进阶的题目,将会进入到另外一番天地,先贴一段代码,如下:
[C++] 纯文本查看 复制代码
1
2
3
4
5
6
7
v9 = CRegCheck::get_rsa_key_seed(v8, v6);
    RSA::RSA((RSA *)&v21);
    RSA::RSAKey((RSA *)&v21, v9);
    sub_33440(&v13, (int *)v5);
    v10 = RSA::tdecrypto((int)&v21, (int)&v13, (int)&ptr);
    sub_332FC(v13 - 12);
    RSA::~RSA((RSA *)&v21);


从这里可以看出来,进阶版这里用的是RSA算法对输入的key和code进行验证。

0x5 get_rsa_key_seed函数

虽然这个名字叫做get_rsa_key_seed,但是其实里面的功能是对输入的key进行4次hash的计算,分别使用的是RSHash、JSHash、PJWHash、以及最后一个自定义的hash值计算的方法,具体的hash 函数代码在这里,请戳这里:
http://www.partow.net/programming/hashfunctions/index.html
对name 做了hash后,会将生成的hash值传入下一个函数,这个函数是RSAKey。

0x6 RSAKey函数

原函数代码如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
bool RSAKey() 
    for(i=0;i<MAX;i++) 
        m[i]=p[i]=q[i]=n[i]=d[i]=e[i]=0;
    prime_random(p,q);
    mul(p,q,n); 
    mov(p,p1); 
    p1[0]--;       
    mov(q,q1); 
    q1[0]--;      /*/q-1;*/ 
    mul(p1,q1,m);//m=(p-1)*(q-1) 
    erand(e,m); 
    rsad(e,m,d); 
    return true
}


看代码可以知道,这个RSAKey函数的主要作用是来生成n、e 、d的,逆向跟一下会发现,如果name不变,n、e、d也是不变的,也就是说name 和 n、e、d三个参数存在某种关系。what?因为我们知道n是p、q的乘积,而p与q这两个素数是用prime_random生成的,居然不变?点进去看看它prime_random函数的实现,发现set_rand_seed、gen_rand这两个函数,再点进去一看,都是类似这样的函数,贴其中的一个如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
void *__fastcall CGenRand::set_rand_seed(CGenRand *this, unsigned int a2)
{
  void *result; // r0@1
  
  __asm
  {
    VMOV            S0, R0
    VCVT.F64.U32    D0, S0
  }
  result = &CGenRand::seed_;
  __asm { VSTR            D0, [R0] }
  return result;

这是什么鬼,感觉好慌。这里卡了很久,被坑了一波。最后发现APK中有x86的lib,打开x86的so看了一下,里面有正确的c代码,是一个伪随机数,算法如下:
[C++] 纯文本查看 复制代码
1
2
hash_seed  = (double)(signed int)(0.00000095367431640625 * (hash_seed* 17.0 + 139.0)) * (-1048576.0)+ hash_seed * 17.0+ 139.0;
return hash_seed/10;


其中,hash_seed是一个全局变量,其实也就是get_rsa_key_seed函数的返回值,这就很明朗了,印证了固定的name对应固定e d n。同样的,在后面的erand函数也是用类似的方法生成了值。
都初始化好了之后开始了最后的解密函数tdecrypto。

0x7 tdecrypto函数

开头用了标准阶段的base64进行解密,然后所有解密的字符不能大于0x31,接着所有解密的字符会进入RSA解密阶段,充当大数的各个位,tdecrypto函数代码如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
string tdecrypto(int d[MAX], int n[MAX], string text) 
    struct slink *h,*p1,*p2; 
    char ch; 
    int i,j,k,c,count,temp; 
    i=0; 
    j=3; 
    count=0; 
    h=p1=p2=(struct slink * )malloc(LEN); 
    int kk; 
    for (kk = 0; kk < text.length(); kk++) 
    {   
        ch = text.at(kk); 
        c=ch;       
        if(j==3) 
        
            p1->bignum[MAX-2]=c-48; 
            j--; 
        
        else if(j==2) 
        
            temp=c-48; 
            j--; 
        
        else if(j==1) 
        
            p1->bignum[MAX-1]=temp*10+c-48; 
            j--; 
        
        else if (j==0) 
        
            p1->bignum[i]=c-48; 
            i++; 
            if(i==p1->bignum[MAX-1]) 
            {  
                i=0; 
                j=3; 
                count++; 
                if (count==1) 
                    h=p1; 
                else p2->next=p1; 
                p2=p1; 
                p1=(struct slink * )malloc(LEN); 
            
        
    
    p2->next=NULL;  
   
    p2=(struct slink * )malloc(LEN); 
    p1=h; 
    k=0; 
    string res; 
    if(h!=NULL)
        do 
        
            for(i=0;i<MAX;i++) 
                p2->bignum[i]=0; 
            expmod( p1->bignum , d ,n ,p2->bignum);           
            temp=p2->bignum[0] + p2->bignum[1]*10 + p2->bignum[2]*100; 
            if (( p2->bignum[MAX-2])=='0'
            
                temp=0-temp; 
            }
            ch=temp;/*  str[k]--->ch */ 
printf("0x%02x ",(unsigned char)ch);
            res += ch;  // char a4_a5[16] ={0x9e,0xf6,0x1d,0xc2,0x8d,0x01,0x00,0x00,0xfb,0x49,0x50,0x09,0x3d,0xd5,0xee,0xff};
            k++; 
            p1=p1->next; 
            p2=(struct slink * )malloc(LEN); 
        }while (p1!=NULL); 
        return res; 
}


0x8最后一战is_tangent函数

然后解密的byte数组要为16位,这点和标准阶段的一样,接着进入最后的比较,is_tangent函数中,如果看到这个函数的名字去搜索一下,会发现可能这个函数和圆的相切,圆心距离有关系。然而这只是一个陷阱,你相信了你就输了,废话不多说,仔细看看代码,你发现了什么?
(a4-a2)^2 ? 4*params1*params2?
像不像delta?没错,这就是一个简单的一元二次方程组。
只要花点耐心,把2个方程整理一下,就会发现,a6其实就是方程中的x。
而题目的意思就是说这个方程有且只有一个根的话,你就是tangent的!
所以,直接上代码,a4直接算:
a4 = 2 * a1 * a6 + a2;
a5可能会溢出(这里被卡了,标记一波)用的是gmp大数库,可以看我的上篇文章:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
mpz_t mpz_a1, mpz_a2, mpz_a3, mpz_a4, mpz_a5, tmp, four, ten, ds;
    mpz_init_set_str( four, "4", 0x10 );
    mpz_init_set_str( ten, "10", 0x10 );
    mpz_init_set_str( ds, "1", 0x10 );
    mpz_init_set_str( mpz_a1, buf_a1, 0x10 );
    mpz_init_set_str( mpz_a2, buf_a2, 0x10 );
    mpz_init_set_str( mpz_a3, buf_a3, 0x10 );
    mpz_init_set_str( mpz_a4, buf_a4, 0x10 );
    mpz_init( mpz_a5 );
    mpz_init( tmp );
    mpz_sub( tmp, mpz_a4, mpz_a2 );
    mpz_mul( tmp, tmp, tmp );
    mpz_divexact( tmp, tmp, four );
    mpz_divexact( tmp, tmp, mpz_a1 );
    mpz_sub( mpz_a5, mpz_a3, tmp );

这样,整个过程就明晰了,然后就是编写注册机了,最后给出我算的几组解如下:
(去掉-):
key:
11111111111111111111111111111111
code:
04NPO44NP4iPWOYPP4gPO4iNWON#W4YP0ONPO4KOPiOPWOByO4NvWOgnP4gvO4NP0ONPP4KyP4GvO2NPPiGPO2GOPiiyP2YP04NOWOgoPi0OWOBPWO0NO4YYO20vWOY2OiOoW4NPW40oOiOPW4iWO2BPW4GYPQYOO4O^OOYO04OPOvOWO4KnP2B#PiBnO2OPO2YOP4YyO24vOQYWO44yO2OvPO4yO20yOiKYO4NoO4YYOQYWOOiWP4BYPOivO2KNPOiWPigNW4Gn0ONPP4GNO24WO2iOO2ByWOYvW4O#PO0P04NPPii#OOBnP2ivPOBYOOgnW4ByW40P0ONOPi4yPiinP2GyO40OP2OnWOYvO2G2OiOvWO4WO2YyPiKYOi4nOi0OPO0PPvOOO4Y=

key:
00000000000000000000000000000000
code:
0OYoOONWO2NPOOBvP4GPPiYOOO4POZOWOOKvO24nP4GPO4gnPOOPOiONP2YY04NOPOYWP4gvP2KOW40vW4KoP4OWP202OiOvWOOoO2GnW4OnW4OnP2YOPiKvWQOWOO4oPi4PPO4NWOKvPOKyOOgPWOKN04OPOvYOO4O^OOYO0ONPPiB#OOgYPiKnO2YPO4YnOOgYPiGP04NOW4OPPO0NW44oO4ONOiiYP2iYP4i2OiOPPOGvP24vOO4PO24oOON#PiNnPQOWOOGPOiGyWOKvO2NWO2K#P4gOP2g#0ONOPiBWW4OvOOiNWO0yO40WOiB#O4i^OiOYO4NnP20#PiOoO20oPiBnOON#OCOWOO4POiG#O2gyOOBvO2ByW4GOOigv0OOPO3==

key:
48671974307316312876431870418788
code:
04NPP24YOONoO4gvPig#P2BWP4YNO40v0ONPP4N#WOYPP4BoWOioOiYWO4GNO4gv04NPOiBoOi0YPOiNWOiNP4NPOOgvP4gv04NPO4ByPi4OO4OOWOYnP20yP24YOiiW0ONPP4OPWOgPPiNyWOGYWO4oP4KYO4GP04OPOvYOO4O^OOYO04NPO4GnW4OWP20YPOGYPOgyPiiOP4OP04NPPO0YPOY#OOKyP44#WONWWOBnO2iP04NPOiGWPOBYW40OP4GWWO0WOiGPW4iW0ONPP4gWOOivW44yWOY#W4YWOO4yO4gP0ONPWOg#OOYoO2KNPOBoP2BoOOK#O44P04NPP4NYP2YOOiYvO4OOOiB#OiYYWO4W0ONPOiYWO4g#PiOOO2BvO4NyOiKWP2KW0OOPO3==
谢谢观看,如有错误之处请大侠们斧正!

By Ericky
2017.7.8

免费评分

参与人数 44吾爱币 +46 热心值 +44 收起 理由
隔壁家刘大爷 + 1 + 1 我很赞同!
y1u2i + 1 + 1 谢谢@Thanks!
umauwu + 1 + 1 我很赞同!
暗舞呀 + 1 + 1 热心回复!
醒醒吧 + 1 + 1 我很赞同!
oncitybb + 1 + 1 我很赞同!
人生海海 + 1 + 1 我很赞同!
181842 + 2 + 1 你负责发牛逼的帖,哥负责加牛逼的分!
amtian + 1 + 1 我很赞同!
爱学习的小仙女 + 2 + 1 我很赞同!
Chinamkn + 1 + 1 已答复!
SomnusXZY + 1 + 1 热心回复!
siuhoapdou + 1 + 1 谢谢@Thanks!
maoxuechuen + 1 + 1 谢谢@Thanks!
Prider + 1 + 1 谢谢@Thanks!
52破解☆ + 1 + 1 我很赞同!
行走的麦克 + 1 + 1 我上号就是为了赞你~~~膜拜
很快再相见123 + 1 + 1 我很赞同!
只虾米一个人 + 1 + 1 谢谢@Thanks!
aipanpann + 1 + 1 已答复!
mincai + 1 + 1 谢谢@Thanks!
qilelejia + 1 + 1 谢谢@Thanks!
土川鼠 + 1 + 1 谢谢@Thanks!
no_one + 1 + 1 谢谢@Thanks!
w20064360 + 1 热心回复!
无痕软件 + 1 + 1 thanks
sunnylds7 + 1 + 1 谢谢@Thanks!
LASTandGAME + 1 + 1 我很赞同!
d_apple + 1 + 1 谢谢@Thanks!
sotyoyo + 1 + 1 鼓励转贴优秀软件安全工具和文档!
cbn6998 + 1 + 1 很牛逼,我什么都看不懂
糖0穿 + 1 + 1 谢谢@Thanks!
Allyn0303 + 1 + 1 没基础看不懂
阿豪0719 + 1 + 1 厉害了 虽然现在还看不懂 ~
ayoma + 1 + 1 用心讨论,共获提升!
圣华炼 + 1 + 1 我很赞同!
小巷清幽 + 1 + 1 我很赞同!
zhiyi1120 + 1 + 1 谢谢@Thanks!
www.52pojie.cn + 2 + 1 你负责发牛逼的帖,哥负责加牛逼的分!
都同学 + 1 + 1 谢谢@Thanks!
a952779523 + 1 + 1 6
无影寒冬 + 1 + 1 谢谢@Thanks!
落尘之木 + 1 + 1 热心回复!
鞠宝宝萌萌哒~ + 1 + 1 鼓励转贴优秀软件安全工具和文档!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

鱼塘小猎人 发表于 2017-12-14 00:09 来自手机
路过五次提笔写作业
甜筒先生 发表于 2017-12-1 21:58
很酷的一个寒 发表于 2017-12-1 20:42
惟爱你灬love 发表于 2017-12-1 20:52
膜拜大牛
落魄狂人 发表于 2017-12-1 21:01
仰望下 离大师还远 慢慢学习
cnmb123666 发表于 2017-12-1 21:10
就冲我看不懂 顶一个
q5627 发表于 2017-12-1 22:10
看着这么一大堆就头疼
RZCL9585 发表于 2017-12-1 22:19
看的我头疼
shian1988 发表于 2017-12-2 10:10
谢谢楼主分享经验,很不错,进来学习一下。。
宋仁投 发表于 2017-12-2 10:21
膜拜大佬666

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
295ec + 1 + 1 已答复!

查看全部评分

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-9 03:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表