2016腾讯安全挑战赛第一轮-PC游戏方向

0x00 查壳


无壳的VC程序

0x01 测试


没有消息弹窗,尝试对函数下断点。

OD载入后,Ctrl+N查找函数,找到GetDlgItem、

程序运行起来,等输入完后点击确定后程序断下。一路F8就来到这里

00401EED   .  E8 6A5B0000           call Tencent2.00407A5C
00401EF2   .  8B40 20               mov eax,dword ptr ds:[eax+0x20]          ; |
00401EF5   .  8B3D 24334200         mov edi,dword ptr ds:[<&USER32.SendMessa>; |USER32.SendMessageA
00401EFB   .  50                    push eax                                 ; |hWnd
00401EFC   .  FFD7                  call edi                                 ; \SendMessageA
00401EFE   .  8D8C24 A0010000       lea ecx,dword ptr ss:[esp+0x1A0]
00401F05   .  51                    push ecx
00401F06   .  6A 40                 push 0x40
00401F08   .  6A 0D                 push 0xD
00401F0A   .  68 E9030000           push 0x3E9
00401F0F   .  8BCE                  mov ecx,esi
00401F11   .  E8 465B0000           call Tencent2.00407A5C
00401F16   .  8B50 20               mov edx,dword ptr ds:[eax+0x20]
00401F19   .  52                    push edx
00401F1A   .  FFD7                  call edi
00401F1C   .  8D8424 9C000000       lea eax,dword ptr ss:[esp+0x9C]
00401F23   .  C64424 74 D7          mov byte ptr ss:[esp+0x74],0xD7
00401F28   .  C64424 75 A2          mov byte ptr ss:[esp+0x75],0xA2
00401F2D   .  C64424 76 B2          mov byte ptr ss:[esp+0x76],0xB2
00401F32   .  C64424 77 E1          mov byte ptr ss:[esp+0x77],0xE1
00401F37   .  885C24 78             mov byte ptr ss:[esp+0x78],bl
00401F3B   .  885C24 79             mov byte ptr ss:[esp+0x79],bl
00401F3F   .  885C24 7A             mov byte ptr ss:[esp+0x7A],bl
00401F43   .  885C24 7B             mov byte ptr ss:[esp+0x7B],bl
00401F47   .  885C24 7C             mov byte ptr ss:[esp+0x7C],bl
00401F4B   .  C64424 7D CA          mov byte ptr ss:[esp+0x7D],0xCA
00401F50   .  C64424 7E A7          mov byte ptr ss:[esp+0x7E],0xA7
00401F55   .  C64424 7F B0          mov byte ptr ss:[esp+0x7F],0xB0
00401F5A   .  C68424 80000000 DC    mov byte ptr ss:[esp+0x80],0xDC
00401F62   .  C68424 81000000 B3    mov byte ptr ss:[esp+0x81],0xB3
00401F6A   .  C68424 82000000 C9    mov byte ptr ss:[esp+0x82],0xC9
00401F72   .  C68424 83000000 B9    mov byte ptr ss:[esp+0x83],0xB9          ;  这里是填好显示的内容,注册失败,注册成功
00401F7A   .  C68424 84000000 A6    mov byte ptr ss:[esp+0x84],0xA6

这里是将显示内容存起来,等下计算完后使用。
函数在401E60位置,IDA打开程序后查看代码


if ( (unsigned int)(v4 - 6) > 0xE )           // 用户名长度要求
    goto LABEL_24;                           // 注册失败
 ···
 do
 {                                             // 将用户名进行运算
   v6 = v5 % v4;
   v7 = &v44[v5++];
   *v7 += v4 * ((_DWORD)v7 + 20160126 - (_DWORD)v44) * lParam[v6];
 }  
 ···
 sub_401960((int)&v17, v9, (int)&v32); //计算输入的serial
 ···
  if ( v19 - (_DWORD)v18 != 20 )  //这里是由上面的401960算出来的,不满足就注册失败
  {
    v52 = -1;
    sub_4022C0(&v17);
LABEL_24:
    v14 = 0;
    goto LABEL_25;
  }
 ···
 do
  {
    v12 = *(_DWORD *)&v44[v11] / 10;            // 这里是将上面的用户名运算结果看作是有符号数,然后除以10
    v13 = v19 - (_DWORD)v10;
    *(int *)((char *)&v21 + v11) = v12;
    if ( v11 >= v13 )
    {
      _invalid_parameter_noinfo(v12);
      v10 = (char *)v18;
    }
    *(int *)((char *)&v26 + v11) = *(_DWORD *)&v10[v11]; //这里是serial算出来的值
    v11 += 4;
  }
  while ( v11 < 20 );
  if ( v30 + v21 != v28 || v28 + v22 != 2 * v30 || v29 + v23 != v26 || v26 + v24 != 2 * v29 || v25 + v27 != 3 * v23 )// 这里比较是否满足条件,满足条件就成功

这样大致可以看出程序的流程了。

Created with Raphaël 2.2.0 开始 输入用户名 长度是否小于21且大于6? 对用户名进行运算 调用sub_401960 比较计算serial结果长度是否等于20? 计算出来的serial结果是否满足条件 显示注册成功 结束 显示注册错误 yes no yes no yes no

具体流程得出后,可以逐一分析了。

0x02分析

1 对用户名进行运算

 do
 {                                             // 将用户名进行运算
   v6 = v5 % v4;                  //v4是用户名长度
   v7 = &v44[v5++];
   *v7 += v4 * ((_DWORD)v7 + 20160126 - (_DWORD)v44) * lParam[v6];
 }
 while ( v5 < 16 );

这个v7的地址-v44的地址等于v5,故可以转化为

*v7 += v4*(20160126+v5)*lParam[v6];

2 调用sub_401960

···   //省略
 v8 = v14 + 1;
 v32 = v15 + 1;
 v31 = v8;
 if ( v15 == 3 )  //意味着每4个字符就在进入这个判断里进行运算
      {
        v16 = 0;
        do
        {
          v26 = *((_BYTE *)&v30 + v16);
          *((_BYTE *)&v30 + v16++) = sub_402420(&v26);// 计算字符在42E040表中的位置
        }
        while ( v16 < 4 );
        v27 = 4 * v30 + ((BYTE1(v30) >> 4) & 3);
        v17 = BYTE2(v30) << 6;
        v28 = 16 * BYTE1(v30) ^ HIBYTE(v17) & 0xF;
        v29 = BYTE3(v30) + v17;
        v18 = 0;
···//省略
//如果说输入的serial长度不为4的倍数,那就到这里来继续进行运算
 do
      {
        v26 = *((_BYTE *)&v30 + v21);
        *((_BYTE *)&v30 + v21++) = sub_402420(&v26);
      }
      while ( v21 < 4 );
      v27 = 4 * v30 + ((BYTE1(v30) >> 4) & 3);
      v29 = BYTE3(v30) + (BYTE2(v30) << 6);
      result = v32 - 1;
      v22 = 0;
      v28 = 16 * BYTE1(v30) ^ (BYTE2(v30) >> 2) & 0xF;

这个也很好理解
看到v27,v28,v29就可以看出这里的运算时将输入的serial每4个字符转成3个值。

3 比较serial长度
刚刚说到是将serial的每4个字符转为3个值,所以说当转化的值的个数为20个时才允许继续往下,测试几次就知道是只有当输入的serial长度为27时,转化得到的个数才为20

4 满足条件比较
注意到输入的用户名需要进行两次运算,第一是计算出值。第二次是将这些值除以10
这里假设对用户名进行两次运算后储存在U中,而对serial计算的结果储存在K中,当满足下面条件时才能注册成功

  1. U 1 U_1 U1 + K 5 K_5 K5 == K 3 K_3 K3
  2. U 2 U_2 U2 + K 3 K_3 K3 == 2 K 5 2K_5 2K5
  3. K 4 K_4 K4 + U 3 U_3 U3 == K 1 K_1 K1
  4. U 4 U_4 U4 + K 1 K_1 K1 == 2 K 4 2K_4 2K4
  5. U 5 U_5 U5 + K 2 K_2 K2 == 3 U 4 3U_4 3U4

变换一下位置,就能通过U来计算出K了,然后可以枚举一下所有可能,也就是0-63,就能找出满足K的字符串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值