base64
1.编码原理
将每3个8位的字节(共24位)重新分组为4个6位的单元
然后将每个6位单元映射到一个特定的可打印字符。
过程:
将每个字符转换为ASC码的二进制值
按6位一组进行分割
用索引表进行映射
如果原始数据的字节数不是3的倍数,就需要进行填充,使用 = 字符
数据字节数 % 3 == 0 -> 无填充
数据字节数 % 3 == 1 -> 补足2字节,编码结果末尾加 ==
数据字节数 % 3 == 2 -> 补足1字节,编码结果末尾加 =
索引表
| 索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 |
|---|---|---|---|---|---|---|---|
| 0 | A | 16 | Q | 32 | g | 48 | w |
| 1 | B | 17 | R | 33 | h | 49 | x |
| 50 | y | 18 | S | 34 | i | 51 | z |
| 3 | D | 19 | T | 35 | j | 52 | 0 |
| 4 | E | 20 | U | 36 | k | 53 | 1 |
| 5 | F | 21 | V | 37 | l | 54 | 2 |
| 6 | G | 22 | W | 38 | m | 55 | 3 |
| 7 | H | 23 | X | 39 | n | 56 | 4 |
| 8 | I | 24 | Y | 40 | o | 57 | 5 |
| 9 | J | 25 | Z | 41 | p | 58 | 6 |
| 10 | K | 26 | a | 42 | q | 59 | 7 |
| 11 | L | 27 | b | 43 | r | 60 | 8 |
| 12 | M | 28 | c | 44 | s | 61 | 9 |
| 13 | N | 29 | d | 45 | t | 62 | + |
| 14 | O | 30 | e | 46 | u | 63 | / |
| 15 | P | 31 | f | 47 | v |
2.常见变形
修改编码表 :
将 Base64 的编码表替换成另外一种编码表->将表的数据替换成原来的表即可实现解密
修改下标:
将 Base64 的编码查表下标对应关系修改->推导出下标逆计算即可
实现方法变形:
将原始数据转成大整数,利用进制256->64计算base64编码
3.逆向方法:
常量表:提供了Base64编码的索引表
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
在代码中的体现:
.rdata:00503E4F db 0 .rdata:00503E50 aAbcdefghijklmn db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', .rdata:00503E50 ; DATA XREF: .data:off_529000↓o .rdata:00503F91 align 10h
查表(含有0x3F):核心的3字节到4字节的转换与编码过程
在代码中的体现:
4*(_BYTE *)(v8 + a2) = '='; *(_BYTE *)(++v8 + a2) = '='; v6 = v8 + 1; *(_BYTE *)(v9 + a2) = off_529000[v5]; v10 = v9 + 1; *(_BYTE *)(v10 + a2) = '=';
Padding计算:处理输入数据不是3字节倍数时的末尾填充
用 = 填充末尾,标记无效数据,应对非3倍数输入
在代码中的体现:
*(v6 + a2) = off_529000[(a1[v12] >> 2) & 0x3F];*(v7 + a2) = off_529000[(a1[v12 + 1] >> 4) & 0xF | v4];*(v9 + a2) = off_529000[(a1[v12 + 2] >> 6) & 3 | v5];
改表:
找index下标:从密文字符反推出编码时使用的索引值
应对方法:
建立索引映射关系 分析自定义表,数密文每个字母对应index多少
将索引转为6位二进制
将4个6位重组为3个8位字节
密码算法常量特征识别
1.什么是特征常量 :
在逆向过程中,经常遇到一些开源的代码,如果能找到这些开源代码,可以减少许多逆向工作量
如何寻找某段代码的开源代码:使用特征常量作为关键字搜索。 特征常量是算法中用到的特征整数或字符串或字节数组等
常见特征常量: 1. 整数特征 2. 字符串特征 3. 字节数据特征 如果我们能找到这些开源代码的那个源码
2.常见加密算法的特征常量
*处理时把十进制和十六进制都试一遍
AES 算法特征常量(S盒):
db 63h, 7Ch, 77h, 7Bh, 0F2h, 6Bh, 6Fh, 0C5h, 30h, 1, 67h b ; DATA XREF: sub_1400012A0+5E↑o db 2Bh, 0FEh, 0D7h, 0ABh, 76h, 0CAh, 82h, 0C9h, 7Dh, 0FAh db 59h, 47h, 0F0h, 0ADh, 0D4h, 0A2h, 0AFh, 9Ch, 0A4h, 72h db 0C0h, 0B7h, 0FDh, 93h, 26h, 36h, 3Fh, 0F7h, 0CCh, 34h db 0A5h, 0E5h, 0F1h, 71h, 0D8h, 31h, 15h, 4, 0C7h, 23h db 0C3h, 18h, 96h, 5, 9Ah, 7, 12h, 80h, 0E2h, 0EBh, 27h db 0B2h, 75h, 9, 83h, 2Ch, 1Ah, 1Bh, 6Eh, 5Ah, 0A0h, 52h db 3Bh, 0D6h, 0B3h, 29h, 0E3h, 2Fh, 84h, 53h, 0D1h, 0 db 0EDh, 20h, 0FCh, 0B1h, 5Bh, 6Ah, 0CBh, 0BEh, 39h, 4Ah db 4Ch, 58h, 0CFh, 0D0h, 0EFh, 0AAh, 0FBh, 43h, 0Dh, 33h db 85h, 45h, 0F9h, 2, 7Fh, 50h, 3Ch, 9Fh, 0A8h, 51h, 0A3h db 40h, 8Fh, 92h, 9Dh, 38h, 0F5h, 0BCh, 0B6h, 0DAh, 21h db 10h, 0FFh, 0F3h, 0D2h, 0CDh, 0Ch, 13h, 0ECh, 5Fh, 97h db 44h, 17h, 0C4h, 0A7h, 7Eh, 3Dh, 64h, 5Dh, 19h, 73h db 60h, 81h, 4Fh, 0DCh, 22h, 2Ah, 90h, 88h, 46h, 0ECh db 0B8h, 14h, 0DEh, 5Eh, 0Bh, 0DBh, 0E0h, 32h, 3Ah, 0Ah db 49h, 6, 24h, 5Ch, 0C2h, 0D3h, 0Ah, 62h, 91h, 95h, 0E4h db 79h, 0E7h, 0C8h, 37h, 6Dh, 8Dh, 0D5h, 4Eh, 0A9h, 6Ch
//AES算法:
AES:高级加密标准
核心目标:将一段原始数据(明文),通过一系列复杂的数学变换,转换成乱码(密文)。没有密钥的人无法从密文还原出明文。
S盒
唯一任务:把一个字节的值,替换成另一个完全不同的值,是一个包含256个数字的固定表格。在加密过程的“字节替换”步骤中,AES算法会取出数据中的每一个字节,以这个字节的值作为索引,去S盒里查找对应的新值,然后用这个新值替换掉原来的字节。
工作原理(举例说明):
当前正在处理的一个字节的值是 0x53
查找:算法将这个值 0x53 作为索引,去S盒中查找第83项
替换:根据标准的AES S盒,索引 0x53 对应的值是 0xED
结果:原来的 0x53 就被替换成了 0xED
这个过程对加密过程中的每一个字节都会执行一次
在IDA看到连续的256个字节数据时:
这个程序使用了AES加密
需要去代码中寻找引用这个S盒数据的函数,那些函数很可能就是加密或解密函数
分析这些函数,找到加密时使用的密钥和加密的模式
MD5 算法初始常量
weak void md5Init(Md5Context *context) { //Set initial hash value context->h[0] = 0x67452301; context->h[1] = 0xEFCDAB89; context->h[2] = 0x98BADCFE; context->h[3] = 0x10325476; //Number of bytes in the buffer context->size = 0; //Total length of the message context->totalSize = 0; }//MD5算法:
是一种广泛使用的密码散列函数,为任意长度的数据(如字符串、文件)生成固定长度的数字
MD5特点:
固定长度输出:计算出的结果永远是 128位通常表示为32个十六进制字符
确定性:同样的输入,无论在任何计算机上、计算多少次,得到的MD5值都完全相同
输入的微小改变会导致输出的MD5值发生巨大的改变
不可逆性:从输出的MD5哈希值不可能反向推导出原始的输入数据,是一个单向的函数
MD5的密码学弱点:
碰撞攻击
不同的输入数据,产生了相同的MD5输出,从而绕过文件完整性检查
进行蛮力破解或查表,找到对应某个MD5值的原始密码
识别MD5:
在代码中看到一些魔数常量,如0x67452301, 是MD5算法初始化时使用的标准常数;
看到一个函数生成一个32位的十六进制字符串,很可能是MD5
解题思路:
如果程序将输入进行MD5计算,然后与一个固定的MD5值进行比较。
找到一个输入,使其MD5值等于目标值。
方法:查在线数据库;利用碰撞
CRC32 常量表
static const unsigned int crc32_table[] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, ……//CRC算法:用于检测数据错误的算法
作用:验证数据在传输或存储过程中是否发生了意外改变,数据被篡改,其 CRC32 值极大概率会发生改变
基本原理:CRC32 是逐字节计算,对于一个给定的字节,它与当前的 CRC 值进行计算后,会得到一个新的 CRC 值。
预计算:一个字节有 256 种可能(0x00 ~ 0xFF),可以预先为这 256 种可能的输入字节,计算出它们对应的 CRC32 结果
查表:
实际计算数据流的 CRC32 时只需要:
取出数据的一个字节
以这个字节为 索引,去常量表中查找对应的预计算值
通过一个简单的操作(通常是异或)将这个预计算值与当前的 CRC 值结合
CRC32 计算函数使用查表法,代码应与下面相似;
uint32_t calculate_crc32(const uint8_t *data, size_t length) { uint32_t crc = 0xFFFFFFFF; for (size_t i = 0; i < length; i++) { uint8_t table_index = (crc ^ data[i]) & 0xFF; crc = (crc >> 8) ^ crc32_table[table_index]; } return crc ^ 0xFFFFFFFF; }表现:
初始化
遍历数据的每一个字节 。
将当前 CRC 值的低 8 位与数据字节进行异或,得到一个 0~255 的索引
将当前 CRC 值右移 8 位,抛弃最低的那个字节,然后与从常量表中查到的值进行异或
处理完所有数据后,对最终的 CRC 值取反后返回
识别CRC32:
一个 256 * 4 = 1024 字节的常量数据区:一个很大的数组,里面有 256 个DWORD数据。
特定的初始值和最终异或值,出现 0xFFFFFFFF 作为初始值或用于最终异或
特征循环结构:
从数据中取一个字节
AND 0xFF` 操作(计算索引)
右移 8 位(
SHR或>> 8)的操作从大数组中查表的操作
异或(
XOR)操作
除了加密算法之外,其他代码的特征常量也可以用于源码查找:
数据库系统:
sqlite3
魔数:数据库文件的前16个字节是:SQLite format 3\000
系统表sqlite_master
SQL语句:大量标准的SQL关键字
mysql/mariaDB客户端库
连接握手:数据包中包含特定的协议版本号
错误码:特定的错误码常量,如 CR_UNKNOWN_ERROR, CR_SERVER_GONE_ERROR
压缩库:
zlib / gzip / DEFLATE
压缩头:
zlib: 0x789C 或 0x78DA
gzip: 文件头以 0x1F 0x8B 开始
Adler-32 校验和:zlib使用的校验算法
LZMA / 7-Zip
魔数:压缩数据流通常以 0x5D 00 00 开头
可执行文件格式
PE 文件
DOS 头:MZ(0x4D 0x5A)
PE 签名:PE\0\0 (0x50 0x45 0x00 0x00)
ELF 文件
魔数:0x7F 'E' 'L' 'F'(0x7F 0x45 0x4C 0x46)
Mach-O 文件
魔数:0xFEEDFACE(32位) 或 0xFEEDFACF (64位)
多媒体格式
图片格式
JPEG: 0xFF 0xD8 0xFF
PNG: 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A
GIF: GIF89a 或 GIF87a
音频/视频格式
MP3: 可能以 ID3 标签开始,或帧同步字 0xFF 0xFB
WAV: RIFF头和 WAVE 格式标识符
网络协议
HTTP
方法:GET, POST, PUT, DELETE 等
版本:HTTP/1.1, HTTP/2
头部字段:User-Agent:, Content-Type:, Cookie:等
TLS/SSL
握手协议:0x16 0x03 开始的记录层头。
ClientHello:包含 TLS 版本信息如 0x03 0x03 (TLS 1.2)
编程语言运行
Python
pyc 文件魔数:不同版本的Python有不同的魔数,如 Python 3.6 的是 0x42 0x0D 0x0D 0x0A
字符串:大量与Python内部相关的字符串,如 _main_, _name_, sys, import, print
Java (JVM)
Class 文件魔数:0xCA 0xFE 0xBA 0xBE
字符串:java/, javax/ 等包名前缀
操作系统相关
Windows API
Registry Hive:regf 签名
错误码:ERROR_ACCESS_DENIED, ERROR_FILE_NOT_FOUND 等
GUID/CLSID:特定的COM类标识符,格式为 {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
Linux / Unix
配置文件路径:/etc/passwd, /etc/shadow
系统调用号:特定的系统调用数字
游戏开发相关
Unreal Engine
Package 文件:以 0x9E2A83C1 开头
字符串:CoreUObject, Engine 等命名空间
其他常见库
Boost C++ 库
Boost.Serialization 会在序列化数据中包含版本信息
Protobuf (Google Protocol Buffers)
Wire Type:字段编码中的特定常量(如 0x08 对应 Varint)
JSON 解析器
字符串:虽然JSON本身是标准,但特定库( RapidJSON, jsoncpp)有其独特的错误消息或调试字符串
使用方法:
在IDA中按ShiftF12查看所有字符串,寻找上述特征字符串
使用二进制编辑器或IDA查看文件开头和特定偏移处的魔数
找到特征常量后,按X查看哪些代码引用了它,快速定位核心逻辑
ida里面如果函数对常数进行了复杂的计算(纯运算),可以查看代码是否写了特征常量
输入的str->考虑flag,在IDA里面进行改名
输入明文数据 -> AES 加密 -> HEX 编码 -> 与目标串比较
目标字符串 -> HEX 解码 -> AES 解密 -> 明文数据
需要用到的第三方库: PyCrypto >> pip install pycrypto
*shift/可以加注释