首页
社区
课程
招聘
[原创]CTF2018第十三题分析(qwertyaa)
2018-7-10 22:26 2298

[原创]CTF2018第十三题分析(qwertyaa)

2018-7-10 22:26
2298

分析主要流程

首先看一遍main函数,如下:

// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int128 v3; // xmm1
  __int128 v4; // xmm2
  __int128 v5; // xmm3
  __int128 v6; // xmm6
  __int128 v7; // xmm7
  __int128 v8; // xmm8
  __int64 v9; // rdx
  __int64 v10; // rax
  __int64 v11; // rdx
  __int64 v12; // rax
  __int64 v13; // rdx
  __int64 v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  _BYTE hexkey[5]; // [rsp+20h] [rbp-198h]
  char Format[4]; // [rsp+30h] [rbp-188h]
  char v20; // [rsp+34h] [rbp-184h]
  double res; // [rsp+40h] [rbp-178h]
  double v22[4]; // [rsp+50h] [rbp-168h]
  __int64 message; // [rsp+70h] [rbp-148h]
  __int64 v24; // [rsp+78h] [rbp-140h]
  __int64 v25; // [rsp+80h] [rbp-138h]
  __int64 v26; // [rsp+88h] [rbp-130h]
  char key[11]; // [rsp+90h] [rbp-128h]
  int v28; // [rsp+190h] [rbp-28h]

  sub_401F00(*(__int64 *)&argc, (__int64 *)argv);
  hexkey[4] = 0;
  memset(key, 0, 0x100uLL);
  v20 = 0;
  v22[0] = 0.0;
  v22[1] = 0.0;
  v22[2] = 0.0;
  v22[3] = 0.0;
  res = 0.0;
  v28 = 0;
  *(_DWORD *)hexkey = 0;
  *(_DWORD *)Format = 0;
  message = 0LL;
  v24 = 0LL;
  v25 = 0LL;
  v26 = 0LL;
  initVals();
  sub_401C50((__int64)&v28, (__int64)key, v9, 252);
  v10 = 0LL;
  do
  {
    v11 = (byte_408D00[v10] ^ 0x19) - (unsigned int)v10;
    *((_BYTE *)&message + v10) = (byte_408D00[v10] ^ 0x19) - v10;
    ++v10;
  }
  while ( v10 != 16 );
  printf(Format, key, v11, &message);
  Format[0] = byte_408D70 ^ 0x25;
  Format[1] = (byte_408D71 ^ 0x25) - 1;
  scanf(Format, key, key, Format);              // input
  if ( strlen(key) != 10 )                      // key len error
  {
    v12 = 0LL;
    do
    {
      v13 = (byte_408D10[v12] ^ 0xF) - (unsigned int)v12;
      *((_BYTE *)&message + v12) = (byte_408D10[v12] ^ 0xF) - v12;
      ++v12;
    }
    while ( v12 != 16 );
    goto LABEL_6;
  }
  if ( (unsigned int)hex2bin((__int64)Format, (__int64)key, (__int64)hexkey, (__int64)key, 10) != 5 )
  {
    v15 = 0LL;
    do
    {
      v13 = (byte_408D20[v15] ^ 0x21) - (unsigned int)v15;
      *((_BYTE *)&message + v15) = (byte_408D20[v15] ^ 0x21) - v15;
      ++v15;
    }
    while ( v15 != 16 );
    goto LABEL_6;
  }
  HIWORD(v22[0]) = *(_WORD *)hexkey;
  v22[2] = v22[0];
  *(_WORD *)((char *)&v22[1] + 5) = *(_WORD *)&hexkey[2];
  HIBYTE(v22[1]) = hexkey[4];
  v22[3] = v22[1];
  sub_4015E0((__int64)Format, (__int64)key, &res, v22, *(unsigned __int64 *)&v22[1], v3, v4, v5, v6, v7, v8);
  Format[0] = byte_408D72 ^ 0x12;
  Format[1] = (byte_408D73 ^ 0x12) - 1;
  Format[2] = (byte_408D74 ^ 0x12) - 2;         // %lf
  sprintf(Format, key, Format, key, *(_QWORD *)&res, res, *(double *)&v3, res);
  if ( key[1] != '.' )
  {
    v16 = 0LL;
    do
    {
      v13 = (aPvutFg[v16] ^ 0x3F) - (unsigned int)v16;
      *((_BYTE *)&message + v16) = (aPvutFg[v16] ^ 0x3F) - v16;
      ++v16;
    }
    while ( v16 != 16 );                        // Oh failed
    goto LABEL_6;
  }
  if ( sqrt(
         (double)(key[0] - 48) * (double)(key[0] - 48)
       + (double)(key[2] - 48) * (double)(key[2] - 48)
       + (double)(key[3] - 48) * (double)(key[3] - 48)) <= 15.5
    || hexkey[2] & 0xF )
  {
    v17 = 0LL;
  }
  else
  {
    v17 = 0LL;
    if ( fabs(v22[2] + v22[3] - res) < 0.003 )
    {
      do
      {
        v13 = (byte_408D50[v17] ^ 0x47) - (unsigned int)v17;
        *((_BYTE *)&message + v17) = (byte_408D50[v17] ^ 0x47) - v17;//Cong
        ++v17;
      }
      while ( v17 != 16 );
      goto LABEL_6;
    }
  }
  do
  {
    v13 = (byte_408D60[v17] ^ 0x37) - (unsigned int)v17;
    *((_BYTE *)&message + v17) = (byte_408D60[v17] ^ 0x37) - v17;//Come on
    ++v17;
  }
  while ( v17 != 16 );
