看雪·众安 2021 KCTF 秋季赛 | 第七题设计思路及解析
2021-12-03 21:53:38 Author: mp.weixin.qq.com(查看原文) 阅读量:28 收藏

看雪·众安 2021 KCTF秋季赛的第七题《声名远扬》已于今天中午12点截止答题!
经统计,本题围观人数多达1179人,共计20支战队成功破解。
恭喜金左手用时4002秒拿下“一血”,接下来和我一起来看看该赛题的设计思路和相关解析吧~

出题团队简介

第七题《声名远扬》出题方: 【洋洋不得意】战队

赛题设计思路

在win64中,代码段寄存器0x23和0x33所对应的GDT表项中CPU的模式分别为32位与64位。
具体原理参考链接:https://bbs.pediy.com/thread-221236.htm
设计思路:

1、exe程序编译为32位程序,把核心判断代码放入64位代码中。

2、加密用base64改了一下编码表,编码表特别好找,单步跟就能看到。

3、校验flag的流程是线性的,没有反调试,加了一丢丢的垃圾指令(可能你们都注意不到)。只要找到onclick函数,一路单步就可以看到输出的结果。

4、把关键字符串隐藏起来,运行时解密出来。比如:"正确","错误",flag编码后的字符串。隐藏字符串算法是异或。

流程如下:

1、把输入的flag进行base64编码;

2、把编码结果丢到64位代码中对比,接收对比结果;

3、把对比结果输出。

破解思路:

1、找到64位代码中flag加密后的数据。

2、找到base64编码表。

3、通过base64算法还原flag。

赛题解析

本赛题解析由看雪论坛sunfishi给出:

考察C++逆向。

 
总体思路:动态调试,黑盒测试。
 
Windows 32位程序,无壳。
 
注:以下分析如未作特殊说明,默认基址为0x251000。
 
题目存在一些花指令,不多做阐述,nop修复即可。
 
比较多的虚函数,同时能看到关键词DuiLib。

有关DuiLib能够从网上找到N篇文章介绍切入点,随便挂一个。

