多喝咖啡 发表于 2020-11-5 14:58

XNUCA2020-RE-UnravelMFC复现

本帖最后由 多喝咖啡 于 2020-11-7 22:32 编辑

当时做这题一步步动态调,花了7 8个小时做出来了...
用的是x32dbg调试,为了跟动态调试的时候一致,把基址改为了0xA90000。
### 如何找按钮对应的函数
如何找到这些按钮按下时对应的消息处理函数呢?


在MFC中,程序是使用消息机制来实现操作响应的,下面是消息映射表的结构。其中我们想要的某个控件的消息处理函数,就存放在该结构体的**pfn**中。**nID与控件的ID相同。**
```c
struct AFX_MSGMAP{
    AFX_MSGMAP * pBaseMessageMap;
    AFX_MSGMAP_ENTRY * lpEntries;
}

struct AFX_MSGMAP_ENTRY{
    UINT nMessage;    //Windows Message
    UINT nCode      //Control code or WM_NOTIFY code
    UINT nID;         //control ID (or 0 for windows messages)
    UINT nLastID;   //used for entries specifying a range of control id's
    UINT nSig;      //signature type(action) or pointer to message
    AFX_PMSG pfn;   //routine to call (or specical value)
}
```

用resource hacker查找资源。得到资源对应的编号。
例如:确定对应1,取消对应2,输入框对应1000。
于是我们在IDA中搜索立即数1000。

在.rdata段里找一找。符合上述结构体的数据

```c
struct AFX_MSGMAP_ENTRY
{
      UINT nMessage;
      UINT nCode;
      UINT nID;
      UINT nLastID;
      UINT_PTR nSig;
      void (*pfn)(void);
};

struct AFX_MSGMAP
{
const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY *lpEntries;
};
```
将上面两个结构体插入到Local Types。再同步到idb。

在edit-struct var中,将上述数据组织成结构体。
于是,我们找到了按钮对应的函数。


### 确认键按不了?

在消息框对应的函数中,有一个与66的比较。可以猜测,我们需要让输入长度为66。果然可以按下确定了。

### 两个藏得很深的函数
sub_C29C80 SMC函数
在生成消息框之前会运行

sub_C29E40 改变check1里面的cmp_str
```c
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
      char key[] = "fuck";
      char plain = "B=.NI;&3JBZ;$;?(I72x0&4GLZDS2V6&%AF.!5#+J[F^";
      plain = 0x27;

      for(int i =0;i<0x2c;i++)
      {
                plain ^= key;
                printf("%c",plain );
      }
      printf("\n");
      return 0;
}
```
所以check1里面的cmp_str = `$HM%/NEX,79PBN\C/BQLVSW,*/'8T#UMC4%EG@@@,.%5`

### 按下确认键会发生的事情

之前一大堆函数大概就是在获取消息框的内容、检查有没有是否有输入之类的。值得注意的一个函数是sub_C017A9,拿ida自己命名的参数`(lpMultiByteStr, lpWideCharStr, 2 * v17, CodePage)`去搜了下,发现神似WideCharToMultiByte这个函数。作用:因为我们的输入存到内存中都是一个字符两字节,会把两字节的字符转换为一字节。经过动态调试,确实是这个函数。

sub_BFDD48这个是个strlen,emmmmm如果不动调的话,真的看不出来。

然后分别check输入的前33个和后33个字符。

