一.详细解题步骤

依旧是先查壳,再丢进ida查看,找到这个主函数。

接下来就是耐心查看每一个函数的代码了,比较明显可疑的就是sub_411523,sub_4113DE,ub_4110E1,sub_411302。我们挨个去看:

首先是,sub_411523函数。噢,这个函数里面还有一层,像套娃一样,那就一层一层的拆到底:

这是一个明显的XTEA加密,找全XTEA解密需要的所有数据,而TEA最主要的就是解密需要的数组,key,delta和sum。在主函数中,v7和v8是key,v9是解密需要的数组。在TEA函数中,v4 -= 0x61C88647;能够看出是对0x61C88647取负,作为delta,则根据加密逻辑,这里的sum,就是0xC6EF3720。不知道为什么的可以去学一下TEA加密逻辑,了解一下TEA加密常见的代码特征。

怎么算出sum值的?

由于解密是对加密逆向,已知加密时sum初始值都是0,进行32轮加密,每一轮对sum的处理都是-delta,所以解密时初始sum=-0x61C88647 * 32。

结合两个函数再解释为什么v7.v8为key?

是因为,在主函数中调用sub_411523函数时,将v7,v8作为实参传入,一直到sub_415100函数中,而在sub_415100函数中将a1,a2作为形参,通过a1,a2在XTEA加密函数中的调用特征,判断这两个变量作为key。

最后,可以套用XTEA解密的模板写一个脚本,先解开这个XTEA加密。

#include <stdio.h>
#include <stdint.h> 
#include <string.h>
#include<iostream>
 using namespace std;

