首页
社区
课程
招聘
[原创]l3hctf两道re的wp
发表于: 2021-11-17 11:39 8943

[原创]l3hctf两道re的wp

2021-11-17 11:39
8943

比较菜, 只做了两道, 如果有错误,请师傅们指出

double-joy

找到具体加密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall sub_558A3CFB3D90(__int64 *a1)
{
  __int64 v1; // rcx
  __int64 i; // rsi
  __int64 v3; // rax
 
  v1 = *a1;
  for ( i = *((int *)a1 + 4); ; ++i )
  {
    v3 = *(unsigned __int8 *)(v1 + i);
    *((_DWORD *)a1 + 4) = i + 1;
    if ( (unsigned __int8)v3 <= 0x12u )
      break;
  }
  return ((__int64 (*)(void))((char *)dword_558A3CFB5004 + dword_558A3CFB5004[v3]))();
}

这里是主要是后面的比较关键, 前面的就是取一下指令.

 

传入的参数我把它改成一个结构体, 具体结构体的定义在Structures里面.

1
2
3
4
5
6
00000000 vm              struc ; (sizeof=0x18, mappedto_16)
00000000 opcode          dq ?
00000008 mem             dq ?
00000010 index           dd ?
00000014 point           dd ? //指向内存的索引, 总是指向最后一个值的下一位置
00000018 vm              ends

查看流程图窗口, 是有非常多的分支的, 具体跳转位于

1
2
3
4
text:000055E187261DBD 49 63 04 81 movsxd  rax, dword ptr [r9+rax*4]
.text:000055E187261DC1 4C 01 C8    add     rax, r9
.text:000055E187261DC4             db      3Eh
.text:000055E187261DC4 3E FF E0    jmp     rax

这里我们将每一个rax改为对应的硬编码, 就可以查看微代码了.

 

这里我大概总结了一下要用到的指令作用(具体细节记录得很丑陋, 就不放出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0 加法
1 减法
2 乘法
3 除法(带符号) 这里要注意一定要带符号
5
7 异或
8 2字节内存赋值 mem[mem[point-2]] = mem[point-1]
9 mem[point-1]作为索引, 再对mem[point-1]赋值
a 非
b 判断是否<0
c 对最后两个值交换
f 跳转指令
10 判断跳转
11 改变索引
12 return

然后逐一分析指令集(一共有两套), 可以发现, 两套加密是轮换来的, 并且第一次加密都会有异或,(这也是为什么后面加密其他字节都不改变, 但是头两次加密其他字节都发生变化的原因) 对两个字节的加密, 都需要加密20次才会轮到下两个字节加密. 两套指令分别使用两个结构体和两段内存, 每个内存中都一直存在一个字节, 表示加密次数.

 

解密脚本: (加密过程写在里面了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include<iostream>
 
uint32_t key = 448431333;
uint32_t key2 = 3848427296;
 
int encode1(uint32_t flag[2])
{
    int key1[4] = {0x494C, 0x6F76, 0x6520, 0x4355};
    uint32_t b = (flag[1] * 0x10);
    uint32_t a = ((signed)flag[1] / 0x20);
    uint32_t c = ((a ^ b) + flag[1]);
    uint32_t d = (c ^ (key + key1[key & 3]));// 6f76
    flag[0] = d + flag[0];
    key += 0x75BCD15;
    uint32_t e = (flag[0] * 0x10);
    uint32_t f = ((signed)flag[0] / 0x20);
    uint32_t g = (e ^ f);
    uint32_t h = g + flag[0];
    uint32_t i = h ^ (key + key1[((signed)key / 0x800) & 3]);
    flag[1] = i + flag[1];
    return 0;
}
void decode1(uint32_t flag[2])
{
    int key1[4] = {0x494C, 0x6F76, 0x6520, 0x4355};
    uint32_t e = (flag[0] * 0x10);
    uint32_t f = ((signed)flag[0] / 0x20);
    uint32_t g = ( e^f );
    uint32_t h = g + flag[0];
    uint32_t i = h ^ (key + key1[((signed)key / 0x800) & 3]);
    flag[1] = flag[1] - i;
    key -= 0x75BCD15;
    uint32_t b = (flag[1] * 0x10);
    uint32_t a = ((signed)flag[1] / 0x20);
    uint32_t c = ((a ^ b) + flag[1]);
    uint32_t d = (c ^ (key + key1[key & 3]));// 6f76
    flag[0] -= d;
    return;
}
int decode2(uint32_t flag[2])
{
    key2 -= 0x154CBF7;
    uint32_t b1 = (flag[0] * 0x10 + 0x2074);
    uint32_t c1 = (key2 + flag[0]);
    uint32_t a1 = ((signed)flag[0] / 0x20 + 0x6561);
    uint32_t d1 = (c1 ^ a1);
    uint32_t e1 = d1 ^ b1;
    flag[1] -= e1;
    uint32_t b = (flag[1] * 0x10 + 0x5354);
    uint32_t c = (key2 + flag[1]);
    uint32_t a = ((signed)flag[1] / 0x20 + 0x4f4d);
    uint32_t d = (c ^ a);
    uint32_t e = d ^ b;
    flag[0] -= e;
    return 0;
}
int encode2(uint32_t flag[2])
{
    key2 += 0x154CBF7;
    uint32_t b = (flag[1] * 0x10 + 0x5354);
    uint32_t c = (key2 + flag[1]);
    uint32_t a = ((signed)flag[1] / 0x20 + 0x4f4d);
    uint32_t d = (c ^ a);
    uint32_t e = d ^ b;
    uint32_t f = e + flag[0];
    flag[0] = f;
    uint32_t b1 = (flag[0] * 0x10 + 0x2074);
    uint32_t c1 = (key2 + flag[0]);
    uint32_t a1 = ((signed)flag[0] / 0x20 + 0x6561);
    uint32_t d1 = (c1 ^ a1);
    uint32_t e1 = d1 ^ b1;
    uint32_t f1 = e1 + flag[1];
    flag[1] = f1;
    return 0;
}
int main()
{
    uint32_t flag[10] = { 0xAEE0FAE8, 0xFC3E4101, 0x167CAD92, 0x51EA6CBE, 0x242A0100, 0x01511A1B, 0x514D6694, 0x2F5FBFEB,
    0x46D36398, 0x79EEE3F0};
 
    for (int i = 8; i >= 0; i-=2)
    {
        for (int j = 19; j >= 0; j--)
        {
            decode2(flag + i);
            if (i == 0 && j == 0)
            {
                flag[0] ^= 0x1010101;
                flag[1] ^= 0x2020202;
            }
            decode1(flag + i);
            if (i == 0 && j == 0)
            {
                flag[0] ^= 0x1010101;
                flag[1] ^= 0x2020202;
            }
            int m = 3;
        }
        int n = 3;
    }
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 4; j++)
            printf("%c", *((char *)&flag[i] + j));
    }
    return 0;
}

