首页
社区
课程
招聘
[原创]看雪 2022 KCTF 春季赛 第九题 同归于尽
发表于: 2022-5-31 06:49 10012

[原创]看雪 2022 KCTF 春季赛 第九题 同归于尽

2022-5-31 06:49
10012

IDA打开,发现所有的字符串字面值都经过了简单的加密。

先看 _main 函数:

得出输入长度是 3200 // 100 = 32 字节,第一个字符是 'A'

程序开头创建了一个线程并监听端口,顺序追到 sub_49C89D,是 recv 的各种消息的处理函数。注意到其中几行(暂时不知道在哪里会用到):

socket 函数的交叉引用,找到发送消息的地方 sub_49C47F。向上找调用者,是 sub_43B62Bsub_432990 等非常复杂的函数,看起来加了某种混淆。

翻看 IDA 左侧的函数列表,下半部分基本都是自动识别出的蓝色的库函数,上半部分白色函数大部分都是字符串构建与解密函数,但是这一片白色函数的最后几个,如 sub_49D244,里面包含了 sm4 加密算法的常量。

顺着找交叉引用,结合 sm4 算法的实现,标记出 sub_49D244是密钥扩展,sub_49D07F是单个块加密,sub_49D025 是多个块加密。
sub_49AF99 是最加密的最外层函数,但是调用它的 sub_4631E9 又是一个非常复杂的函数。

开始上手动态调试。输入长32字节,第一个字符是'A',保守起见32个字符只输入 [0-9A-Za-z] 范围内的字符。

0x401AAF 遇到了第一处异常,是 int 2Dh 反调试(回忆起 IDA 加载程序时提示的 pdb 路径包含 AntiDebug)。x32dbg 中可以 Shift+F9 忽略异常继续调试,但程序里可能有暗坑,暂时不考虑。

在前面找到的几个 sm4 加密函数下断点:sub_49D244 第一个参数指向的16个字节是密钥,先提取出来;sub_49D025 第三个参数是加密长度(32),第四个参数指向的是待加密内容,发现前 16 字节有值,后 16 字节全是 0,也先提取出来;运行到函数返回,从第五个参数指向的地址提取出加密结果。

找一份标准的 sm4 加密进行测试,发现程序的加密结果与标准算法一致。

对于待加密字节与原始字节的关系,如果输入的值有规律,则这里也能观察到规律;改变一个输入字节,这里也只改变一个字节,尝试异或一下得到了 0x55。联系到 sub_49C89D 里面循环异或 0x55 的代码,猜测待加密的字节就是输入值hexdecode之后再逐位异或0x55,测试后得到验证。

调用加密的函数太复杂不想看。从 _main 函数开头易知 sub_49B4ED 是 printf,查找交叉引用发现在 _main 函数之外只在 sub_46D092 有三处调用。
在调试器中把 EIP 直接改到这三处调用前面,看到 0x47A497 处的调用是输出 "成功"0x4754310x478424 处的调用是输出 "失败"

sub_46D092 在 IDA 无法反编译(提示 "stack frame is too big"),但是 Ghidra 可以(Ghidra 的反编译效果虽然差一点,但对畸形函数的支持很好)。

定位到这三个位置:(分别在 3280、4561、9279 行)

第一块代码是检查输入字符不含小写字母,因此可以确定输入只包含 [0-9A-Z];第二块代码要求 bVar1 为 true,搜索一下读 bVar1 的引用发现只在第一处赋值为了false。

动态调试,发现第一处的 if (*(int *)(unaff_EBP + 0x240) < 1) 条件不满足。搜索 0x240,找到下面这里(7255行):

很明显的循环判等,而且 LAB_004783e1 就是上面第一块代码的位置。

在这里下断点,提取出待比较的值,发现其中一边就是前面 sm4 的加密结果。

写脚本:先解密sm4,在逐字节异或0x55,最后hexencode,得到32字节的序列号,发现第一个字节不是'A';输入程序中也不正确。

怀疑有反调试导致提取出的密钥或加密结果不正确。为了避免调试干扰,把提取密钥的位置 0x49D244 patch成死循环,运行程序,然后再附加,发现提取到的密钥确实有变化。

重新计算序列号,得到了正确的结果。

最终脚本:

(有个小坑:网上搜索 sm4 的 python实现,很多文章推荐 gmssl 库,但是测试发现它的 ECB 加密模式的密文与明文长度竟然不相等(似乎是加了padding,但ECB模式不应该这样),而且按上面的思路解密得到的结果也不对,不太清楚哪里出了问题;后来找到一篇文章提到的 sm4 库是正常的。这两个库都可以通过 pip 直接安装。)

 
  scanf(v51, v138);
  if ( v138[0] != 'A' )    //
  {
    v52 = sub_415B2C(v139);
    v53 = (char *)sub_499715(v52);
    printf(v53);
    v54 = (_DWORD *)sub_414097((int)v139);
    v55 = sub_499741(v54);
LABEL_6:
    system((int)v55);
    return 0;
  }
  v56 = 0;
  do
    ++v56;
  while ( v138[v56] );
  if ( 100 * v56 != 3200 )    //
  {
  scanf(v51, v138);
  if ( v138[0] != 'A' )    //
  {
    v52 = sub_415B2C(v139);
    v53 = (char *)sub_499715(v52);
    printf(v53);
    v54 = (_DWORD *)sub_414097((int)v139);
    v55 = sub_499741(v54);
LABEL_6:
    system((int)v55);
    return 0;
  }
  v56 = 0;
  do
    ++v56;
  while ( v138[v56] );
  if ( 100 * v56 != 3200 )    //
  {
 
switch ( buf )
{
  case 1:
    for ( i = 0; i < v10; ++i )
      v5[i] ^= 0x55u;
switch ( buf )
{
  case 1:
    for ( i = 0; i < v10; ++i )
      v5[i] ^= 0x55u;
 
 
 
 
 
 
 
 
 
 
  if (*(int *)(unaff_EBP + 0x240) < 1) {
LAB_004783e1:    // LAB_004783e1
    uVar5 = 0;
    do {
      uStackY75624 = 0x4783ef;
      pcVar4 = (char *)FUN_0040462d((void *)(unaff_EBP + 0x140),uVar5);
      if ('`' < *pcVar4) {
        uStackY75624 = 0x478400;
        pcVar4 = (char *)FUN_0040462d((void *)(unaff_EBP + 0x140),uVar5);
        if (*pcVar4 < '{') {
          uStackY75624 = 0x47841c;
          iVar2 = FUN_004176e5((undefined4 *)(unaff_EBP + -0xab90));
          pcVar4 = (char *)FUN_00499c2d(iVar2);
          uStackY75624 = 0x478429;
          FID_conflict:_wprintf(pcVar4);   // "失败"
          bVar1 = false;    // bVar1
          break;
        }
      }
      uVar5 = uVar5 + 1;
    } while (uVar5 < 0x20);
  if (*(int *)(unaff_EBP + 0x240) < 1) {
LAB_004783e1:    // LAB_004783e1
    uVar5 = 0;
    do {
      uStackY75624 = 0x4783ef;
      pcVar4 = (char *)FUN_0040462d((void *)(unaff_EBP + 0x140),uVar5);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-5-31 06:53 被mb_mgodlfyn编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//