逆向手的自我修养
逆向魔法师学习法术的目标(去想,去学,去征服)
- des加密(已解决)
- frida的使用,安卓逆向
- 去平坦化
- aes加密
- 混淆
- 反调试
- sm4
- go与rust的处理
- linux内核与gdb
- z3, dfs, smc
还有每日的练题,语言和wp学习
1.try-catch与结构化异常处理(SEH)(全称[Structure Exception Handler]
seh是windows系统对c/c++程序做的语法拓展,用于处理异常事件的程序控制结构,
#include <windows.h>//包含windows中的api与seh的相关定义
seh含有-try{}-catch函数
与-try{}_final函数
由于ida的反编译语言是c/c++,于是我了解一下c/c++中的seh
主要是(windows的seh检验)(块检验)
1 | struct _EXCEPTION_REGISTRATION_RECORD { |
什么是链表:像链条一样存储结构,与结构体不同的是结构体中多了个结构体指针,A结构体存了一些结构体成员还存了B结构体指针,
B结构体存了相同的结构体成员和指向C的结构体指针,以此类推
下面举例说明struct * p, *head, * tail(链表有头有尾, head标记头节点, tail标记尾节点)
1 | struct *p, *head, *tail; |
一个节点是一个有异常处理能力的作用域
1 | try {</p> |
1 | catch (const std::exception& e) |
异常有不同类型
应用
1.异常传播
如果一个函数中的try-catch没有捕获到异常,异常会向上抛给调用者,直到被合适的catch块捕获或导致程序终止。
(try抛出的异常类型与catch的类型不匹配)
2.多catch块
可以有多个catch块来捕获不同类型的异常,按照从上至下的顺序匹配。
忽略异常传播(上层调用异常处理无法判断)
不恰当catch块
2.des加密
des是经过festial网络的块加密,并且对于标准的中字节处理是大端序处理,而des加密是对称加密,des处理字节的方式是大端序,接受64位明文和64位密文返回64位明文
那如果不够64位,根据一定的填充法填充64位
什么是festial网络?
1.将明文分为左右两个部分
2.将右半部分作为输入,通过f函数与子密钥进行运算
3.将左面的明文与f函数的输出异或,生成新的有半部分
4.左右部分进行交换,进入下一轮
PKCS#7 方案
PKCS填充方法
- 计算需要填充的字节数
pad_len = block_size - (len(data) % block_size) - 每个填充字节的值都等于
pad_len - 如果数据长度恰好是块大小的整数倍,则额外填充一个完整的块(这是因为des解密会检索最后一个数据:解密算法可以总是去除最后一个字节的值作为填充长度,然后移除相应数量的字节)
比如“abcde”填充9-len(”abcde“)%9 = 4
即填充\x04
密钥生成机制
文献参考
密钥是64位,但是实际上真正的密钥是56位,因为原密钥有8位是校验位(每字节最后一位为校验位,保证1出现的次数为奇数)
之后这个密钥再拆分成左右两个28位的密钥
1 | pair<uint32_t, uint32_t> PC1(uint64_t key) //pair<uint32_t, uint32_t>在utility头文件,返回相当于c结构体有两个结构变量uint_t的数据类型 |
两个生成的28位子密钥先要左旋特定位数(循环左移)(右旋就是循环右移)
pc2中有一个盒决定选择哪48位作为密钥
新的子密钥由上一个生成
1 | void keyGen(pair<uint32_t, uint32_t> keypair) |
至此生成了16个48位密钥
至此密钥已成,将明文分为左右32位进入feistel网络
接下来为加密代码
1 | void desEncrypt(uint64_t * plain, uint64_t key) |
开始时对明文进行ip置换之后加密后对密文进行fp置换得到最后的密文,当然这仅仅只是简单的映射(一 一对应关系,一个集合的元素对应另一个集合的元素)
接下来就是ip与fp置换
1 | uint64_t IP (uint64_t message) |
feistel轮函数如下所示
1 | uint32_t feistel(uint32_t a, uint64_t key) |
拓展与映射
先通过e盒拓展到48位与密钥异或,之后通过映射成32位,再由P盒最后一次置换输出
1 | uint64_t expand(uint32_t a) |
接下来要有48位变为32位,这是映射关系
48是8*6而32是8乘以4
48位会每6位进入一个s盒(4行*16列),例如:(总共8个s盒)
第 1-6位(总共48位中的前6位) → 输入给 S盒1
第 7-12位 → 输入给 S盒2
每块的第一个和第六个位用于索引行,其他4个位用于索引列,最会得到一个32位输出
1
2
3
4
5
6
7
8
9
10
11uint64_t S(int64_t a)
{
uint32_t res = 0;
for(int i = 0; i<8; i++)
{
uint8_t row = (((a>>(i*6))&0x20)>>4)|((a>>(i*6))&1);
uint8_t col = ((a>>(i*6))&0x1E)>>1;
res |= S_box[i][row*16+col]<<(4*i);
}
}P置换就是再次打乱S盒的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17uint32_t P(uint64_t a)
{
uint8_t p[] = {16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25};
uint32_t afterP = 0;
for(int i = 0; i<32; i++)
{
afterP |= ((a>>(32-p[i]))&1)<<(31-i);
}
return afterP;
}
小结一下
1.des加密先由pc1函数由64位去校验位并分为左右两个28位的密钥部分
2.之后进入keygnerate函数进行16循环,每个循环中先左旋特定位数,之后合成56位进行密钥生成
3.接下来明文先进行ip置换,,之后将64位明文分为两个32明文,此后在这进行16次轮加密,每轮后对右面的32位密钥先进行e盒拓展再映射到s盒子在进行p盒置换
4.即将结果first与seond部分交换后返回,共16轮
5.最后将secnd部分提到高32位之后进行fp置换得到密文
特征
由于des加密十分庞大,所以可以通过观察不变的盒子来判断
S盒
S盒引入了非线性操作,且S盒的每一位都是设计抵抗差分和线性密码分析,一般不会修改S盒
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
35uint8_t S_box[8][64] =
{{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13},
{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9},
{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12},
{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14},
{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3},
{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13},
{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12},
{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}};p盒
p盒子的特殊设计使得每轮由同一s盒子输出的4个bit位在下一轮都能分散到4个不同的s盒进行处理
1
2
3
4
5
6
7
8
9
10uint8_t p[] =
{16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25};
e盒是将32位拓展为48位,增强混淆能力
1 | uint8_t e[] = |
但是des加密已经不安全,且加密严重依赖系统程序,且des加密为大端序,现代大多数计算机多为小端序,所以难以实现
由于为对称加密,只要知道16个子密钥就可以根据feistel网络特性将密文当输入,密钥倒着用反推回去就
3.base58
base58是在base64上去除了比较混淆的字符(0和O,小写字母l与大写字母I,+与*)
剩下的按09AZa~z排列
无法用整字节转换,所以要将一串转为base58
常见到len*138/100
这其实是len*log(256) = log(100)乘以base58所需的最大长度
base58加密的关键一步是大数除法
怎么解大数除法,这就要模拟,什么是模拟,模拟就是在纸上算然后将思路以计算机语言实现
输入是大多是字符串,例如256进制的有0x01, 0xfa, 0xff那么让他们除以两个在一起除58,得到的余数传给下一个
就类似数学中的除法,取余,这里可以想一下计算机中的十进制和数学上的除法就可以理解了
接下来看base58加密与解密
1 | #include <stdio.h> |
4.python逆向
1.常见的情况(pyc文件介于源码与字节码之间,pyd文件是动态链接库)
一.pyc文件这届uncompyle6反编译(进到python3.8版本)
二.给了个txt文件里面是pyc字节码
1.读取py字节码2.根据opcode文件查询意思
三.exe打包的py文件
1.通过脚本变为结构体和文件2.再把时间属性和魔法数字放回去保存3.uncompyle6
四.pyc加花
1..uncompyle6和字节码判断是否加花2.读取co_code的长度3.去掉花并修改co_code长度4.uncompyle6
2.pyc文件直接uncompyle6反编译
1 | python -m xx.py # m为module,直接将python模块作为脚本运行,生成pyc文件 |
而在命令符中python .\xxx.pyc可以直接运行
3.二.给了个txt文件里面是pyc字节码
python3的开头前4个字节为魔法数字,之后4个字节为位字段(bit field),
接着4字节为时间戳,再4字节为源文件文件大小,然后为序列化的代码对象
接下来是对位字段的解释,位字段是为了能够复现编译的结果
根据python的官方文档
1 | 如果位字段为 0,则该 pyc 是传统的基于时间戳的 pyc |
有操作数指令:3 字节**(1字节操作码 + 2字节操作数)
Python 3.0-3.5
- 依然使用 3 字节指令格式
Python 3.6+
- 改为 2 字节指令格式:
- 第1字节:操作码
- 第2字节:参数(如果指令需要参数)
010editor打开pyc文件python2的前8位是python2的魔法数字,python3是前16位
1 | import dis, marshal //marshal库用来装载,dis用于反编译 |
1 | 0 RESUME 0 |
LOAD_CONST将值推放到栈顶,
load_fast把这个加载局部变量
load_global是将全局变量或内置函数推到栈顶
LOAD_NAME 将关联值推送到堆栈
STORE_FAST存储局部变量(将tos(栈)储存将存到本地)
GET_iter(获取迭代器)FOR_ITER(执行迭代器)
LOAD_FAST`临时从局部变量中加载到栈中
SETUP_LOOP 28 (to 30)//循环开始,28是相对偏移30是绝对指令位置(当前指令位置+相对偏移)
BINARY_SUBTRACT tos = tos1 - tos(tos为栈顶tos1为栈的下一位)
BINARY_MOUDULO tos = tos1 % tos
BINARARY_RSHIFT num(tos = tos1>>tos) //之后原来的tos1和tos替换为新的tos
4.将一堆pyc文件打包成exe

看图标知道是pyc打包的程序,并非标准exe文件,是有着exe头戴着python解释器的,以pyc文件为基础的exe文件,也当然就不用ida
先修头,头文件的二进制在0xE3之前(#define TYPE_CODE 0xE3,0xE3是marshal模块对代码对象的类型标记)
直接解包python pyinstxtractor.py “绝对路径”,这里由于头被修改
python文件打包成exe文件会将pyc文件的部分信息抹去而这些信息都在struct文件中
5.加花的pyc
1..uncompyle6和字节码判断是否加花(加花会报错)
2.读取co_code的长度(指令的长度由字节码指令序列决定,指令增减后都要改大小)
len(code.co_code)一个操作3字节
3.去掉花并修改co_code长度
先uncompyle6反编译看花是什么,然后找到操作码对应的的16进制数,然后删去()
之后根据操作在内存的操作数值为多少在010editor找到对应的,修改对应的值(len-删去的字节)
opcode可在python文件夹的include文件夹opcodexxx.h文件下
4.uncompyle6
5.mfc逆向
mfc程序是根据c++的mfc库编写的程序,主要是使用xspy去解决问题
mfc要找程序基址与函数的偏移

主要是找message map=0x0000000140004E80(201500_ezre_dump_SCY.exe+ 0x004e80 )(消息映射表区域)
关注确定按钮即oncommand响应按钮(处理菜单、控件和快捷键)
接着找到偏移
然后使用detect it easy软件找到基址
由于mfc是消息映射,可通过导入结构体办法还原
接着开始导入结构体,在view菜单栏下的open subview->local type(本地类型)
用于存储数据库的基本类型(如struct,enum(枚举),union(联合体),typedef别名,指针类型)
插入两个符号表
1 | struct AFX_MSGMAP_ENTRY |
1 | struct AFX_MSGMAP |
在c syntax板块插入这两个结构体(自动识别为struct)
只好找到消息映射表的地方(基址+偏移)

直接alt+q先导入AFX_MSGMAP
得到 stru_140004E80 AFX_MSGMAP <offset ?GetMessageMap@CDialogEx@@MEBAPEBUAFX_MSGMAP@@XZ,
.text:0000000140004E80 ; DATA XREF: sub_140001600↑o
.text:0000000140004E80 offset qword_140004E90> ; CDialogEx::GetMessageMap(void)
而 offset qword_140004E90> 正是消息处理函数
有几个消息处理函数就导入几个AFX_MSGMAP_ENTRY(关注id和消息处理函数)

然后就找到了
6.aes加密
今天也是来学习aes加密(aes加密本来上半年例会内容,今天也是来学习相关内容)
先回顾一下上面的des加密,明文是64字节,密钥是64字节,密钥先变为两个28字节之后左旋然后变为48字节
明文先ip置换,之后通过festial网络加密,然后fp置换得到最后结果
当然现在des加密已被证明为不安全的,aes加密就是取代的des加密的,其为经典的块加密算法,可以使用128bit(16字节),192bit(24字节)与256bit(32字节)三种长度的密钥,而des加密仅仅是64bit(4字节的密钥),aes的数据块固定为128字节(16字节),
整个加密在4*4的矩阵(16字节),aes加密基于代换-置换网络,主要操作有轮密钥加、字节代换、行位移、列混合四种,其数值运算的相关操作都是定义在GF(28)//伽罗瓦域(计算机尽头是数学)
在这样一个有限域主要是防止数值溢出,aes使用大端序(高位地址)
GF(伽罗瓦域/有限域)
有限域顾名思义是有限的域,域又是什么域是一种具有加法和减法的集合,其可以进行加、减、乘、除(除了0)运算,其具有乘法的结合律,但是其加法和乘法的与四则运算的加法和乘法有有些不同
1.封闭性:对任意 a,b∈Fa,b∈F,a+b∈Fa+b∈F,a⋅b∈Fa⋅b∈F
2.交换律:对任意 a,b∈F,a+b=b+a,a⋅b=b⋅a(突然想到了一般的矩阵不具有交换性,可逆矩阵是有交换性的)
3.结合律:对任意 a,b,c∈Fa,b,c∈F,(a+b)+c=a+(b+c)(a+b)+c=a+(b+c),(a⋅b)⋅c=a⋅(b⋅c)(a⋅b)⋅c=a⋅(b⋅c)
4.分配律:对任意 a,b,c∈Fa,b,c∈F,a⋅(b+c)=a⋅b+a⋅ca⋅(b+c)=a⋅b+a⋅c
5.单元存在性:存在0,1∈F,使得对任意 a∈Fa∈F,a+0=a,a⋅1=a
6逆元存在,对任意 a∈Fa∈F,
存在 −a∈F,使得 a+(−a)=0(加法逆元)
若 a≠0,存在a-1∈F,使得 a⋅a-1=1(乘法逆元)
轮密钥加
二进制中的异或相当于数学中的加发
0+0 = 0
0+1=1
1+0=1
1+1=0
文献参考
特征
以AES128为例子,在主循环中依次执行字节替换、行位移、列混合、轮密钥加四个操作共9轮,第10轮不进行列混合(des仅有ip、fp置换还有密钥生成),不过编译器可能将这个加密操作优化的面目全非,逆向是不一定能看出来
其次是s盒,aes算法有专门为字节代换的256位s盒,标准s盒如下
1 |
|
还有十个轮常量用于密钥拓展
标准轮常量
1 | const uint32_t Rcon[10] = {0x01000000, 0x02000000, |
数值溢出前是后一个是前一个的两倍
实现
AES首先进行密钥拓展,这与des加密相同,AES由原来的4个uint32_t为基础,拓展出10轮(毕竟是10轮加密),总计44个共11组密钥,因为加密是在4*4的矩阵(果然计算机离不开数学)上进行操作,且矩阵是列优先排列的(先填满一列再填满下一列),所以先将密文按快转化为矩阵加密完成后再转换回来
s0, s1, s2, s3, s4 ……s15, s16
变成
| s0 | s4 | s8 | s12 |
|---|---|---|---|
| s1 | s5 | s9 | s13 |
| s2 | s6 | s10 | s14 |
| s3 | s7 | s11 | s15 |
一开始先进性初始密钥加操作,然后循环9轮,最后进行第十轮,第十轮不进行列混合
一下为AES加密的主函数和状态矩阵的生成函数
1 | void convertToStateArry(uint8_t s[16], uint8_t a[4][4]) |
字节拓展操作首先把密钥按大端序转为32位字W0W3,按照以下规则拓展出W4W43
**如果是每组的开头(i 能被 4 整除):**W[i] = W[i-4] ⊕T(W[i-1])
**其他情况(i 不能被 4 整除):**W[i] = W[i-4] ⊕ W[i-1]
T是密钥拓展所用的轮函数,由字节偏移,字节代换和轮常量异或三个操作组成
1 | uint32 T(uint32_t w, int round) |
轮密钥加操作将对应的密钥与数据进行异或,因为在GF(256)下的加法与异或或等价
(其中的每一个数可表示为(1或者0)*x7+0*x6+…….+0/1*x0,为了在这里面对于加法封闭,所以加法要模2,这就使得其加法与异或效果相同)
1 | void addRound(uint8_t sArry[4][4], int round) //轮密钥加 |
行位移就是将矩阵第一行不动,第二行循环左移1,第三行2位,第三行3位,可以直接模拟(心中想一下怎么出来的)
1 | void shiftRow(uint8_t sArry[4][4]) |
鸽了好几天了,接着写(开学前完了一会,今天那晚下学期资料就来学AES加密,不过有点小忘了)
字节代换就是取一个字节高四位作为行号,低四位作为列号在s盒中查表(上面密钥生成函数也有字节代换)
1 | void subbytes(uint8_t sArry[4][4]) |
列混合操作是使用事先准备的4*4矩阵和当前矩阵进行矩阵乘法,GF(2^8)下的乘法满座交换律、结合律、分配律,其与二相乘可等价
1.左移1位
2.若果做以前最高位为1,则与0x1B异或
(在AES加密中存在不可约多项式,为了封闭性要始终保持在一个字节内,要选择一个不可约多项式取模, P(x) = x8 + x4 + x3 + x + 1,其使用16进制表示为0x11B,如果溢出则会要模P(x),使用异或(相当于减法)得到x4 + x3 + x + 1,也就是0x1B,而异或0x1B相当于把溢出的减去)
所以只要实现乘2就可以实现所有情况
1 | uint8_t GFMUL(uint8_t s, int n) |

过程就是先将明文转换为4*4的状态矩阵,遵循列优先排列,将128位的密钥变为4个32位(大端序),然后进行拓展(若果是%4为0则进行T函数,T函数中有字节偏移(循环左移1字节),之后用s盒字节替换,最后异或轮常量),生成10组密钥,然后主函数先进行一次轮密钥加,其实就是状态矩阵与初始密钥异或,然年进行九次循环,先字节代换再行位移,然后列混合,最后轮密钥加,字节代换
其实就是将状态矩阵的每个元素的高4字节为行低四字节为列用s盒替换,行位移其实就是矩阵第二行整体循环左移一个,第三行循环左移两个,第四行循环左移三个,而列混合就是矩阵的乘法,然后在GF域下的加其实就是异或
完整加密代码
1 |
|
解密
因为是对称加密,AES的每一步都可以实现逆运算(s盒与逆s盒涉及一些数学知识,以后再学,先说一下前置的知识仿射变换与幂运算,还有半群、群、环、域)
逆字节替换
根据s盒准备个反s盒,之后查表替换
1 |
|
逆行位移
循环左移改为循环右移(0行不动,一行动一,二行动二,三行行三)
1 | void deShiftRows(uint8_t sArry[4][4]) |
逆列混合
矩阵乘法,所以仅需colM矩阵的逆矩阵
1 | const uint8_t delColM[4][4] = 0xe, 0xb, 0xd, 0x9, |
轮密钥加就是简单的异或
总的解密
1 | const uint8_t S2[16][16] = {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, |
7.矩阵求逆
定义法矩阵求逆
由于A的伴随矩阵等于A的行列式乘以A的逆矩阵
所以现要求出A的代数余子式,然后转置求出伴随矩阵
$$
A^-1 = A^*/|A|
$$