#### 一个魔改了的RC4
sub_C0971F对输入的前33位做了一个魔改的rc4加密
```c
int main()
{
      char key[] = "sorry_you_are_wrong";
      unsigned char T={0};
      unsigned char S[] = {196, 120, 53, 239, 86, 152, 45, 254, 98, 81, 138, 177, 186, 121, 127, 37, 169, 181, 73, 189, 137, 159, 19, 228, 60, 235, 26, 90, 204, 174, 191, 212, 243, 106, 203, 83, 35, 117, 236, 149, 247, 221, 88, 51, 31, 193, 151, 249, 94, 58, 77, 207, 146, 142, 14, 126, 54, 136, 76, 165, 24, 158, 5, 124, 1, 44, 49, 194, 209, 199, 55, 104, 132, 229, 180, 178, 216, 72, 8, 41, 198, 114, 108, 128, 139, 46, 231, 79, 52, 99, 131, 100, 224, 154, 57, 34, 237, 129, 80, 43, 17, 222, 233, 238, 48, 11, 170, 230, 59, 96, 242, 150, 195, 42, 119, 12, 173, 0, 18, 68, 97, 252, 167, 140, 118, 47, 182, 160, 219, 6, 25, 208, 107, 226, 187, 102, 2, 15, 232, 153, 40, 29, 10, 190, 141, 255, 69, 103, 143, 71, 27, 163, 50, 33, 234, 161, 157, 225, 112, 245, 211, 125, 188, 122, 3, 135, 145, 240, 38, 65, 113, 7, 93, 168, 22, 16, 179, 36, 223, 87, 95, 162, 23, 200, 192, 171, 213, 148, 116, 185, 214, 70, 155, 220, 244, 101, 202, 156, 201, 32, 197, 85, 215, 175, 30, 246, 206, 130, 218, 164, 205, 62, 9, 241, 133, 183, 91, 67, 134, 75, 210, 105, 82, 250, 217, 20, 63, 61, 64, 115, 84, 166, 28, 39, 227, 110, 147, 66, 74, 123, 13, 78, 184, 111, 4, 172, 92, 89, 144, 109, 251, 176, 21, 248, 56, 253};
      int j=0;
      int i=0;
      int temp;
      for(i=0; i< 256; i++)
      {
                T = key;
      }

      for(i=0; i<256;i++)
      {
                j = (T + j +S)%256;
                temp = S;
                S = S;
                S = temp;
      }

      i = j = 0;
      int t;
      for(int r=0; r<0x21;r++)
      {
                i = (i+1) % 256;
                j = (j+S) % 256;
                temp = S;
                S = S;
                S = temp;
                t = (S+S) % 256;
                printf("key = %x %d\n",S,S);
      }
      return 0;

}
```

所以输入会与`0x40,0x28,0xb6,0x5c,0xd3,0x84,0x5c,0x1a,0xe0,0x18,0xfd,0x3f,0x5d,0xce,0xf6,0xbb,0x3d,0x46,0x43,0x82,0x7a,0xa9,0x5c,0xe3,0xe4,0x48,0xfd,0xa5,0xb9,0x39,0x7b,0xd4,0xfe`进行异或。
#### 类BASE32
经过rc4加密后,来到0xF05188。在内存中dump出真正的代码,再写脚本patch。虽然这个函数经过了混淆(各种jmp)但是ida还是能F5出结果。

(其中参数a1为dest,a2为src,a3为src的length)
这左移右移的,加3加4的,再联系到输入长度33,cmp_str的长度44,这不就是base系列吗。

```c
#include <stdio.h>
int main(int argc, char const *argv[])
{
      unsigned char encoded[] ="$HM%/NEX,79PBN\\C/BQLVSW,*/'8T#UMC4%EG@@@,.%5";
      unsigned char result;
      int xor_arr[] = {0x40,0x28,0xb6,0x5c,0xd3,0x84,0x5c,0x1a,0xe0,0x18,0xfd,0x3f,0x5d,0xce,0xf6,0xbb,0x3d,0x46,0x43,0x82,0x7a,0xa9,0x5c,0xe3,0xe4,0x48,0xfd,0xa5,0xb9,0x39,0x7b,0xd4,0xfe};

      int result_ptr = 0;
      int encoded_ptr = 0;
      for(int i = 0; i <44;i++)
      {
                encoded -= 0x23;
      }

      for(int i=0;i<11;i++)
      {
                result = ((unsigned char)encoded <<2) | ((unsigned char)encoded >>4);
                result = ((unsigned char)encoded <<4) | ((unsigned char)encoded >>2);
                result = ((unsigned char)encoded <<6) | ((unsigned char)encoded);
                encoded_ptr += 4;
                result_ptr += 3;
      }
      for(int i = 0; i <33;i++)
      {
                printf("%c",(result^xor_arr));

      }
      printf("\n");
      return 0;
}
//Fr4nk1y_MfC_l5_t0O_ComPIeX_4nd_dl
```
#### 魔改的TEA
check2里面有一个改了delta的TEA加密算法。
首先会验证flag=='f'
sub_C051B5的作用是将8个字符组成2个32位的int。
sub_F05420便是TEA加密了。密钥存在dword_F05048中。

