二娃 发表于 2019-6-10 19:37

2019腾讯游戏安全竞赛PC版分析

本帖最后由 二娃 于 2020-9-2 19:48 编辑

不知道发生了啥,图片都看不了了,有兴趣可以移步博客
首先感谢littleNA大佬的文章<https://bbs.pediy.com/thread-230312.htm>,看完这篇文章后事半功倍。

# 第一题 EasyJob

首先为了方便动静结合调试,我先修改了PE文件头部使之跳过重定位。使用工具DIE

![](http://wx4.sinaimg.cn/mw690/006juYZNly1g3u77afrstj30ha0bh751.jpg)

这样修改之后,使用ida和其他调试工具的时候地址就能对得上了,告别aslr。

用ida打开程序后,看一下`main`函数,确定`sub_45F650`和`sub_460050`分别为检查`request code`和`verification code`的函数,做一下重命名。

![](http://wx1.sinaimg.cn/large/006juYZNly1g3u7sgxif2j30ob0dkab5.jpg)

值得注意的是`check_request_code`和`check_verification_code`有同一个参数,我把它命名为`req_code_stru`。在后面的分析过程中,我得出该变量类型的结构体

```c
struct req_code_stru
{
vector request_code_part;
__int64 num1;
__int64 num2;
__int64 num3;
__int64 num4;
};
```

其中`vector`结构可以参考上面littleNA大佬的文章,在这个结构体中应该是`vector<string>`,每个成员的作用会在后面讲到。

进入`check_request_code`函数后,发现字符串

![](http://wx3.sinaimg.cn/large/006juYZNly1g3u7txwhw6j30kk01bq2t.jpg)

很明显是一个正则表达式,于是推测`request code`需要符合该正则表达式。打开程序随便输入了一个符合该表达式的`request code`,比如`1111-2222-3333-4444`,输入后程序提示继续输入`verification code`,表明推测是正确的。通过调试后还发现该函数会把`request code`通过字符`-`分成4个`string`插入到`req_code_stru->request_code_part`中。

接下来看一下`check_vertification_code`。通过浏览后发现一个比较关键的函数`sub_45F960`,通过`req_code_stru->request_code_part`计算出4个`num`,具体代码如下

```c
int __thiscall sub_45F960(req_code_stru *req_code_stru)
{
char *v1_part1; // esi
char *v2_part1; // eax
char *v3_part4; // ebx
char *v4_part4; // ecx
int v5; // edi
char *v6_part1; // ecx
char *v7_part4; // eax
int v8; // edi
char *v9_part1; // edx
char *v10_part4; // eax
int v11; // edi
char *v12_part1; // edx
char *v13_part4; // eax
char *v14_part2; // eax
char *v15_part3; // edi
char *v16_part3; // ecx
char *v17_part2; // edx
char *v18_part3; // eax
char *v19_part2; // edx
char *v20_part3; // eax
char *v21_part1; // edx
char *v22_part3; // eax
char *v23_part1; // edx
char *v24_part1; // eax
char *v25_part2; // eax
char *v26_part2; // edx
char *v27_part3; // ecx
char *v28_part3; // eax
int v29; // ecx
char *v30_part4; // edx
char *v31_part4; // eax
char *v32_part1; // ecx
char *v33_part4; // eax
char *v34_part2; // edx
char *v35_part3; // eax
char *v36_part2; // eax
int v37; // eax
int v38; // edx
int v39; // ST24_4
int v40; // ecx
__int64 v41; // rdi
bool v42; // cf
unsigned __int64 v43; // rt0
__int64 v44; // kr38_8
__int64 v45; // rax
unsigned int v46; // ecx
unsigned int v47; // ecx
signed __int64 v48; // rax
int v49; // ecx
req_code_stru *v51_reg_code_stru; //
int v52; //
int v53; //
int v54; //
int v55; //
int v56; //
int v57; //
int v58; //
int v59; //
int v60; //
int v61; //

v1_part1 = req_code_stru->request_code_part._Myfirst;
v51_reg_code_stru = req_code_stru;
if ( *((_DWORD *)req_code_stru->request_code_part._Myfirst + 5) < 0x10u )
    v2_part1 = req_code_stru->request_code_part._Myfirst;
else
    v2_part1 = *(char **)v1_part1;
v3_part4 = v1_part1 + 72;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v4_part4 = v1_part1 + 72;
else
    v4_part4 = *(char **)v3_part4;
v5 = (*v4_part4 ^ *v2_part1) << 8;            // v5=(part4^part1)<<8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v6_part1 = v1_part1;
else
    v6_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v7_part4 = v1_part1 + 72;
else
    v7_part4 = *(char **)v3_part4;
v8 = v6_part1 * v7_part4 + v5;          // v8=part1*part4+v5
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v9_part1 = v1_part1;
else
    v9_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v10_part4 = v1_part1 + 72;
else
    v10_part4 = *(char **)v3_part4;
v11 = v9_part1 / v10_part4 + v8;      // v11=part1/part4+v8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v12_part1 = v1_part1;
else
    v12_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v13_part4 = v1_part1 + 72;
else
    v13_part4 = *(char **)v3_part4;
v54 = v11 + v12_part1 % v13_part4;      // v54=part1%part4+v11
v14_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
    v14_part2 = *(char **)v14_part2;
v15_part3 = v1_part1 + 48;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v16_part3 = v1_part1 + 48;
else
    v16_part3 = *(char **)v15_part3;
v56 = *v16_part3 * *v14_part2 << 8;         // v56=part3*part2<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
    v17_part2 = v1_part1 + 24;
else
    v17_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v18_part3 = v1_part1 + 48;
else
    v18_part3 = *(char **)v15_part3;
v57 = (char)(v17_part2 ^ v18_part3) + v56;// v57=(part2^part3)+v56
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
    v19_part2 = v1_part1 + 24;
else
    v19_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v20_part3 = v1_part1 + 48;
else
    v20_part3 = *(char **)v15_part3;
v58 = v19_part2 % v20_part3 + 32 + v57; // v58=part2%part3+32+v57
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v21_part1 = v1_part1;
else
    v21_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v22_part3 = v1_part1 + 48;
else
    v22_part3 = *(char **)v15_part3;
v55 = v58 + v21_part1 / v22_part3;      // v55=part1/part3+v58
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v23_part1 = v1_part1;
else
    v23_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v24_part1 = v1_part1;
else
    v24_part1 = *(char **)v1_part1;
v59 = v23_part1 % v24_part1 << 8;       // v59=part1%part1<<8
v25_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
    v26_part2 = v1_part1 + 24;
else
    v26_part2 = *(char **)v25_part2;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
    v25_part2 = *(char **)v25_part2;
v60 = v26_part2 / v25_part2 + v59;      // v60=part2/part2+v59
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v27_part3 = v1_part1 + 48;
else
    v27_part3 = *(char **)v15_part3;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v28_part3 = v1_part1 + 48;
else
    v28_part3 = *(char **)v15_part3;
v29 = v27_part3 * v28_part3 + 8 + v60;// v29=part3*part3+8+v60
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v30_part4 = v1_part1 + 72;
else
    v30_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v31_part4 = v1_part1 + 72;
else
    v31_part4 = *(char **)v3_part4;
v61 = v29 + (char)(v30_part4 ^ v31_part4);// v61=(part4^part4)+v29
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
    v32_part1 = v1_part1;
else
    v32_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
    v33_part4 = v1_part1 + 72;
else
    v33_part4 = *(char **)v3_part4;
v52 = (*v32_part1 ^ v33_part4) << 8;       // v52=(part1^part4)<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
    v34_part2 = v1_part1 + 24;
else
    v34_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
    v35_part3 = v1_part1 + 48;
else
    v35_part3 = *(char **)v15_part3;
v53 = v34_part2 / v35_part3 + v52;      // v53=part2/part3+v52
if ( *((_DWORD *)v1_part1 + 17) >= 0x10u )
    v15_part3 = *(char **)v15_part3;
v36_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
    v36_part2 = *(char **)v36_part2;
v37 = v15_part3 * v36_part2;            // v37=part3*part2
if ( *((_DWORD *)v1_part1 + 23) >= 0x10u )
    v3_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 5) >= 0x10u )
    v1_part1 = *(char **)v1_part1;
v38 = v37 + v53 + v3_part4 % *v1_part1;    // v38=part4%part1+v37+v53
v39 = v38;
v40 = (unsigned __int64)(unsigned int)v38 >> 29;
LODWORD(v41) = 20 * (v54 + 2 * v61);          // v41=20*(v54+2*v61)
v42 = __CFADD__(8 * v38, v38);
v38 *= 9;
HIDWORD(v43) = v40 + v42;
LODWORD(v43) = v38;
HIDWORD(v41) = v43 >> 29;
v51_reg_code_stru->num1 = 30i64 * (unsigned int)v55
                        + __PAIR__(20 * ((unsigned int)v54 + 2 * (unsigned __int64)(unsigned int)v61) >> 32, 8 * v38)
                        + v41;                // num1=30*v55+20*(v54+2*v61)+72*v38
v51_reg_code_stru->num2 = 23i64 * (unsigned int)v54
                        + 32i64 * (unsigned int)v55
                        + 42i64 * (unsigned int)v61
                        + 54i64 * (unsigned int)v39;// num2=23*v54+32*v55+42*v61+54*v39
LODWORD(v41) = 25 * v55 + 38 * v61 + 67 * v39;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 29) + __CFADD__(8 * v54, v54);
LODWORD(v43) = 9 * v54;
HIDWORD(v41) = v43 >> 31;
v44 = __PAIR__(
          (25i64 * (unsigned int)v55 + 38i64 * (unsigned int)v61 + 67 * (unsigned __int64)(unsigned int)v39) >> 32,
          18 * v54)
      + v41;
HIDWORD(v41) = v51_reg_code_stru;
v51_reg_code_stru->num3 = v44;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 31) + __CFADD__(2 * v54, v54);
LODWORD(v43) = 3 * v54;
LODWORD(v41) = (__PAIR__(v43 >> 30, 12 * v54)
                + 45i64 * (unsigned int)v55
                + 33i64 * (unsigned int)v39
                + ((unsigned __int64)(unsigned int)v61 << 6)) >> 32;
v45 = SHIDWORD(v51_reg_code_stru->num1);
v46 = HIDWORD(v45) ^ LODWORD(v51_reg_code_stru->num1);
v42 = v46 < HIDWORD(v45);
LODWORD(v51_reg_code_stru->num1) = v46 - HIDWORD(v45);
v47 = v51_reg_code_stru->num2;
HIDWORD(v51_reg_code_stru->num1) = (HIDWORD(v45) ^ v45) - (v42 + HIDWORD(v45));
*(_QWORD *)(HIDWORD(v41) + 24) = __PAIR__(
                                     (*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 28),
                                     (*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ v47)
                                 - __PAIR__(*(_DWORD *)(HIDWORD(v41) + 28) >> 31, *(_DWORD *)(HIDWORD(v41) + 28) >> 31);
v51_reg_code_stru->num3 = (signed __int64)(__PAIR__(
                                             (*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 36),
                                             (*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 32))
                                           - __PAIR__(
                                             *(_DWORD *)(HIDWORD(v41) + 36) >> 31,
                                             *(_DWORD *)(HIDWORD(v41) + 36) >> 31))
                        % 20;               // num3=(25*v55+38*v61+67*v39+18*v54)%20
v48 = (signed __int64)(__PAIR__(
                           ((signed int)v41 >> 31) ^ (unsigned int)v41,
                           ((signed int)v41 >> 31) ^ (unsigned int)(12 * v54 + 45 * v55 + 33 * v39 + (v61 << 6)))
                     - __PAIR__((signed int)v41 >> 31, (signed int)v41 >> 31))
      % 20;                                     // num4=(12*v54+45*v55+33*v39+v61*64)%20
v51_reg_code_stru->num4 = v48;
if ( SHIDWORD(v51_reg_code_stru->num3) >= SHIDWORD(v48) )
{
    if ( SHIDWORD(v51_reg_code_stru->num3) > SHIDWORD(v48)// 如果num3>num4就互相交换
      || (LODWORD(v48) = v51_reg_code_stru->num3, (unsigned int)v48 > LODWORD(v51_reg_code_stru->num4)) )
    {
      v49 = v51_reg_code_stru->num3;
      HIDWORD(v48) = HIDWORD(v51_reg_code_stru->num3);
      LODWORD(v51_reg_code_stru->num3) = v51_reg_code_stru->num4;
      LODWORD(v48) = HIDWORD(v51_reg_code_stru->num4);
      HIDWORD(v51_reg_code_stru->num3) = v48;
      LODWORD(v51_reg_code_stru->num4) = v49;
      HIDWORD(v51_reg_code_stru->num4) = HIDWORD(v48);
    }
}
return v48;
}
```

F5后看起来很乱,需要结合汇编一起分析。

接下来在`sub_45FE00`计算出`num5`

![](http://wx1.sinaimg.cn/large/006juYZNly1g3ur90h0hgj30ue0h9q46.jpg)

`num5`等于`request_code`每个数字相加,比如`1111-2222-3333-4444`计算出来的`num5`就是40

接下来回到`check_request_code`中把得到的4个`part`和5个`num`拼接

![](http://wx4.sinaimg.cn/large/006juYZNly1g3urs0u7v6j30jq069753.jpg)

这里看F5可能看不太懂,可以看汇编

![](http://wx3.sinaimg.cn/large/006juYZNly1g3urslzjx5j313g0fjtbe.jpg)

这里是先把4个`operator<<`的参数全部压栈再一个个call,应该是编译器优化的结果。得到的结果就是一个字符串`s=part1+part2+part3+part4+num1+num2+num3+num4+num5`

最后将得到的字符串再做一个以`part1`为`key`的`hmac_sha256`计算

![](http://wx4.sinaimg.cn/large/006juYZNly1g3ushcmmntj30hv03a3yn.jpg)

因为用ida看了字符串发现`OpenSSL 1.0.2m2 Nov 2017`的字样所以事先用(https://github.com/fireundubh/IDA7-Rizzo)打上了`OpenSSL`的符号。关于`OpenSSL`编译可以看<https://cloud.tencent.com/developer/article/1343632>。不过遗憾的是打上了符号也没办法识别出这个`hmac`使用的`hash`算法是什么,后来我是通过动态调试获得一组输入和输出暴力跑出了`hash`为`sha256`,做完`hmac_sha256`后得到的结果即为`verification_code`。

附上`python3`注册机代码

```python
import re
import hmac

def hmac_sha256(key,s):
    return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()

def get_verification_code(part1,part2,part3,part4):
    v5=(ord(part4)^ord(part1))<<8
    v8=ord(part1)*ord(part4)+v5
    v11=ord(part1)//ord(part4)+v8
    v54=ord(part1)%ord(part4)+v11
    v56=ord(part3)*ord(part2)<<8
    v57=(ord(part2)^ord(part3))+v56
    v58=ord(part2)%ord(part3)+32+v57
    v55=ord(part1)//ord(part3)+v58
    v59=ord(part1)%ord(part1)<<8
    v60=ord(part2)//ord(part2)+v59
    v29=ord(part3)*ord(part3)+8+v60
    v61=(ord(part4)^ord(part4))+v29
    v52=(ord(part1)^ord(part4))<<8
    v53=ord(part2)//ord(part3)+v52
    v37=ord(part3)*ord(part2)
    v38=ord(part4)%ord(part1)+v37+v53
    v39=v38
    v38=v38*9
    v41=12*v54+45*v55+33*v39
    num1=30*v55+20*(v54+2*v61)+8*v38
    num2=23*v54+32*v55+42*v61+54*v39
    num3=(25*v55+38*v61+67*v39+18*v54)%20
    num4=(12*v54+45*v55+33*v39+v61*64)%20
    if num3>num4:
      num3,num4=num4,num3
    num5=0
    for i in range(4):
      num5+=int(part1)+int(part2)+int(part3)+int(part4)
    code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)
    return hmac_sha256(part1,code)

if __name__=='__main__':
    request_code=input('input request code: ')
    reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
    if reobj!=None:
      part1=reobj.group(1)
      part2=reobj.group(2)
      part3=reobj.group(3)
      part4=reobj.group(4)
      print(get_verification_code(part1,part2,part3,part4))
    else:
      print('[+] Invalid code!')
```

![](http://wx4.sinaimg.cn/large/006juYZNly1g3usl0ix7aj30xz0hrmxz.jpg)

# 第二题 Rotate

第二题和第一题相差不大,不同的地方是多了一个`num6`以及`hmac_sha256`的`key`不同。看一下导入表,发现一些`opencv`的函数

![](http://wx1.sinaimg.cn/mw690/006juYZNly1g3usq7bc6qj30hg06smyc.jpg)

为了方便分析,去抠了一下`cv::Mat`的结构体。

```c
struct cvMat
{
int flags;
int dims;
int rows;
int cols;
unsigned __int8 *data;
unsigned __int8 *datastart;
unsigned __int8 *dataend;
unsigned __int8 *datalimit;
void *allocator;
void *u;
int *size_p;
unsigned __int64 *step_p;
unsigned __int64 step_buf;
};

```

其中`MatStep`和`MatSize`被我拆了,影响不是很大。

首先在`check_verification_code`中使用`cv::imread`读取了题目目录下的`flag.jpg`

![](http://wx2.sinaimg.cn/large/006juYZNly1g3uswxb4pej30on0b0dh1.jpg)

这里应该是个`cv::Mat`构造函数,被编译器优化成内联函数了。

接下来在`sub_14007A2C0`中进行图像的旋转。

![](http://wx3.sinaimg.cn/large/006juYZNly1g3uu0o35avj30lt0hhq47.jpg)

通过`cv::getRotationMatrix2D`获得旋转的矩阵再用`cv::warpAffine`进行仿射变换。

在`sub_140079C50`进行图像的处理并计算`num6`,首先是图像的处理

![](http://wx1.sinaimg.cn/large/006juYZNly1g3uu6sstphj30xa03eglv.jpg)

![](http://wx4.sinaimg.cn/large/006juYZNly1g3uu6vplnij30kx08xq3o.jpg)

分别调用了`cv::cvtColor` `cv::resize`和`cv::dct`

然后根据图像的数据计算`num6`

```c
v9 = *Dst.step_p;                           // 一行元素的字节数
v10 = &v45;
v11 = (double *)(Dst.data + 24);
do
{
    v12 = *(v11 - 3);
    *((double *)v10 - 1) = v12;
    v13 = *(v11 - 2);
    *(double *)v10 = v13;
    v14 = *(v11 - 1);
    *((double *)v10 + 1) = v14;
    v15 = *v11;
    v10 = *(_QWORD *)v11;
    v16 = v11;
    *((double *)v10 + 3) = v16;
    v17 = v11;
    *((double *)v10 + 4) = v17;
    v18 = v11;
    *((double *)v10 + 5) = v18;
    v19 = v11;
    *((double *)v10 + 6) = v19;
    v5 = v12 * 0.015625                         // 图像数据的平均数
       + v5
       + v13 * 0.015625
       + v14 * 0.015625
       + v15 * 0.015625
       + v16 * 0.015625
       + v17 * 0.015625
       + v18 * 0.015625
       + v19 * 0.015625;
    v10 += 8;
    v11 = (double *)((char *)v11 + v9);         // 下一行
    --v8;
}
while ( v8 );                                 // 8次
v20 = 0i64;
do                                          // 图像每一点的数据与平均数对比
                                                // 如果数值小于平均数则为0否则为1
                                                // 得到一个长度为64格式为二进制的字符串
{
    if ( *((double *)&v44 + v20) < v5 )
    {
      if ( v4->_Myres < 0x10 )
      v22 = v4;
      else
      v22 = *(string **)v4->_Buf;
      v22->_Buf = '0';
    }
    else
    {
      if ( v4->_Myres < 0x10 )
      v21 = v4;
      else
      v21 = *(string **)v4->_Buf;
      v21->_Buf = '1';
    }
    if ( *((double *)&v45 + v20) < v5 )
    {
      if ( v4->_Myres < 0x10 )
      v24 = v4;
      else
      v24 = *(string **)v4->_Buf;
      v24->_Buf = '0';
    }
    else
    {
      if ( v4->_Myres < 0x10 )
      v23 = v4;
      else
      v23 = *(string **)v4->_Buf;
      v23->_Buf = '1';
    }
    if ( *((double *)&v46 + v20) < v5 )
    {
      if ( v4->_Myres < 0x10 )
      v26 = v4;
      else
      v26 = *(string **)v4->_Buf;
      v26->_Buf = '0';
    }
    else
    {
      if ( v4->_Myres < 0x10 )
      v25 = v4;
      else
      v25 = *(string **)v4->_Buf;
      v25->_Buf = '1';
    }
    if ( *(double *)&v47 < v5 )
    {
      if ( v4->_Myres < 0x10 )
      v28 = v4;
      else
      v28 = *(string **)v4->_Buf;
      v28->_Buf = '0';
    }
    else
    {
      if ( v4->_Myres < 0x10 )
      v27 = v4;
      else
      v27 = *(string **)v4->_Buf;
      v27->_Buf = '1';
    }
    v20 += 4i64;
}
while ( v20 < 64 );                           // 总共有8*8=64个数
```

得到的长度为64的字符串即为`num6`

最后拼接`part`和`num`得到字符串`s=part1+part2+part3+part4+num1+num2+num3+num4+num5+num6`再以`num6`为`key`做`hmac_sha256`得到`verification code`,相关代码与第一题一致。

附上`python3`注册机

```python
import re
import hmac
import cv2
import numpy as np

def hmac_sha256(key,s):
    return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()

def get_num6(part1,part2,part3,part4):
    img=cv2.imread('.\\flag.jpg',cv2.IMREAD_COLOR)
    center=(img.shape*0.5,img.shape*0.5)
    angle=0
    for i in range(4):
      angle+=int(part1)+int(part2)+int(part3)+int(part4)
    scale=1.0
    trans=cv2.getRotationMatrix2D(center,angle,scale)
    dsize=(img.shape,img.shape)
    img=cv2.warpAffine(img,trans,dsize)
    img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    img=np.float64(img)
    dsize=(8,8)
    img=cv2.resize(img,dsize)
    img=cv2.dct(img)
    total=0
    for i in img:
      for j in i:
            total+=j
    average=total/64
    num6=''
    for i in img:
      for j in i:
            if j<average:
                num6+='0'
            else:
                num6+='1'
    return num6
   

def get_verification_code(part1,part2,part3,part4):
    v5=(ord(part4)^ord(part1))<<8
    v8=ord(part1)*ord(part4)+v5
    v11=ord(part1)//ord(part4)+v8
    v54=ord(part1)%ord(part4)+v11
    v56=ord(part3)*ord(part2)<<8
    v57=(ord(part2)^ord(part3))+v56
    v58=ord(part2)%ord(part3)+32+v57
    v55=ord(part1)//ord(part3)+v58
    v59=ord(part1)%ord(part1)<<8
    v60=ord(part2)//ord(part2)+v59
    v29=ord(part3)*ord(part3)+8+v60
    v61=(ord(part4)^ord(part4))+v29
    v52=(ord(part1)^ord(part4))<<8
    v53=ord(part2)//ord(part3)+v52
    v37=ord(part3)*ord(part2)
    v38=ord(part4)%ord(part1)+v37+v53
    v39=v38
    v38=v38*9
    v41=12*v54+45*v55+33*v39
    num1=30*v55+20*(v54+2*v61)+8*v38
    num2=23*v54+32*v55+42*v61+54*v39
    num3=(25*v55+38*v61+67*v39+18*v54)%20
    num4=(12*v54+45*v55+33*v39+v61*64)%20
    if num3>num4:
      num3,num4=num4,num3
    num5=0
    for i in range(4):
      num5+=int(part1)+int(part2)+int(part3)+int(part4)
    num6=get_num6(part1,part2,part3,part4)
    code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)+num6
    return hmac_sha256(num6,code)

if __name__=='__main__':
    request_code=input('input request code: ')
    reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
    if reobj!=None:
      part1=reobj.group(1)
      part2=reobj.group(2)
      part3=reobj.group(3)
      part4=reobj.group(4)
      print(get_verification_code(part1,part2,part3,part4))
    else:
      print('[+] Invalid code!')
```

![](http://wx3.sinaimg.cn/large/006juYZNly1g3uutyjvfyj30xz0hr0tt.jpg)

# 第三题 Invisiable

该题目录下的文件

![](http://wx2.sinaimg.cn/large/006juYZNly1g3w5vevb65j30kl05mjrm.jpg)

打开`Invisiable.exe`,首先看到有个`tlscallback`反调试

![](http://wx2.sinaimg.cn/large/006juYZNly1g3x2g76xzyj30oi0csjsa.jpg)

通过`NtQueryInformationProcess`查了`DebugPort`,如果程序被调试了这个`DebugPort`就不为0。检测到没有被调试后程序修改了某个函数,把这个函数填充成`nop`。当然这个反调试没什么影响,因为我用了`SharpOD`插件,所以基本不用管。

看一下`main`中的主要部分

![](http://wx2.sinaimg.cn/large/006juYZNly1g3w5hlq1igj30t20brgmd.jpg)

验证输入的函数并不是直接调用的,ida看起来也不方便,所以我选择通过动态调试获取了3个step的验证输入的函数

## first step

在`sub_404180`中判断`number-2019000`是否为水仙花数。

![](http://wx1.sinaimg.cn/large/006juYZNly1g3w5qmn35gj30ka0hf3zc.jpg)

如果是的话就就执行接下来的操作

![](http://wx4.sinaimg.cn/large/006juYZNly1g3w6loz1j4j30vy09h755.jpg)

读取题目目录下的`invisiable.mp3`文件数据,然后通过`sub_401C20`解密得到真实的文件`decode.py`

解密函数`sub_401C20`如下

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w69a2iw8j30lh0dtq3p.jpg)

将原文件数据的每个字节与字符串`ReverseEngineerIsEasy`的每个字符循环异或,得到真实文件数据,然后写回硬盘上。

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w6gew29tj30gw01omx3.jpg)

然后对目录下的文件`flag`也做了同样的事情,解密后得到`nice.png`文件。

![](http://wx4.sinaimg.cn/large/006juYZNly1g3w6izwezpj30pi0badgh.jpg)

最后做了一个谜之操作,修改了`main.dll`

![](http://wx2.sinaimg.cn/large/006juYZNly1g3w6q5osryj30ia06d0sz.jpg)

看了一下`main.dll`偏移`0x46c`处正是它的导出函数的位置,这里修改成了`ret C`。

到这里first step就结束了

## second step

首先判断了一下输入

![](http://wx1.sinaimg.cn/large/006juYZNly1g3w6uvsyvjj30qm01c748.jpg)

如果符合条件就执行接下来的操作

读取目录下`1.txt`的数据

![](http://wx2.sinaimg.cn/large/006juYZNly1g3w6yuup2sj30h601xjrd.jpg)

然后判断文本内容

![](http://wx4.sinaimg.cn/large/006juYZNly1g3w76nfs8bj30rt07e74v.jpg)

也就是说`1.txt`的文本内容格式需要为`xxxx-xxxx`,`part1`长度暂时未知。

然后对这两个`part`进行了和first step解密文件很像的操作,`buf1`和`buf2`分别为`part1`和`part2`

![](http://wx1.sinaimg.cn/large/006juYZNly1g3w7dvkm22j30q40anaai.jpg)

然后将得到的结果做`base64encode`计算,得到的结果需要是`VFxQXkljVFo=`

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w7i85sohj310h07raau.jpg)

将`VFxQXkljVFo=`做`base64decode`后得到`T\P^IcTZ`,也就是说我们的`part1`长度必须为8。

然后就是写脚本跑一个符合该要求的输入出来了

```python
def xor_asc(l):
    s='T\\P^IcTZ'
    a=
    b=
    for i in range(8):
      b=a^l
    return b

def inc_asc(l):
    for i in range(len(l)):
      l+=1
      if l==ord('-'):
            l+=1
      if l>126:
            l=33
      else:
            break
    return l

def check_asc(l):
    for i in l:
      if i>126 or i<33:
            return False
    return True

def create_txt():
    part1=[]
    part2=
    while(True):
      part1=xor_asc(part2)
      if check_asc(part1):
            s1=''.join()
            s2=''.join()
            s=s1+'-'+s2
            print(s)
      part2=inc_asc(part2)
      

if __name__=='__main__':
    create_txt()
    #u}6uhB2q-!!f+
```

为了方便输入,所以只取了可视的ascii字符(不包括空格)。这个脚本不会停,所以请自行`ctrl+c`强制结束。随便取了一个结果`u}6uhB2q-!!f+`保存到`1.txt`后,再输入一个符合要求的`number`,second step就结束了。

## third step

在`sub_404220`中对输入的`number`进行了验证

![](http://wx3.sinaimg.cn/large/006juYZNly1g3xjkrzb7uj30kd0k7myd.jpg)

需要输入一个长度为6的数字,然后每两个为一组拆成3个2位数,这3个数需要符合一个方程,解的结果为`744110`。

输入正确结果后,程序加载`main.dll`

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w9joxm5mj30ip0b7dgg.jpg)

如果仅仅是这样,那么恭喜,程序崩溃。

还记得在first step中修改了`main.dll`的导出函数吗?这就是程序崩溃的原因。为了能够得到正确结果,在third step输入数字前需要把未修改的`main.dll`替换回去。最后符合题目要求,程序输出了flag。

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w9jrwafuj30xz0hr0tj.jpg)

但是我感觉这里过于诡异,所以不知道算不算正解。又看了一遍题目,要求输出的flag形式为`flag{xxxxxxx}`。。。也就是说`1.txt`中`part1`的前五位是可以确定的,但是我不想改了,就这样吧。

还有一个诡异的地方就是,first step中创建的`decode.py`和`nice.png`在后面没用到,贴一下`decode.py`的代码

```python
# coding=utf-8
import cv2
import numpy as np
import random
import os
from argparse import ArgumentParser
ALPHA = 5

def build_parser():
    parser = ArgumentParser()
    parser.add_argument('--original', dest='ori', required=True)
    parser.add_argument('--image', dest='img', required=True)
    parser.add_argument('--result', dest='res', required=True)
    parser.add_argument('--alpha', dest='alpha', default=ALPHA)
    return parser


def main():
    parser = build_parser()
    options = parser.parse_args()
    ori = options.ori
    img = options.img
    res = options.res
    alpha = float(options.alpha)
    if not os.path.isfile(ori):
      parser.error("original image %s does not exist." % ori)
    if not os.path.isfile(img):
      parser.error("image %s does not exist." % img)
    decode(ori, img, res, alpha)


def decode(ori_path, img_path, res_path, alpha):
    ori = cv2.imread(ori_path)
    img = cv2.imread(img_path)
    ori_f = np.fft.fft2(ori)
    img_f = np.fft.fft2(img)
    height, width = ori.shape, ori.shape
    watermark = (ori_f - img_f) / alpha
    watermark = np.real(watermark)
    res = np.zeros(watermark.shape)
    random.seed(height + width)
    x = range(height / 2)
    y = range(width)
    random.shuffle(x)
    random.shuffle(y)
    for i in range(height / 2):
      for j in range(width):
            res]] = watermark
    cv2.imwrite(res_path, res, )


if __name__ == '__main__':
    main()

```

这就超出我的能力范围了(怎么还有快速傅里叶变换呢)。试着用了一下这个脚本

```cmd
py -2 decode.py --original nice.png --image out.png --result res.png
```

得到图片

![](http://wx1.sinaimg.cn/large/006juYZNly1g3w9vos38rj30lo09y77e.jpg)

`nice.png`和`out.png`分别为

![](http://wx1.sinaimg.cn/large/006juYZNly1g3w9w08uolj30lo09y74a.jpg)

![](http://wx3.sinaimg.cn/large/006juYZNly1g3w9w9l0xmj30lo09yqa4.jpg)

看了这几张图,总感觉第三题还有一点门路。然而小弟能力有限,只能到这了,希望大佬们能指导指导。


题目太大,上传不了,有兴趣的可以自行去下载。

kikkki 发表于 2019-7-4 23:49

膜拜大神,小白我看来这辈子都学不会。。太强大了。去培训了4个月的JAVA,到头来,连最基本的增删改都不会。我这猪脑,啥时候能变得和楼主一样优秀,有一门拿得出手的技术。

二娃 发表于 2019-9-30 19:58

こ_浅唱沵给旳恐 发表于 2019-9-30 00:21
怎么看出来 那里就是串文本.....

IDA直接按A转出来的很奇怪.....

这个仔细看看就知道是这串文本了 按A很奇怪是因为这里不是整个字符串

第三题解完了 但是我这里其实不是正解
需要用到那个解码出来的图片上的数字2019作为一个xorkey 我当时解完了都还不知道这个2019是干什么的 后来整明白了 不过这篇文章就没改了 具体可以看https://www.52pojie.cn/thread-908932-1-1.html底下的回复

二娃 发表于 2019-6-10 19:42

所以为啥字体最后变成斜的了呢。。。

Checkon 发表于 2019-6-10 19:58

完全就根本
看不懂

不诉离殇 发表于 2019-6-10 19:59

完全看不懂。。

57558938 发表于 2019-6-10 20:06

看不懂+1

二娃 发表于 2019-6-10 20:09

对不起我的表达能力太差了。如果没看过题目的话可能确实是看不懂 = =

_小白 发表于 2019-6-10 20:19

顶一下楼主

chiao 发表于 2019-6-10 20:28

厉害,顶一下

vethenc 发表于 2019-6-10 20:33

村长喊你回来放牛

焰火 发表于 2019-6-10 20:35

虽然没看过题目,但是楼主思路表达了出来,看有人说看不懂,感觉是楼主没有说明白一些细节问题,因为所以就直接跳转过去讲了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 2019腾讯游戏安全竞赛PC版分析