Dump微信PC端的界面Duilib文件-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com(https://bbs.pediy.com/thread-259443.htm)
 
通过虚函数跳转最终定位到按钮回调函数sub_26D2D0。
 
简单调试分析后,能够发现首先是进行了变表的base64编码,具体位置在0x26E530,伪代码分析特征还是比较明显的。
 
 
当然最大的特征当属编码表,其特征位于函数0x26E250,伪代码如下:
int __cdecl base64Maps(int a1){  int *v1; // eax  char v3[8]; // [esp+4h] [ebp-68h] BYREF  int v4; // [esp+Ch] [ebp-60h]  _BYTE base64[65]; // [esp+10h] [ebp-5Ch] BYREF  unsigned int v6[2]; // [esp+51h] [ebp-1Bh] BYREF  int v7; // [esp+68h] [ebp-4h]   v4 = 0;  sub_26D5D0((char *)v6 + 3, 8u);  qmemcpy(base64, "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!", sizeof(base64));  v1 = (int *)__FrameHandler3::TryBlockMap::TryBlockMap(                (__FrameHandler3::TryBlockMap *)v3,                (const struct _s_FuncInfo *)base64,                (unsigned int)v6);  sub_26EA90((char *)v6 + 3, *v1, v1[1]);  v7 = 0;  sub_26EAD0((void *)a1, (int)v6 + 3);  v4 |= 1u;  v7 = -1;  sub_26EA70();  return a1;}

下面进行验证。
 
动态调试得到编码串:

尝试变表解密:
import base64 baseMaps = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="newMaps = "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!" cipherText = "EHsnG0bjGT44BqIhETj!"cipherText = cipherText.translate(cipherText.maketrans(newMaps, baseMaps))print(base64.b64decode(cipherText.encode('utf-8')).decode())

解得lovectf{mas0n},验证成功。
 
进入下一步。
 
经过调试分析确定总体逻辑如下图:

单步调试过程中会发现在verify运行至一个段间跳转时出现异常。
反复调试后,最终发现突破点在于其进行原跳转前,32位至64位模式的切换。
 
只不过在这里的表现形式与往常有所不同。
关于32位程序至64位程序的切换,可以参考文章:
 
CTF中32位程序调用64位代码的逆向方法 - 安全客,安全资讯平台 (anquanke.com)(https://www.anquanke.com/post/id/171111)
 
知晓其模式切换后,强制指定PE64标识。
放入IDA,为方便定位函数,Rebase Segment,基址设为0。

通过调试得到调用函数地址,减去基址后得到偏移量0x146f0。
定位函数,得到伪代码后,看到了函数调用。
汇编下形式为call rdi。
翻找之后能够知道rdi来自于指令mov rdi, [rsp+arg_0]。
 
向上分析:

确定函数偏移为0x145AC。
 
简单分析伪代码,结合代码复用,能够确定其check逻辑如下:
__int64 __fastcall sub_145AC(char *a1, __int64 a2){  unsigned int v4; // edx  char v5; // al  __int64 v6; // rcx  int v7; // edx  char v8; // al  char *v9; // rcx  __int64 v10; // rax  unsigned int v11; // edx  char v12; // al  __int64 v13; // rcx  char v14; // cl  __int64 v15; // r8  int *v16; // rax  char v17; // al  __int64 v18; // rcx  char v19; // cl  __int64 v20; // r8  int *v21; // rax  int v23; // [rsp+4h] [rbp-3Ch] BYREF  char v24; // [rsp+8h] [rbp-38h]  __int16 v25; // [rsp+9h] [rbp-37h]  char v26; // [rsp+Bh] [rbp-35h]  unsigned int v27; // [rsp+Ch] [rbp-34h]  char v28[48]; // [rsp+10h] [rbp-30h] BYREF   v27 = 44;  v4 = 0;  *(__m128i *)v28 = _mm_load_si128((const __m128i *)&xmmword_14408);  *(__m128i *)&v28[32] = _mm_load_si128((const __m128i *)&xmmword_143F8);  *(__m128i *)&v28[16] = _mm_load_si128(xmmword_14418);  do  {    v5 = v4 - 52;    v6 = v4++;    v28[v6] ^= v5;  }  while ( v4 < v27 );                           // 还原base64编码串  v28[v27] = 0;  v7 = 0;  v8 = *a1;  if ( *a1 )  {    v9 = a1;    do    {      if ( v8 != v9[v28 - a1] )                 // strcmp        break;      ++v9;      ++v7;      v8 = *v9;    }    while ( *v9 );  }  v10 = v7;  v11 = 0;  v24 = -48;  if ( a1[v10] == v28[v10] )                    // bingo  {    v23 = 0x78063019;                           // 正确    v25 = 0;    v26 = 0;    do    {      v12 = v11 - 52;      v13 = v11++;      *((_BYTE *)&v23 + v13) ^= v12;    }    while ( v11 < 4 );    v24 = 0;    v14 = v23;    if ( (_BYTE)v23 )    {      v15 = a2 - (_QWORD)&v23;      v16 = &v23;      do      {        *((_BYTE *)v16 + v15) = v14;        v16 = (int *)((char *)v16 + 1);        v14 = *(_BYTE *)v16;      }      while ( *(_BYTE *)v16 );    }  }  else  {    v23 = 0x3C002078;                           // 错误    v25 = 0;    v26 = 0;    do    {      v17 = v11 - 52;      v18 = v11++;      *((_BYTE *)&v23 + v18) ^= v17;    }    while ( v11 < 4 );    v24 = 0;    v19 = v23;    if ( (_BYTE)v23 )    {      v20 = a2 - (_QWORD)&v23;      v21 = &v23;      do      {        *((_BYTE *)v21 + v20) = v19;        v21 = (int *)((char *)v21 + 1);        v19 = *(_BYTE *)v21;      }      while ( *(_BYTE *)v21 );    }  }  return 0i64;}
简单异或还原明文验证猜想。
"""v23 = 0x78063019;    v25 = 0;    v26 = 0;    do    {      v12 = v11 - 52;      v13 = v11++;      *((_BYTE *)&v23 + v13) ^= v12;    }    while ( v11 < 4 );""" v11 = 0v23 = bytearray(int.to_bytes(0x78063019, length=4, byteorder="little")) while 1:    v12 = v11 - 52    v13 = v11    v11 += 1    v23[v13] ^= v12 & 0xff     if v11 >= 4:        breakprint(v23.decode('gbk'))# 正确

最终解题脚本如下:
import base64 """  v27 = 44;  v4 = 0;  *(__m128i *)v28 = _mm_load_si128((const __m128i *)&xmmword_15408);  *(__m128i *)&v28[32] = _mm_load_si128((const __m128i *)&xmmword_153F8);  *(__m128i *)&v28[16] = _mm_load_si128(xmmword_15418);  do  {    v5 = v4 - 52;    v6 = v4++;    v28[v6] ^= v5;  }  while ( v4 < v27 );  v28[v27] = 0;""" xmmArr = [0x0B3E38188BB9CBA9DBAFFB697ABA2948B, 0x0BFDBD9AAD6D4BCA1878490B0B5AE858C, 0x0F8D6D7A7BAB89480B78A94B9AE]v27 = 44v28 = b''for xmm in xmmArr:    v28 += int.to_bytes(xmm, length=16, byteorder="little")v28 = bytearray(v28) v4 = 0while 1:    v5 = v4 - 52    v6 = v4    v4 += 1    v28[v6] ^= v5 & 0xff     if v4 >= v27:        breakv28[v27] = 0print(v28)cipherText = v28.decode() baseMaps = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="newMaps = "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!" cipherText = cipherText.translate(cipherText.maketrans(newMaps, baseMaps))print(base64.b64decode(cipherText.encode('utf-8')).decode())

往期解析

1、看雪·众安 2021 KCTF 秋季赛 | 第二题设计思路及解析

2、看雪·众安 2021 KCTF 秋季赛 | 第三题设计思路及解析

3、看雪·众安 2021 KCTF 秋季赛 | 第四题设计思路及解析

4、看雪·众安 2021 KCTF 秋季赛 | 第五题设计思路及解析

5、看雪·众安 2021 KCTF 秋季赛 | 第六题设计思路及解析


第八题《群狼环伺》正在火热进行中,

👆还在等什么,快来参赛吧!

- End -
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

“阅读原文展开第八题的角逐!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458407655&idx=1&sn=bd30038f0ebc7bac76d01c5c6e1bf1f5&chksm=b18f626d86f8eb7bb49a47cf210f5f5c541edec627a657244b7e41ddb20715a680ce6b811cde#rd
如有侵权请联系:admin#unsafe.sh