load

这里打开了一个文件映射, 并且把输入复制到文件中:

1
2
3
FileMappingW = CreateFileMappingW(0xFFFFFFFF, 0, 4u, 0, 0x400u, Name);
v7 = MapViewOfFile(FileMappingW, 0xF001Fu, 0, 0, 0x400u);
strncpy(v7, Source, 0x3FFu);

接下来创建了一个进程, 进程对输入进行处理.

 

这里的进程我没有去dump, 我dump也发生了错误, 所以我直接用ida去附加这个进程, 不管具体子进程如何变化基本上都能分析.在恢复线程执行的函数下好断点, 保证主进程的处理都执行过了. 然后附加好了之后, 选中所有的load.exe段, 让ida分析, 就可以找到关键函数.下好断点, 直接运行, 转过来单步主进程, 就可以在断下来了(注意, 这里会有一个软件断点, 直接pass给进程就可以了)

 

读取从文件中读取输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v0 = kernel32_OpenFileMappingW(983071, 0, aL3hsec);
 vcruntime140_memset(v26, 0, 1024);
 if ( v0 )
 {
   v1 = kernel32_MapViewOfFile(v0, 983071, 0, 0, 0);
   v2 = (char *)v1;
   do
   {
     v3 = *v2++;
     v26[(_DWORD)v2 - v1 - 1] = v3;
   }
   while ( v3 );
   kernel32_UnmapViewOfFile(v1);
   kernel32_CloseHandle(v0);
 }

字符串每两字节转换为16进制,类似于atoi函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
do
    {
      v6 = *((_BYTE *)&v30 + v5);
      if ( v6 >= '0' && v6 <= '9' )
      {
        v6 -= '0';
        *((_BYTE *)&v30 + v5) = v6;
      }
      if ( (unsigned __int8)(v6 - 'a') <= 5u )
        *((_BYTE *)&v30 + v5) = v6 - 'W';
      ++v5;
    }
    while ( v5 < v4 );

接下来大概就是通过伴随矩阵求逆矩阵

 

A* / |A| = A-1

 

这里需要用到一个代数余子式的东西, 具体都是线代里面的内容

 

a1 a2 a3

 

a4 a5 a6

 

a7 a8 a9

 

tmp = a1 (a5 a9 - a8 a6) - a2 (a4 a9 - a7 a6) + a3 (a4 a8 - a7 * a5)

 

m1 = (a5 a9 - a8 a6) / tmp

 

m2 = -(a4 a9 - a7 a6) / tmp (符号分别正负顺序(根据位置决定))

 

.......

1
2
3
4
sub_401070(3, (int)v34, 0);                   // v34赋值
 sub_401070(2, (int)v25, 9);                   // v25赋值
 sub_401370(v34, 3);                           // 矩阵运算
 sub_401370(v25, 2);

解密: 矩阵的逆矩阵只需要再求一次逆矩阵就是本身了.

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
 
= np.array([
[1,0,-9],
[0,-1,-6],
[-1,-2,-4]])  # 初始化一个非奇异矩阵(数组)
# print(np.linalg.inv(a))  # 对应于MATLAB中 inv() 函数
 
# # 矩阵对象可以通过 .I 更方便的求逆
A = np.matrix(a)
print(A.I)

flag:

 

flag{f812f706f306ff02ff0dfde207}


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-11-17 11:44 被margina1编辑 ,原因:
上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币:
活跃值: (191)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
不错不错
2021-11-23 11:39
0
游客
登录 | 注册 方可回帖
返回
//