Back

津门杯 GoodRE writeup

和两位学长一起做出来的,总算是能在团队赛中做出来题了,结束了长达一个月的白给生涯,不容易啊

思路

先拖到 ida 里进行静态分析

查看main函数,把变量定义和初始化删了,并将 cin 的东西命名为了 input

__int64 __fastcall main(int a1, char **a2, char **a3) {
  std::operator<<<std::char_traits<char>>(&std::cout, ">> ", a3);
  std::operator>><char,std::char_traits<char>>(&std::cin, input);
  if ( strlen(input) == 64 ) {
    v3 = input;
    v4 = &unk_55A06D2D0020;
    v5 = 0LL;
    do {
      v6 = hexstr2int(v3, 8);
      v7 = v14;
      sub_55A06D2CC408(&v14[v5], v6);
      sub_55A06D2CC408(&v18[v5], 17);
      v8 = v19;
      sub_55A06D2CC408(&v19[v5], *v4);
      ++v3;
      ++v4;
      v5 += 36LL;
    } while ( v3 != v21 );
    sub_55A06D2CCB30(v14, v18, v9);
    sub_55A06D2CCB30(v15, v18, v10);
    sub_55A06D2CCB30(v16, v18, v11);
    sub_55A06D2CCB30(v17, v18, v12);
    while ( !sub_55A06D2CCADC(v7, v8) ) {
      v7 += 36;
      v8 += 36;
      if ( v7 == v18 ) {
        __printf_chk(1LL, "flag{%s}\n", input);
        return 0LL;
      }
    }
    puts("error");
  }
  else {
    puts("error");
  }
  return 0LL;
}

简单查看一下,发现输入长度为64,然后 do while 看起来是进行初始化,后面的四个连续函数应该是加密

同时确定这个应该是算法题,不涉及混淆之类的东西

于是查看一下 v6= 的那个函数

__int64 __fastcall hexstr2int(_BYTE *a1, int a2) {
  v2 = strlen(a0123456789abcd) + 1;             // 17
  if ( a2 <= 0 )
    return 0LL;
  v3 = v2 - 1;
  v4 = a1;
  LODWORD(result) = 0;
  while ( 1 ) {
    if ( v3 <= 0 )
      return 0LL;
    for ( i = 0LL; a0123456789abcd[i] != *v4; ++i ) {// input是0-f
      if ( i == v2 - 2 )
        return 0LL;
    }
    if ( v3 <= i )
      break;
    result = (i + (v2 - 1) * result);           // input转成int
    if ( ++v4 == &a1[a2 - 1 + 1] )
      return result;
  }
  return 0LL;
}

发现应该是将输入转换成了 int,而且输入的字符必须得在 0123456789ABCDEF

然后看一眼重复出现三次的 sub_55A06D2CCB30 函数

__int64 __fastcall sub_55A06D2CC408(__int64 a1, int a2) {
  *(a1 + 8) = 0;
  *(a1 + 4) = a2;
  v2 = 4LL;
  while ( 1 ) {
    v3 = v2 - 1;
    if ( *(a1 + v2 + 3) )
      break;
    if ( !--v2 )
      goto LABEL_4;
  }
  v3 = v2;
LABEL_4:
  *a1 = v3;
  return v3;
}

看起来就是简单的赋值,a1 的第一部分填写长度,第二部分存 a2

初始化部分应该可以先不管了,去看加密函数

unsigned __int64 __fastcall sub_55A06D2CCB30(__int64 a1, __int64 a2, __int64 a3) {
  sub_55A06D2CCA13(v5, a1);
  sub_55A06D2CCA13(v6, (a1 + 36));
  sub_55A06D2CC408(v7, 0);
  sub_55A06D2CC408(v8, 0x830A5376);
  sub_55A06D2CC408(v9, 0x1D3D2ACF);
  sub_55A06D2CC667(v10, v9, v8);                // v10=delta=0x9e3779b9
  sub_55A06D2CCA13(v11, a2);
  sub_55A06D2CCA13(v12, (a2 + 36));
  sub_55A06D2CCA13(v13, (a2 + 72));
  sub_55A06D2CCA13(v14, (a2 + 108));
  v3 = 32;
  do {
    sub_55A06D2CC2E9(v7, v7, v10);              // sum+=delta
    left_rot(v15, v6, 4);
    sub_55A06D2CC2E9(v15, v15, v11);            // v15+v11
    sub_55A06D2CC2E9(v16, v6, v7);              // sum+plain
    right_rot(v17, v6, 5);
    sub_55A06D2CC2E9(v17, v17, v12);            // key+plain
    sub_55A06D2CC667(v15, v15, v16);
    sub_55A06D2CC667(v15, v15, v17);
    sub_55A06D2CC2E9(v5, v5, v15);
    left_rot(v18, v5, 4);
    sub_55A06D2CC2E9(v18, v18, v13);
    sub_55A06D2CC2E9(v19, v5, v7);
    right_rot(v20, v5, 5);
    sub_55A06D2CC2E9(v20, v20, v14);
    sub_55A06D2CC667(v18, v18, v19);
    sub_55A06D2CC667(v18, v18, v20);
    sub_55A06D2CC2E9(v6, v6, v18);
    --v3;
  } while ( v3 );
  sub_55A06D2CCA13(a1, v5);
  sub_55A06D2CCA13((a1 + 36), v6);
  return __readfsqword(0x28u) ^ v21;
}

一开始看到一大串 sub_ 还以为是 AES 之类的(毕竟之前做过一道类似的),然后看了一下重要的参数

64bit明文,128bit密钥,32轮加密

看起来很像tea家族的算法,但不是很确定。因为懒得详细看每个函数了,所以动态调试一下,做个黑盒测试

调试之后发现 0x830A53760x1D3D2ACF 其实是用来算 delta 的,算出来 v10=0x9e3779b9,这就肯定是tea家族了

另外,动调的时候发现这里应该都是用的结构体,所以每个小的加法都是单独的函数给出的,但好像用上的只有结构体的前八个字节,后面的字节有什么用还不太清楚

然后具体往下看,看到循环中的前几个分别是 sum+=deltav6<<4(v6<<4)+k0sum+p0v6>>5k1+p0。和网上的三种 TEA 加密比对了一下,发现竟然就是最简单的 TEA 加密,一开始看这么多函数还以为是 XTEA 呢

脚本

密钥就是主函数中初始化的几个 0x11,密文就是最后用来 cmp 的字符串(找结构体对应的那个字符数组就行),于是找了个脚本,直接解密

#include <stdio.h>
#include <stdint.h>

void decrypt(uint32_t* v, uint32_t* k) {
    uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
    uint32_t delta = 0x9e3779b9;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i < 32; i++) {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum -= delta;
    }
    v[0] = v0; v[1] = v1;
}

int main() {
    uint32_t v[2] = {0x79AE1A3B, 0X596080D3}, k[4] = {17, 17, 17, 17};
    decrypt(v, k);
    printf("%X%X", v[0], v[1]);
}

把所有密文全扔进去算结果,就能拿到 flag 了

总结

第一眼看到这个题就感觉能做出来,果然如此

深刻体会到了动调进行黑盒测试的重要性,可以大幅度加快做题速度,能猜出来就不要花时间看

其实这题有点可惜,本来能抢到三血的,结果忘了大小写的问题,脚本算出来后本地测试一直是 error,最后还是大佬队友发现的,然后就过了

Built with Hugo
Theme Stack designed by Jimmy