void tea(uint32_t *v, uint32_t *k7_8) {
    uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
    uint32_t delta = 0x9e3779b9;
 
    for (i = 0; i < 32; i++) {
        v1 -= ((v0 << 4) + k7_8[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k7_8[3]);
        v0 -= ((v1 << 4) + k7_8[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k7_8[1]);
        sum -= delta;
    }
 
    v[0] = v0;
    v[1] = v1;
}
 
int main() {
    uint32_t enc[2]={0x60FCDEF7,0x236DBEC};
    uint32_t key[]={0x12,0x34,0x56,0x78};
    tea(enc,key);
    cout<<enc[0];
    return 0;
}

解密结果是3

我们继续看下一个函数sub_4113DE,这是一个PE文件解析函数。

深入查看一下sub_411221函数

现在我们确定,这个函数目前没有能够和前面联系的加密信息,不影响我们解题,暂且放一下继续解题。

查看下一个函数sub_4110E1,当然查看后你会发现这是一个自定义输入的函数,那我们继续查看下一个函数sub_411302:
有一个坏消息是,这个函数点进去后发现了爆红,那这里第一时间就想到了花指令这个方向。返回到汇编代码,让我们看看这是怎么回事。追踪到爆红的代码段,发现出现了大量的数据,这是SMC的特征。解决方法就是,将数据转换一下,然后全部分析为代码:shift+F12,写入一个脚本。

for i in range(0x41D000,0x41E600):
    patch_byte(i,get_wide_byte(i)^3)

为什么要用脚本先转换数据再分析为代码?

是因为SMC加密后,我们看到的是错误数据,结合sub_4113DE函数中,.hdctf是这个部分解密后的结果,但数据在sub_411221和3异或,所以这里要先改变数据回到原本的样子,再转变为代码。

之后按C快捷键,将转变好的数据全部重新分析为代码

此时F5还是无法查看到反汇编代码,是因为函数还没有被承认,所以需要我们再使用快捷键P重新识别一下函数。

这样F5就能看到反汇编代码了

int __cdecl sub_41D000(char *Str)
{
  char v2; // [esp+0h] [ebp-558h]
  size_t m; // [esp+190h] [ebp-3C8h]
  BOOL v4; // [esp+19Ch] [ebp-3BCh]
  signed int k; // [esp+1A8h] [ebp-3B0h]
  int v6; // [esp+1B4h] [ebp-3A4h]
  int v7; // [esp+1C0h] [ebp-398h]
  int i; // [esp+1CCh] [ebp-38Ch]
  int j; // [esp+1CCh] [ebp-38Ch]
  int v10; // [esp+1CCh] [ebp-38Ch]
  char v11; // [esp+1DBh] [ebp-37Dh]
  char v12; // [esp+1DBh] [ebp-37Dh]
  _BYTE v13[540]; // [esp+1E4h] [ebp-374h]
  char v14[24]; // [esp+400h] [ebp-158h] BYREF
  signed int v15; // [esp+418h] [ebp-140h]
  char v16[264]; // [esp+424h] [ebp-134h] BYREF
  char v17[40]; // [esp+52Ch] [ebp-2Ch] BYREF

  __CheckForDebuggerJustMyCode(&unk_425036);
  v17[0] = 15;
  v17[1] = -108;
  v17[2] = -82;
  v17[3] = -14;
  v17[4] = -64;
  v17[5] = 87;
  v17[6] = -62;
  v17[7] = -32;
  v17[8] = -102;
  v17[9] = 69;
  v17[10] = 55;
  v17[11] = 80;
  v17[12] = -11;
  v17[13] = -96;
  v17[14] = 94;
  v17[15] = -53;
  v17[16] = 44;
  v17[17] = 22;
  v17[18] = 40;
  v17[19] = 41;
  v17[20] = -2;
  v17[21] = -1;
  v17[22] = 51;
  v17[23] = 70;
  v17[24] = 14;
  v17[25] = 87;
  v17[26] = -126;
  v17[27] = 34;
  v17[28] = 82;
  v17[29] = 38;
  v17[30] = 43;
  v17[31] = 110;
  v17[32] = -28;
  v17[33] = -126;
  v17[34] = 36;
  j_memset(v16, 0, 0x100u);
  v15 = j_strlen(Str);
  strcpy(v14, "you_are_master");
  v13[531] = 0;
  v6 = 0;
  for ( i = 0; i < 256; ++i )
  {
    v13[i + 264] = i;
    v13[i] = v14[i % j_strlen(v14)];
  }
  for ( j = 0; j < 256; ++j )
  {
    v6 = ((unsigned __int8)v13[j] + v6 + (unsigned __int8)v13[j + 264]) % 256;
    v11 = v13[j + 264];
    v13[j + 264] = v13[v6 + 264];
    v13[v6 + 264] = v11;
  }
  v7 = 0;
  v10 = 0;
  for ( k = 0; k < v15; ++k )
  {
    v10 = (v10 + 1) % 256;
    v7 = (v7 + (unsigned __int8)v13[v10 + 264]) % 256;
    v12 = v13[v10 + 264];
    v13[v10 + 264] = v13[v7 + 264];
    v13[v7 + 264] = v12;
    v16[k] = v13[((unsigned __int8)v13[v7 + 264] + (unsigned __int8)v13[v10 + 264]) % 256 + 264] ^ Str[k];
  }
  v4 = j_strlen(Str) == 35;
  for ( m = 0; m < j_strlen(v17); ++m )
  {
    if ( v17[m] != v16[m] )
    {
      v4 = 0;
      break;
    }
  }
  if ( v4 )
    return sub_41114F("right!!!!", v2);
  else
    return sub_41114F("please try agin~", v2);
}

很明显这是RC4加密,解RC4需要那个key,而key就是我们前面XTEA解出来的数据。这里的解密脚本来自别的大佬,原文指路:文章 - [HDCTF 2023]enc lingfeng的WriteUp | NSSCTF

#include<stdio.h>
#include<string.h>
void main(){
    char Str[]={0xf,0x94,0xae,0xf2,0xc0,0x57,0xc2,0xe0,0x9a,0x45,0x37,0x50,0xf5,0xa0,0x5e,0xcb,0x2c,0x16,0x28,0x29,0xfe,0xff,0x33,0x46,0xe,0x57,0x82,0x22,0x52,0x26,0x2b,0x6e,0xe4,0x82,0x24};
      char v2; // [esp+0h] [ebp-558h]
  size_t m; // [esp+190h] [ebp-3C8h]

  int k; // [esp+1A8h] [ebp-3B0h]
  int v6; // [esp+1B4h] [ebp-3A4h]
  int v7; // [esp+1C0h] [ebp-398h]
  int i; // [esp+1CCh] [ebp-38Ch]
  int j; // [esp+1CCh] [ebp-38Ch]
  int v10; // [esp+1CCh] [ebp-38Ch]
  char v11; // [esp+1DBh] [ebp-37Dh]
  char v12; // [esp+1DBh] [ebp-37Dh]
  char v13[540]; // [esp+1E4h] [ebp-374h]
  char v14[24]; // [esp+400h] [ebp-158h] BYREF
  int v15; // [esp+418h] [ebp-140h]
  char v16[264]; // [esp+424h] [ebp-134h] BYREF
  char v17[40];
  strcpy(v14, "you_are_master");
   v13[531] = 0;
  v6 = 0;
  v15 = strlen(Str);
  for ( i = 0; i < 256; ++i )
  {
    v13[i + 264] = i;
    v13[i] = v14[i % strlen(v14)];
  }
  for ( j = 0; j < 256; ++j )
  {
    v6 = ((unsigned __int8)v13[j] + v6 + (unsigned __int8)v13[j + 264]) % 256;
    v11 = v13[j + 264];
    v13[j + 264] = v13[v6 + 264];
    v13[v6 + 264] = v11;
  }
  v7 = 0;
  v10 = 0;
  for ( k = 0; k < v15; ++k )
  {
    v10 = (v10 + 1) % 256;
    v7 = (v7 + (unsigned __int8)v13[v10 + 264]) % 256;
    v12 = v13[v10 + 264];
    v13[v10 + 264] = v13[v7 + 264];
    v13[v7 + 264] = v12;
    v16[k] = v13[((unsigned __int8)v13[v7 + 264] + (unsigned __int8)v13[v10 + 264]) % 256 + 264] ^ Str[k];
  }
  for ( k = 0; k < v15; ++k ){
    printf("%C",v16[k]);
  }
}

flag就是HDCTF{y0u_ar3_rc4_t3a_smc_m4ster!!}

二.分析代码:

这道题,我选择仔细分析这个PE文件解析函数,因为这是我第一次见到这个函数,一起来学一下。

int __cdecl sub_414B00(int a1, int a2)
{
  int result; // eax
  int i; // [esp+E8h] [ebp-44h]
  char *Str1; // [esp+F4h] [ebp-38h]
  __int16 v5; // [esp+118h] [ebp-14h]字节数量

  __CheckForDebuggerJustMyCode(&unk_425036);
  v5 = *(_WORD *)(*(_DWORD *)(a1 + 60) + a1 + 6);
  Str1 = (char *)(a1 + *(_DWORD *)(a1 + 60) + 248);//指向第一个表节
  for ( i = 0; ; ++i )
  {
    result = v5;
    if ( i >= v5 )
      break;
    if ( !j_strcmp(Str1, ".hdctf") )//比较
      return sub_411221(*((_DWORD *)Str1 + 3) + a1, *((_DWORD *)Str1 + 4), a2);
//对目标节的处理
    Str1 += 40;
  }
  return result;
}

所以这个函数的作用其实就是在PE文件中查找目标字节,然后对这部分进行一个自定义处理。

当然,这道题中的知识点还包括XTEA和RC4加密原理,和解密逻辑。这部分原先我已经单独学过了,这里放下我简陋的笔记,有需要的师傅们可以通过这个简单了解一下,如果想学明白还是要找一些详细讲解看一看的。

https://www.yuque.com/g/memory-luvcr/whgrxl/hrecgy4gfioy0wll/collaborator/join?token=Wtlgwso4RyN9cjPu&source=doc_collaborator# 《RC4》

https://www.yuque.com/g/memory-luvcr/whgrxl/ulteyg669ogflu2v/collaborator/join?token=WvVgOIICkPyBaqOj&source=doc_collaborator# 《TEA》

OK!这篇结束啦,依旧期待有大佬能够在线指出错误和文章的不足,万分感谢!!!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