【2021.12.25】ctf逆向中常见加密算法和编码识别(含exe及wp)
文章目录
- 【2021.12.25】ctf逆向中常见加密算法和编码识别(含exe及wp)
- 0、前言
- 1、基础加密手法
- 2、base64
- (1)原理:
- (2)base64完整编码过程
- (3)base64完整解码过程
- (4)特殊情况
- (5)base64加解密代码
- (6)base64在ctf中的变换
- (7)变换码表之后的base解法:
- (8)base58
- (9)练习
- 3、TEA
- (1)简介
- (2)TEA加解密代码
- (3)TEA扩展:XTEA加密
- 4、RC4
- (1)简介
- (2)加密过程
- (3)RC4加解密代码
- (4)RC4逆向
- (5)逆向RC4算法技巧
- (6)练习
- 5、MD5
- (1)简介
- (2)特点
- (3)运用
- (4)ctf逆向中的使用
- 6、总结
0、前言
在对数据进行变换的过程中,除了简单的字节操作之外,还会使用一些常用的编码加密算法,因此如果能够快速识别出对应的编码或者加密算法,就能更快的分析出整个完整的算法。
CTF 逆向中通常出现的加密算法包括 base64、TEA、AES、RC4、MD5 等。
1、基础加密手法
简单位运算
位运算 | C语言符号 | 意义 |
---|---|---|
左移 | << | 一般是位左移,低位补零 |
右移 | >> | 一般是位右移,有符号数高位补符号位,无符号数高位补0 |
异或 | ^ | 按位比较,相同位取0,不同位取1 |
与 | & | 按位比较,全为1时取1,否走取0 |
或 | | | 按位比较,全为0时取0,否则取1 |
非 | ~ | 按位取反 |
简单加密类型
基础加密算法 | 简易公式 | 意义 |
---|---|---|
凯撒加密 | Y=X+a | 任意值X经过偏移为a的移位变换为Y |
仿射加密 | Y = aX+b | 与上面类似 |
2、base64
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。其码表有64个字符。
加密的数据分为3个字节一组、6位一段;其中6位的含义是2的6次方=64,可以用64个码表字符表示。
(1)原理:
3个字节一组,将 3 字节的数据,(字符对应Ascll码)先后放入一个 24位的缓冲区中,先来的字节占高位。
3个字节24位从高到低平分成4段,每段6位,
用这6位二进制为代表的数字在base表中查找对应位置的字符。
(2)base64完整编码过程
假设要编码的字符串长度是3的倍数。
(3)base64完整解码过程
假设要解码的字符串长度是4的倍数。
(4)特殊情况
若加密字符长度除以3余1 ,则最后一组只有1个字节8位,将8位二进制后续以0填充至12位,(6的倍数),分成两段,查表获得两个字符,再添加‘==’,凑齐4个字符,完整演示如下。
若加密字符长度除以3余2,则最后一组只有2个字节16位,将16位二进制后续以0填充至18位,(6的倍数),分成3段,查表获得三个字符,再添加‘=’,凑齐4个字符,完整演示如下。
(5)base64加解密代码
–C语言–
环境:win10
编译:VC++6.0
加密
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char a[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char s[100];
int i,j;
void jiami(char x,char y,char z){
s[j]=a[x>>2];
s[j+1]=a[(x<<4|y>>4)&0x3f];
s[j+2]=a[(y<<2|z>>6)&0x3f];
s[j+3]=a[z&0x3f];
}
int main(){
int len;
char str[100];
printf("base64需要加密的字符串:");
gets(str);
len=strlen(str);
for(i=0,j=0;i<=len-3;i+=3,j+=4){
jiami(str[i],str[i+1],str[i+2]);
}
if(len%3==1){
s[j]=a[str[len-1]>>2];
s[j+1]=a[str[len-1]<<4&0x3f];
s[j+2]='=';
s[j+3]='=';
}
if(len%3==2){
s[j]=a[str[len-2]>>2];
s[j+1]=a[(str[len-2]<<4|str[len-1]>>4)&0x3f];
s[j+2]=a[(str[len-1]<<2)&0x3f];
s[j+3]='=';
}
printf("\n加密之后:");
puts(s);
printf("\n");
system("pause");
return 0;
}
解密
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char s[100];
int i,j;
char a[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void jiemi(int a,int b,int c,int d)
{
s[j]=a<<2|b>>4;
s[j+1]=b<<4|c>>2;
s[j+2]=c<<6|d;
}
int main()
{
int len,len1;
int str1[100];
char str[100];
printf("base64需要解密的字符串:");
gets(str);
len=strlen(str);
for(i=0;i<len;i++)
for(j=0;j<64;j++)
if(str[i]==a[j]){
str1[i]=j;
len1=i+1; }
for(i=0,j=0;i<=len1-4;i+=4,j+=3)
jiemi(str1[i],str1[i+1],str1[i+2],str1[i+3]);
if(len1%4==2)
s[j]=str1[i]<<2|str1[i+1]>>4;
if(len1%4==3){
s[j]=str1[i]<<2|str1[i+1]>>4;
s[j+1]=str1[i+1]<<4|str1[i+2]>>2;
}
printf("\n解密之后:");
puts(s);
putchar('\n');
system("pause");
return 0;
}
–python–
环境:win10
编译:VScode-python3
python的话就比较简单了,直接调库即可。
(6)base64在ctf中的变换
-
替换base64码表
-
改写编码过程
(1)编码之后每一位进行偏移或仿射
(2)编码时3字节分4段,每段不固定6位, 可以分成 5位 6位 6位 7位 共24位 3字节
(3)编码不固定3字节一组,也可以4字节32位, 分为6位 6位 7位 7位 6位,共32位
常见base家族
base32,码表32个字符,8个字节一组、5位一段;2的5次方=32
base16,码表16个字符,1个字节一组、4位一段;2的4次方=16
(7)变换码表之后的base解法:
C语言脚本: 直接替换码表即可
python脚本:将编码中字符,与新base64码表和原始base64码表,作一一映射的替换, 再调库进行正常base64解码(原始码表)
import base64
import string
s = "pCNxpTJ2d3d5nPoQnSAAnQBel4lihldkikV78nQ="#base64编码
str1 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"#base64新的码表
str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"#base64原始码表
trantab = str.maketrans(str1,str2)#maketrans方法 返回str1与str2一一映射表
#print(type(trantab),trantab )
s_base64 = s.translate(trantab)#translate方法,s的每一个字符,通过上面trantab一一映射表,替换为另一字符串
s_new = base64.b64decode(s_base64).decode('utf-8')
print(s_new)
(8)base58
base58,码表58个字符,将三个字节数值 连起来看做256进制, 再将其转换为4个58进制,对应码表查找字符。
图片参考:编码算法-Base58
(9)练习
base64换表练习
链接:https://pan.baidu.com/s/11Mm_qRxgplp-pKLSWnXPlg
提取码:wqpf
base64算法变换练习
链接:https://pan.baidu.com/s/1ZrsfrSJauYbTAnFd_X0Ang
提取码:qh6u
3、TEA
(1)简介
微型加密算法(Tiny Encryption Algorithm,TEA)是一种易于描述和执行的块密码,通常只需要很少的代码就可实现。由剑桥大学计算机实验室的David Wheeler和Roger Needham于1994年发明。
它是一种分组密码算法,其明文密文块为64比特,密钥长度为128比特。
TEA算法利用不断增加的Delta(黄金分割率)值作为变化,使得每轮的加密是不同,该加密算法的迭代次数可以改变,建议的迭代次数为32轮。QQ使用此加密技术,加密轮数为16轮。
明文密文块为64比特:一般两个4字节数 (int型)
密钥长度为128比特:一般4个四字节数(int型)
整体的加密过程如下图
图片参考:TEA、XTEA、XXTEA加密解密算法
(2)TEA加解密代码
环境:win10
编译:VC++6.0
建议代码结合上图一起分析。
加密
#include<stdio.h>
void encode(unsigned int* v,unsigned int *k)
{
unsigned int v0=v[0],v1=v[1];
unsigned int k0=k[0],k1=k[1],k2=k[2],k3=k[3];
unsigned int delta=0x9e3779b9;
int i;
unsigned int sum=0;
for(i=0;i<32;i++) //核心加密算法,建议32轮,最低16轮
{
sum+=delta;
v0+=((v1<<4)+k0)^(v1+sum)^((v1>>5)+k1); //r<<4/r*16
v1+=((v0<<4)+k2)^(v0+sum)^((v0>>5)+k3);
}
v[0]=v0;
v[1]=v1;
}
int main()
{
//明文,必须是8字节的倍数,不够需要程序补全,参考base64方法
unsigned int m[2]={1,2};
unsigned int k[4]={2,2,3,4};//密钥随便 一定是128位,即4个4字节数
encode(m,k);
printf("%d %d\n",m[0],m[1]);
return 0;
}
解密
#include<stdio.h>
#include<windows.h>
void decode(unsigned int* v,unsigned int *k)
{
unsigned int v0=v[0], v1=v[1], sum=0xC6EF3720, i; //由加密轮数而算出
unsigned int delta=0x9e3779b9;
unsigned int k0=k[0], k1=k[1], k2=k[2], k3=k[3];
for (i=0; i<32; i++) { //核心解密算法
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
v[0]=v0;
v[1]=v1;
}
int main()
{
unsigned int c[2]={1347371722,925494771}; //密文,两字节一组
unsigned int k[4]={2,2,3,4};//密钥随便 一定是128位,即4个4字节数
decode(c,k);
printf("%d %d\n",c[0],c[1]);
return 0;
}
(3)TEA扩展:XTEA加密
XTEA加密和TEA加密基本类似,只是多了一点点,对比一下代码即可发现。
环境:win10
编译:VC++6.0
加密
#include <stdio.h>
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
void encipher(unsigned int num_rounds, unsigned int v[2], unsigned int key[4])
{
unsigned int i;
unsigned int v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
sum += delta;
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}
int main()
{
unsigned int v[2]={1,2};
unsigned int k[4]={2,2,3,4};
unsigned int r=32;//num_rounds建议取值为32
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("加密前原始数据:%u %u\n",v[0],v[1]);
encipher(r, v, k);
printf("加密后的数据:%u %u\n",v[0],v[1]);
return 0;
}
解密
#include <stdio.h>
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
void decipher(unsigned int num_rounds, unsigned int v[2], unsigned int key[4])
{
unsigned int i;
unsigned int v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
sum -= delta;
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}
int main()
{
unsigned int c[2]={1345390024,2801624574};
unsigned int k[4]={2,2,3,4};
unsigned int r=32;//num_rounds建议取值为32
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("解密前原始数据:%u %u\n",c[0],c[1]);
decipher(r, c, k);
printf("解密后的数据:%u %u\n",c[0],c[1]);
return 0;
}
4、RC4
(1)简介
百度百科:RC4
RC4(来自 Rivest Cipher 4 的缩写)是一种流加密算法,密钥长度可变。它加解密解密使用相同的密钥,因此也属于对称加密算法。RC4 是有线等效加密(WEP)中采用的加密算法,也曾经是 TLS可采用的算法之一。
特点:
名称 | 特点 |
---|---|
秘钥 | 长度可变,通常小于256字节 |
密钥流 | 指S盒,长度256Byte,初始化为0~255序列,然后根据秘钥K搅乱 |
明文 | 长度任意,明文只一次异或加密 |
(2)加密过程
注意看加密过程,明文就一个异或加密,
只是密钥流要当时生成,加密解密的密钥流都是一样的
完整加密过程
结合上图
注意: 加密前,生成的密钥流s盒,加密后密钥流s盒会发生改变,所以解密时,用的密钥流s盒是初始化之后但加密前的,即上图第一步之后生成的s盒。
(3)RC4加解密代码
先初始化秘钥
再进行加解密,(用的秘钥都是上述代码初始化出来的)
完整加密代码
环境:win10
编译:VC++6.0
main函数有通过RC4加解密的整个过程
//程序开始
#include<stdio.h>
#include<string.h>
/*初始化函数*/
//参数1:传入长度256的unsigned char型数组首地址
//参数2:密钥,其内容可以随便定义:char key[256];
//参数3是密钥的长度,Len = strlen(key);
void rc4_init(unsigned char*s, unsigned char*key, unsigned long Len)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i<256; i++)
{
s[i] = i;//初始化s盒,0~255
k[i] = key[i%Len];
}
for (i = 0; i<256; i++)//将s盒打乱
{
j = (j + s[i] + k[i]) % 256;
tmp = s[i];//交换s[i]和s[j]
s[i] = s[j];
s[j] = tmp;
}
}
/*加解密*/
//参数1:是上边rc4_init函数中,被搅乱的S-box;
//参数2:是需要加密/解密的数据data;
//参数3:data的长度.
void rc4_crypt(unsigned char*s, unsigned char*Data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k<Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[x]和s[y]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}
int main()
{
unsigned char s[256] = { 0 }, s2[256] = { 0 };//S-box
char key[256] = { "justfortest" };
unsigned char pData[512] = "Hello World";
unsigned long len = strlen((char*)pData);
int i;
printf("pData=%s\n", pData);
printf("key=%s,length=%d\n\n", key, strlen(key));
rc4_init(s, (unsigned char*)key, strlen(key));//已经完成了初始化
printf("完成对S[i]的初始化,如下:\n\n");
for (i = 0; i<256; i++)
{
printf("%02X", s[i]);
if (i && (i + 1) % 16 == 0)putchar('\n');
}
printf("\n\n");
for (i = 0; i<256; i++)//用s2[i]暂时保留经过初始化的s[i],很重要的!!!
{
s2[i] = s[i];
}
printf("已经初始化,现在加密:\n\n");
rc4_crypt(s, (unsigned char*)pData, len);//加密
for(i=0;pData[i];i++){
printf("0x%x,",pData[i]);
}
//printf("pData=%s\n\n", pData);
printf("\n");
printf("已经加密,现在解密:\n\n");
//rc4_init(s,(unsignedchar*)key,strlen(key));//初始化密钥
rc4_crypt(s2, (unsigned char*)pData, len);//解密
printf("pData=%s\n\n", pData);
return 0;
}
//程序完
(4)RC4逆向
上述可以看出RC4算法,加密和解密是一样的流程
先初始化密钥流S盒,再通过初始化的密钥流S盒对数据进行异或加密/解密
所以逆向RC4算法:找到密钥、加密之后的数据,用来跑一边解密脚本
(5)逆向RC4算法技巧
通过上面两张图可以看到:
密文=明文^密钥流
所以!明文=密文^密钥流
即密钥固定时,每次加密生成的秘钥流是固定的
**解密难点:**密钥流未知
技巧:
我们可以通过选择明文攻击
即输入一串已知明文,调试获取加密之后的密文
这样就已知了明文和对应密文
所以可求:密钥流=已知明文^对应密文
然后根据原始密文
可求得:原始明文=原始密文^密钥流
(6)练习
RC4练习
链接:https://pan.baidu.com/s/1nblRiYkSFewKRyulSirUXg
提取码:ffyq
5、MD5
(1)简介
MD5 消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输完整一致。
(2)特点
1、无论加密数据多大多小,都只生成长度一样的散列值(一般是128位,16字节)
2、相同的数据,随时生成的散列值都一致,但只要数据发生一点变化,生成的散列值差别巨大
3、MD5本身不可逆
(3)运用
1、密码管理,输入密码后进行md5加密然后与数据库中的md5值对比(易受到md5撞库攻击)
2、电子签名,确保数据传输过程中数据更改
(4)ctf逆向中的使用
在线md5加密网址:https://www.somd5.com/
6、总结
ctf逆向中的加密算法坑定不止上述这些,只是说通过这些简单的加密算法,学习,进阶,再难的算法都是从基础算法变换而来的,好比万丈高楼平地起!加油!