LABEL_6:
  printf(Format, key, v13, &message);
  return 0;
}

里面的所有字符串都简单加密了一下,动态调试即可得他们分别是key len error.key char error%lfOhhh.Try againCome on, go onCongratulation

 

程序里的fabs和浮点取负似乎不一定能够识别出来,在了解到COERCE_UNSIGNED_INT64用于将参数强制转换为unsigned __int64(等价于*(unsigned __int64*)(&x))以及double的最高位是符号位后很容易手工识别出来。

 

而程序中的exp函数可以通过函数中的一些魔数识别,sqrt可以通过异常处理时用的字符串识别,将这两个函数重新命名后按F5可以识别出不少东西来。

 

而开头的initValssub_401C50似乎是在初始化一些东西,或许是在训练网络,里面的GetTickCount似乎用于反调试,这一点和 lelfei 以前的CM很像。

 

输入后是判断输入的key是否长度为10,并将其进行“hex2bin”,然后将结果放到两个double高2/3byte上去。

 

接下来将其传入一个函数记作nnRun,通过传递地址返回一个double,接下来进行一些检查:

  1. 返回的double%lf打印后第二个字符是'.'
  2. 返回的double%lf打印后第一、三、四个字符的平方和开根号后要大于15.5(代入9/9/99/9/8可知这三位必须都是9才满足要求)
  3. hex2bin后的第三字符 & 0xf 结果为0(即原始密钥的对应位为0)
  4. 传入nnRun的两个double之和与返回的double之差的绝对值要小于0.03

而阅读nnRun可知,但传入double不在(1,10)的范围内时返回的double必为10.0,而这显然不满足条件1,而double最高位是符号位,接下来是阶码,后面才是尾数,而改变key改变的是这两个double的高位内容,所以所剩的key的可能数不多了(总共仅几百万种可能)。

实现nnRun

但是由于前面两个用于初始化的函数执行的很慢,所以我们要直接Dump初始化结果,并自已实现一遍nnRun才能较快穷举。

 

仔细看几遍,就可写出nnRun的代码如下:

void nnRun(double *out, double *in) {
    double mid[20];
    in[0] = (in[0] - 0.01 + 1.0) / (9.99 - 0.01 + 1.0);
    in[1] = (in[1] - 0.03 + 1.0) / (9.99 - 0.03 + 1.0);
    for (int i = 0; i != 18; i++) {
        mid[i] = 1.0 / (exp(-(in[1] * wei1[i * 2 + 1] + in[0] * wei1[i * 2])) + 1.0);
    }

    double sum = 0.0;
    for (int i = 0; i != 18; i++)
        sum += wei2[i] * mid[i];

    if (in[2] <= 1.0 || in[2] >= 10.0 || in[3] <= 1.0 || in[3] >= 10.0) {
        // wei2[0] = wei2[0]*wei2[0]; // modify the weight
        *out = 10.0;
    } else {
        *out = sum * (19.32 - 0.26 + 1.0) + 0.26 - 1.0;
    }
}

其中的wei*是被前面所述的两个函数初始化的内容。

 

这似乎是一个两层的神经网络,第一层有sigmoid函数,第二层直接乘权重,最后求和,这里似乎是在用神经网络模拟加法。(然而我并不懂神经网络,这些都是瞎猜的。(懂了似乎对这道题解题也没太大帮助。))

暴力取得key

毕竟即使懂,由于神经网络很难被人类直接看懂,暴力是个不错的选择。

 

由第一段可知,所剩下的可能数已经不多,枚举可知double的最高两bit必须在3ff1h-4023h的范围内。

 

暴力的程序如下("defs.h"是IDA的plugins文件夹下的头文件):

#include "defs.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