for循环剩下的内容没看太懂,经过动态调试得知是交换密文c0,c1的位置。

```c
#include <stdio.h>
#include <stdint.h>
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t x0=v, x1=v, sum=0x86772b40, i;/* set up */
    uint32_t delta=0x2433B95A;                     /* a key schedule constant */
    uint32_t k0=k, k1=k, k2=k, k3=k;   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
      x1 -= ((x0<<4) + k2) ^ (x0 + sum) ^ ((x0>>5) + k3);
      x0 -= ((x1<<4) + k0) ^ (x1 + sum) ^ ((x1>>5) + k1);
      sum -= delta;
    }                                              /* end cycle */
    v=x0; v=x1;
}

int main()
{
    uint32_t v={0x2d46347f,0x5e79f6f4},k={0x0D9610D02,0x2AADA57D,0x0A37537F1,0x0C29E3913};
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
    printf("原始数据:%x %x\n",v,v);
    decrypt(v, k);
    printf("解密后的数据:%x %x\n",v,v);
    return 0;
}
```
#### 妙用Z3
这个方程涉及到了进位和借位的操作?手算算不出来,经过了2个小时,想到了z3,直接秒出...

```python
from z3 import *

c54 = BitVec('c54',64)
c10 = BitVec('c10',64)
c76 = BitVec('c76',64)

s = Solver()
s.add(c54 - c10 == 0x3F66B755B4490579)
s.add(c10 + c76 == 0x162F924623D2CAE0)
s.add(c76 - c54 == 0x7C3C71F1B295D77F)
s.check()
print(s.model())

```
[c54 = 7830893152465779821,
c10 = 3262352701727045364,
c76 = 16783048594668704748]

再加上c3 = 0x2F9970FF
c2 = 0xDF3634AE
用TEA解密就得到flag。注意字节序。

### flag
Fr4nk1y_MfC_l5_t0O_ComPIeX_4nd_dlff1cUlt_foR_THe_r0Ok1E_t0_REver5e

更新题目链接
链接: https://pan.baidu.com/s/1cY2XeP7CYq0JluTUT6hkBg 提取码: 4seg

多喝咖啡 发表于 2020-12-13 18:39

BXb 发表于 2020-12-13 17:32
什么版本的ida噢。我的简单修下也可以f5,只是没有你的全。

7.0 我当初以为也要patch一下去这个混淆的 没想到直接f5就行

BXb 发表于 2020-12-13 17:32

多喝咖啡 发表于 2020-12-13 17:15
我的ida能直接F5出结果诶 没有patch

什么版本的ida噢。我的简单修下也可以f5,只是没有你的全。

斌斌A 发表于 2020-11-5 16:46

学习了,感谢楼主分享

LittleMatch 发表于 2020-11-6 00:11

感谢楼主分享

碎步流年 发表于 2020-11-6 10:50

学习了,感谢分享

wanshiz 发表于 2020-11-6 11:05

楼主本事高。

re1wn 发表于 2020-11-6 20:49

膜一波师傅

chenyanwjf 发表于 2020-11-7 08:56

学习下,感谢。!!

Li1y 发表于 2020-11-7 10:44

求个原文件

多喝咖啡 发表于 2020-11-7 22:33

Li1y 发表于 2020-11-7 10:44
求个原文件

已更新在正文末尾

TZ弹指之间 发表于 2020-11-9 18:43

楼主本事高。
页: [1] 2 3
查看完整版本: XNUCA2020-RE-UnravelMFC复现