具体转换步骤
第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
第二步,将上面的24个二进制位每6个一组,共分为4组。
第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
第四步,根据Base64编码对照表(见下图)获得对应的值。
码表
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y
实例:

位数不足情况

后面补0,没有位数的字节补=
Python脚本解码
import base64
encodestr = base64.b64encode('abcr34r344r'.encode('utf-8'))
print(encodestr)
>>>
import base64
bs = 'bXMwODA2Nw== '
bbs = str(base64 .b64decode(bs)
print(bbs)
黑盒处理
特性:识别到密文的base64特征,但是不知道加密表
方法:置换法
解密:用魔改表解密标准base64码表
加密:用标准表加密密文,得到中间表
解密:再用中间表解密标准base64
加密:用标准表加密密文,结果为魔改base64表
gcc
-pie(Position-Independent Executable)
功能:-pie 选项是链接选项,用于生成位置无关的可执行文件(PIE)。它告诉链接器将使用 -fPIE 或 -fPIC 编译生成的目标文件链接成一个位置无关的可执行文件。
用途:用于构建可以在任意内存地址加载和执行的可执行文件。PIE 是现代操作系统中安全机制(如 ASLR)的一部分,用于防止攻击者预测程序在内存中的布局。
特点:
要求:所有用于生成 PIE 的目标文件必须是用 -fPIE 或 -fPIC 编译的。
安全性:生成的 PIE 文件可以显著提高程序的安全性,因为它们可以在每次运行时加载到不同的内存地址。
基址固定,gdb可以下断点
tea
tea算法的主要特征表现在sum和delta变量,以及3行核心加密中出现的右移4左移5,两行各有3个小括号互相异或
加密使用的数据为2个32位无符号整数,密钥为4个32位无符号整数即密钥长度为128位
#include <stdio.h>
#define uint32_t unsigned int//ubuntu下不兼容 宏替换
//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
int main()
{
uint32_t v[2]={1,2},k[4]={2,2,3,4};
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("加密前原始数据:%u %u\n",v[0],v[1]);
encrypt(v, k);
printf("加密后的数据:%u %u\n",v[0],v[1]);
decrypt(v, k);
printf("解密后的数据:%u %u\n",v[0],v[1]);
}
xtea
增加了更多的密钥表,移位和异或操作等等
#include <stdio.h>
#include <stdint.h>
#define uint32_t unsigned int//ubuntu变量类型替换
/* 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, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}
int main()
{
uint32_t v[2]={1,2};
uint32_t const 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]);
decipher(r, v, k);
printf("解密后的数据:%u %u\n",v[0],v[1]);
return 0;
}
xxtea
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
#define uint32_t unsigned int
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main()
{
uint32_t v[2]= {1,2};
uint32_t const k[4]= {2,2,3,4};
int n= 2; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("加密前原始数据:%u %u\n",v[0],v[1]);
btea(v, n, k);
printf("加密后的数据:%u %u\n",v[0],v[1]);
btea(v, -n, k);
printf("解密后的数据:%u %u\n",v[0],v[1]);
return 0;
}
ELF文件
ELF文件(Executable and Linking Format)是用在Linux系统下的一种目标文件(object file)存储格式。典型的目标文件有如下3类:
可重定向文件(relocatable file)可重定向文件里面包含了代码和数据,用于和其他可重定向文件一起链接形成一个可执行文件或者动态库。符号为ET_REL。就是gcc加-c参数而生成的只编译不链接文件。后期还需要ld来完成重定位过程。平时很少直接用到。
可执行文件(executable file) 可执行文件里面包含了可以运行的程序代码。符号为ET_EXEC。
动态库文件(shared object file) 动态库文件里面也包含了可用于链接的代码和程序。符号为ET_DYN。它用于2个过程,首先链接器把它和其他可重定向文件、动态库一起链接形成一个可执行文件。然后程序运行时,动态链接器负责在需要时动态加载动态库文件。
(1) ELF是容器:装载了代码、数据和各种元数据
(2) 分层结构:ELF 头、程序头表、节区、节区头表
(3) 两种视角:
执行视角:段(Segments)- 加载器关心
链接视角:节(Sections)- 链接器关心
(4) 生命周期:从源代码到目标文件,再到可执行文件,最后变成进程
- ELF头(ELF Header)
ELF 头是整个文件的”门面”,包含了文件的基本信息和指向其他部分的指针。用readelf -h命令可以查看:
Entry point address:程序执行的入口点地址
Start of program headers:程序头表的位置
Start of section headers:节区头表的位置
2. 程序头表(Program Header Table)
程序头表告诉操作系统如何创建进程映像,用readelf -l命令查看:
- 节区头表(Section Header Table)
节区头表描述了文件中各个节区的信息,用readelf -S查看:
VA,IMAGEBase,RVA,FA初步了解
ImageBase:表示基准地址,表示PE文件(DLL文件为主)加载到进程的虚拟内存时的特定位置
VA,虚拟地址,也就是程序被加载到内存中的地址
RVA,以虚拟地址前边加上个“相对的”,也就是说它还是按虚拟地址来换算,只不过不是从0开始,而是把一个模块的基址作为参考点。
VA与RVA满足下面的换算关系:
RVA + ImageBase = VA
PE(Portable Executable)头部信息大多以RVA形式存在。原因在于,PE文件(主要是DLL(DLL,Dynamic Linked Library))加载到进程虚拟内存的特定位置时,该位置可能已经加载了其它PE文件(DLL)。此时必须通过重定位(Relocation)将其加载到其它空白的位置,若PE头信息使用的是VA,则无法正常访问。因此,使用RVA来定位信息,即使发生了重定位,只要相对于基准位置的相对地址没有变化,就能正常访问到指定信息,不会出现任何问题。
FA(File Address,文件地址):指的是可执行文件在存储设备(如硬盘)上的地址。它是文件在存储介质中的偏移量,用于标识文件中的特定位置。在分析可执行文件的结构或者进行文件操作时,需要使用 FA 来定位文件中的数据。与虚拟地址不同,FA 是基于文件系统的地址,而虚拟地址是在程序运行时操作系统为程序分配的内存地址。