__int64 weiLL1[40] = {
    0x0BFF0645E9156DBC1, 0x3FB5B59C6AE0004B,  0x0BFE0FD9DEF0F5F9A,
    0x3FE685E23E24E853,  0x0BFBEDEBCA72F7512, 0x0BFD40B39A1B3CB8C,
    0x0BFE87D3F640F203C, 0x3FD5B9DCAD3E9529,  0x3FDA27F983BCFDC3,
    0x3FD62BE5D9589EAE,  0x0BFE581B69A36B717, 0x3FE6801BBF527252,
    0x3FDEAE2EA9FA99CC,  0x3FC290C00A49CF36,  0x0BFE842DEACE9DB6E,
    0x0BFF30F05544464E8, 0x0BFE6EC1FDA490984, 0x0BFC7C4E2F5A93E07,
    0x0BFD8D6B32E2FB6F9, 0x0BFDC416F857C9468, 0x3FE7F90F71FEB8D9,
    0x0BFD2CC5C4EF8FBF4, 0x0BFD1F8160A51CD2B, 0x0BFE5202BE8B76C05,
    0x0BFF23B46C97DC5A4, 0x0BFD7E42B9359DDEB, 0x0BFD7C90E0C010F65,
    0x0BFD48AAE09661ADC, 0x3FCD2D7C013B771C,  0x3FD2520DDF5608B2,
    0x0BFE4BF1ED4E7248A, 0x3FD5EBAD5FB98A18,  0x3FBA13DBE1D40BC8,
    0x0BFB123848B37921F, 0x0BFE993D71BB7483C, 0x3FD1BB92E0D38D8F};
double *wei1 = (double *)weiLL1;

double wei2[20] = {
    -0.1194112006523917, 0.3642266945402522,  -0.3350415063325052,
    -0.6162511508923307, 1.038326594084909,   0.1932021480282533,
    1.001826904076745,   0.4434245541912034,  -0.5712156493805307,
    -1.368248566749872,  0.1587663304193661,  -1.153231630918381,
    0.2535851365039063,  -0.9888794593893403, 0.6114148602075213,
    0.1491657913886502,  0.5188520292352635,  0.2985569369090871};

void nnRun(double *out, double *in) {
    double mid[20];
    in[0] = (in[0] - 0.01 + 1.0) / (9.99 - 0.01 + 1.0);
    in[1] = (in[1] - 0.03 + 1.0) / (9.99 - 0.03 + 1.0);
    for (int i = 0; i != 18; i++) {
        mid[i] =
            1.0 / (exp(-(in[1] * wei1[i * 2 + 1] + in[0] * wei1[i * 2])) + 1.0);
    }

    double sum = 0.0;
    for (int i = 0; i != 18; i++)
        sum += wei2[i] * mid[i];

    if (in[2] <= 1.0 || in[2] >= 10.0 || in[3] <= 1.0 || in[3] >= 10.0) {
        // wei2[0] = wei2[0]*wei2[0]; // modify the weight
        *out = 10.0;
    } else {
        *out = sum * (19.32 - 0.26 + 1.0) + 0.26 - 1.0;
    }
}

char key[20] = {0};
char cmd[200] = {0};
char val[200] = {0};

void swap(char *x, char *y) {
    _WORD *a = (_WORD *)x;
    _WORD *b = (_WORD *)y, t = *a;
    *a = *b;
    *b = t;
}

int main() {
    double x = 0.0, y[4] = {0.0};
    for (int i = 0; i < 0x10000; i++)
        if ((i & 0xf) == 0) { // For hexkey[2] & 0xF == 0
            for (int j = 0x3f; j < 0x41; j++) {
                x = 0.0;
                *(unsigned short *)((char *)&x + 5) = i;
                (*((unsigned char *)&(x) +
                   (sizeof(x) / sizeof(unsigned char) - 1))) = j;
                if (x > 1.0 && x < 10.0) {
                    for (int k = 0x3ff1; k <= 0x4023; k++) {
                        y[0] = 0.0;
                        HIWORD(y[0]) = k;
                        y[1] = x;
                        y[2] = y[0];
                        y[3] = y[1];
                        double ret = 0.0;
                        nnRun(&ret, y);
                        sprintf(val, "%lf", ret);
                        if (val[1] == '.' && ret >= 9.99 &&
                            fabs(y[2] + y[3] - ret) < 0.003) {
                            sprintf(key, "%04X%04X%02X", k, i, j);
                            swap(key, key + 2);
                            swap(key + 4, key + 6);
                            { // Check it by run reality
                                printf("Testing %s...\n", key);
                                FILE *f = fopen("in.txt", "wb");
                                fprintf(f, "%s", key);
                                fclose(f);
                                system("cmd.exe /c nncrackme.exe < in.txt > out.txt");
                                f = fopen("out.txt", "rb");
                                memset(val, 0, sizeof val);
                                fgets(val, 200, f);
                                fclose(f);
                                if (strcmp(val, "Input your KEY:Congratulation\r\n") == 0) {
                                    printf("Success key: %s\n", key);
                                }
                            }
                        }
                    }
                }
            }
        }
    return 0;
}

这个程序最后还会把枚举出来的正确密钥用NNCrackme.exe来验证一遍,当然这个其实是不必要的(因为我一开始省略了一些条件判断让NNCrackme.exe来直接判断)。

 

最后得到的key为F13FE02140


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2018-7-10 22:39 被qwertyaa编辑 ,原因: 校对
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回