一、查壳
把文件拖入查壳工具
发现是c++, 由exeinfope可知此程序无壳。
二、静态分析(IDA)
把文件拖到IDA中
接下来寻找 main 函数
点击图片中的 main ,得到
反编汇
点击f5 进行反汇编,得到伪代码
进行分析
分析伪代码
从上往下进行一步步分析一下,进行一下注释
因此目前要搞明白24行那个函数的作用,点进去查看一下
继续点击查看
void *__cdecl sub_411AB0(char *a1, unsigned int a2, int *a3)
{
int v4; // [esp+D4h] [ebp-38h]
int v5; // [esp+D4h] [ebp-38h]
int v6; // [esp+D4h] [ebp-38h]
int v7; // [esp+D4h] [ebp-38h]
int i; // [esp+E0h] [ebp-2Ch]
unsigned int v9; // [esp+ECh] [ebp-20h]
int v10; // [esp+ECh] [ebp-20h]
int v11; // [esp+ECh] [ebp-20h]
void *v12; // [esp+F8h] [ebp-14h]
char *v13; // [esp+104h] [ebp-8h]
if ( !a1 || !a2 )
return 0;
v9 = a2 / 3;
if ( (int)(a2 / 3) % 3 )
++v9;
v10 = 4 * v9;
*a3 = v10;
v12 = malloc(v10 + 1);
if ( !v12 )
return 0;
j_memset(v12, 0, v10 + 1);
v13 = a1;
v11 = a2;
v4 = 0;
while ( v11 > 0 )
{
byte_41A144[2] = 0;
byte_41A144[1] = 0;
byte_41A144[0] = 0;
for ( i = 0; i < 3 && v11 >= 1; ++i )
{
byte_41A144[i] = *v13;
--v11;
++v13;
}
if ( !i )
break;
switch ( i )
{
case 1:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v5 = v4 + 1;
*((_BYTE *)v12 + v5) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
*((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
*((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
v4 = v5 + 1;
break;
case 2:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v6 = v4 + 1;
*((_BYTE *)v12 + v6) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
*((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
*((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[64];
v4 = v6 + 1;
break;
case 3:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v7 = v4 + 1;
*((_BYTE *)v12 + v7) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
*((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
*((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];
v4 = v7 + 1;
break;
}
}
*((_BYTE *)v12 + v4) = 0;
return v12;
}
点开是这样的
函数参数
a1: 指向输入字符串指针。
a2: 输入字符串的长度。
a3: 指向一个整数的指针,用于存储编码后数据的长度。
局部变量
v4, v5, v6, v7: 用于索引和计数。
i: 循环计数器。
v9: 计算需要多少个Base64字符来存储输入数据。
v10: 计算分配的内存大小(包括一个额外的字节用于存储空字符)。
v11: 用于跟踪剩余的输入数据长度。
v12: 指向分配的内存,用于存储Base64编码的数据。
v13: 输入数据的当前指针。
分析代码
这个函数是一个Base64编码实现,它处理了不同长度的输入数据,并正确地计算了输出数据的长度。
看到16行的"/3"和19行的“*4",这就是base64对字符的二进制编码做的处理,这是定义了v9和v10来存放base64对字符的二进制变化;
结合查到的base64的编码和对伪代码的分析,我们基本明白了大致过程:
将flag输入后经过Base64编码加密,赋值给v4,再复制给Destination,将Destination进行了循环,最后将Destination和str2进行了比较,若相同就是正确的flag,否则就是错误的flag。这一流程说明了Str2中存储的就是加密并且for循环操作后的字符串
逆向思考
Str2-->给Destination减去其索引值-->进行Base解码
逆向脚本
先的到Str2
先for循环减去for循环中加的数值,再对得到的字符串进行base64解密
import base64
Des="e3nifIH9b_C@n@dH"
flag=""
for i in range(len(Des)):
flag+=chr(ord(Des[i])-i)
print(base64.b64decode(flag))
得到flag{i_l0ve_you}
三、知识总结
C语言中的标准库函数https://blog.csdn.net/weixin_44793491/article/details/107644666https://blog.csdn.net/weixin_44793491/article/details/107644666
strlen
在C语言中,strlen
是一个标准库函数,用于计算以 null 结尾的字符串(C 风格字符串)的长度。strlen
函数定义在 <string.h>
头文件中。
size_t strlen(const char *str);
参数
str
:指向要计算长度的字符串的指针。
返回值
- 返回值是
size_t
类型,表示字符串的长度,不包括结尾的 null 字符。
功能
strlen
函数遍历字符串,直到遇到第一个 null 字符('\0'
),然后返回计数的字符数。
示例代码
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
size_t length = strlen(str);
printf("Length of '%s' is %zu\n", str, length);
return 0;
}
得到
Length of 'Hello, World!' is 13
注意事项
- strlen 传入的字符串必须是以 null 结尾的有效 C 字符串。
- 如果传入的不是,strlen 可能会读取到内存中的随机位置,直到遇到 null 字符,这可能导致程序崩溃或返回错误的字符串长度。
- strlen 函数的时间复杂度是 O(n),其中 n 是字符串的长度,因为它需要逐个字符检查直到找到 null 字符。
strncpy
在C语言中,strncpy
是一个标准库函数,用于将一个字符串复制到另一个字符串中,并且可以指定要复制的最大字符数。strncpy
函数定义在 <string.h>
头文件中。
char *strncpy(char *dest, const char *src, size_t n);
参数
- dest:指向目标字符串的指针,这是要将数据复制到的位置。
- src:指向源字符串的指针,这是要从中复制数据的位置。
- n:指定要复制的最大字符数。
返回值
返回 dest 指向的目标字符串的指针。
功能
- strncpy 函数从 src 复制最多 n 个字符到 dest。
- 如果 src 的长度小于 n,则 dest 将用空字符 ('\0') 填充剩余的部分。
- 如果 src 的长度大于或等于 n,则 dest 不一定会以空字符结尾,这意味着目标字符串可能不会以空字符正确终止。
示例代码
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20];
strncpy(dest, src, 13); // 复制整个字符串,包括结尾的空字符
printf("Copied string: %s\n", dest);
strncpy(dest, src, 6); // 只复制前6个字符
printf("Copied partial string: %s\n", dest);
return 0;
}
得到
Copied string: Hello, World!
Copied partial string: Hello, Wor
注意事项
- strncpy 不保证目标字符串以空字符结尾,如果源字符串长度大于或等于 n,目标字符串可能不会以空字符结尾。
- 如果你需要确保目标字符串以空字符结尾,你可能需要手动添加一个空字符到 dest[n-1]。
- 如果源字符串长度小于 n,剩余的字符将被填充为 '\0'。
- strncpy 可以用于防止缓冲区溢出,但是需要谨慎使用,以确保字符串以空字符结尾。
- 由于 strncpy 的这些特性,一些开发者和安全专家推荐使用更安全的替代函数,如 strlcpy,它在某些操作系统中可用,并且设计用来防止溢出并确保字符串以空字符结尾。
base64编码
字符集特征:
Base64编码使用一个包含64个字符的字符集,通常包括大写字母(A-Z)、小写字母(a-z)、数字(0-9)以及两个附加符号(+ 和 / )。还有一个用于填充的等号字符(=)。
本题对应的就是ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
base64加密的特征代码:
计算输出长度:Base64编码每3个字节映射到4个字符
所以计算方法是将输入字节数除以3,然后乘以4。
v9 = a2 / 3;
if ((int)(a2 / 3) % 3)
++v9;
v10 = 4 * v9;
内存分配:为编码后的字符串分配内存,长度为计算出的长度加1,加1是为了存储字符串结尾的空字符。
v12 = malloc(v10 + 1);
初始化内存:将分配的内存初始化为0。
j_memset(v12, 0, v10 + 1);
逐字节处理输入数据: 循环处理输入数据,每次最多处理3个字节
while (v11 > 0) {
// ...
for (i = 0; i < 3 && v11 >= 1; ++i) {
byte_41A144[i] = *v13;
--v11;
++v13;
}
// ...
}
Base64编码逻辑:使用了一个查找表 aAbcdefghijklmn
,它很可能包含了Base64的64个字符。根据Base64编码规则,将每个字节的位分组,并从查找表中索引对应的字符。
switch (i) {
case 1:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
// ...
break;
case 2:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
// ...
break;
case 3:
*((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
// ...
break;
}
填充:在处理2个字节的情况时,编码后会有一个Base64字符用作填充。
*((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[64];
结尾空字符:编码结束后,在字符串末尾添加空字符。
*((_BYTE *)v12 + v4) = 0;
通过这些特征,我们可以推断出这段代码是用于Base64编码的。它遵循了Base64编码的基本规则,包括输入数据的处理方式、查找表的使用、以及填充字符的添加。
reverse的base64解码脚本
import base64 #导入base64模块用于解密
s1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' #标准表
s2 = 'qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD' #base64换表
en_text = '5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8==' #密文
map = str.maketrans(s2, s1) #用str类中的maketrans建立映射,注意第一个参数是需要映射的字符串,第二个参数是映射的目标
map_text = en_text.translate(map) #映射实现替换密文,替换前是base64换表加密,替换后则是base64标准表加密
print(map_text) #可以先看看标准表加密的原base64密文
print(base64.b64decode(map_text)) #直接使用提供的base64解密函数解密
标签:BUUCTF,41A144,v12,int,字符串,byte,BYTE,reverse3 From: https://blog.csdn.net/Co_xy/article/details/144608597