[NSSCTF 2nd]MyBase
die查一下
用ida64打开
main函数里面没有什么信息,接着追一下函数,内容在test函数里面
函数会对我们输入的内容进行base64加密,这段逻辑也很简单,就是将加密后的字符串和目标字符串依次进行比较,一样就输出成功,那么我们是不是可以直接把这段字符串解码一下,然后直接启动程序输入解码内容就可以了,当然不是,看一下解码之后的内容
直接乱码了,原因是这个base_64encode不是base64加密,是一个自定义的函数,我们追踪看看
_BYTE *__fastcall base64_encode(__int64 a1, unsigned __int64 a2, size_t *a3)
{__int64 v4; // rax__int64 v5; // raxint v6; // eax__int64 v7; // raxint v8; // eax__int64 v9; // raxunsigned int v10; // [rsp+28h] [rbp-28h]int v11; // [rsp+30h] [rbp-20h]int v12; // [rsp+34h] [rbp-1Ch]_BYTE *v13; // [rsp+38h] [rbp-18h]__int64 v14; // [rsp+40h] [rbp-10h]unsigned __int64 v15; // [rsp+48h] [rbp-8h]*a3 = 4 * ((a2 + 2) / 3);v13 = malloc(*a3);if ( !v13 )return 0i64;v15 = 0i64;v14 = 0i64;while ( v15 < a2 ){v4 = v15++;v12 = *(unsigned __int8 *)(a1 + v4);if ( v15 >= a2 ){v6 = 0;}else{v5 = v15++;v6 = *(unsigned __int8 *)(a1 + v5);}v11 = v6;if ( v15 >= a2 ){v8 = 0;}else{v7 = v15++;v8 = *(unsigned __int8 *)(a1 + v7);}v10 = (v11 << 8) + (v12 << 16) + v8;v13[v14] = base64_table[v8 & 0x3F];v13[v14 + 1] = base64_table[(v10 >> 6) & 0x3F];v13[v14 + 2] = base64_table[(v10 >> 12) & 0x3F];v9 = v14 + 3;v14 += 4i64;v13[v9] = base64_table[(v10 >> 18) & 0x3F];if ( !setjmp(env) )exception_handler();}if ( a2 % 3 == 1 ){v13[*a3 - 1] = 61;}else if ( a2 % 3 != 2 ){goto LABEL_18;}v13[*a3 - 2] = 61;
LABEL_18:v13[*a3] = 0;return v13;
}
这个代码是简单的还原了反方向的base64加密过程,参杂了base64_table,区别于一般的base64加密这边是属于自己创建了码表,所以我们还要去获得这个码表,直接追踪可以看到一个码表
但是程序又不止有这个码表,可以看到上面的test函数中在base64加密进程后面还有一个函数exception_handler(),点进去这个函数
发现接收的函数时码表,接着追踪这个函数
char *__fastcall generate_base64_table(const char *a1)
{size_t v1; // rbxsize_t v2; // raxint v3; // eaxchar v5; // [rsp+27h] [rbp-59h]char *Destination; // [rsp+30h] [rbp-50h]char *v7; // [rsp+40h] [rbp-40h]int i; // [rsp+4Ch] [rbp-34h]v7 = (char *)malloc(0x41ui64);srand(*a1);v1 = strlen("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");v2 = strlen(a1);Destination = (char *)malloc(v1 + v2 + 1);strcpy(Destination, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");strcat(Destination, a1);for ( i = 63; i > 0; --i ){v3 = rand();v5 = Destination[v3 % (i + 1)];Destination[v3 % (i + 1)] = Destination[i];Destination[i] = v5;}strncpy(v7, Destination, 0x40ui64);v7[64] = 61;v7[65] = 0;free(Destination);strcpy(base64_table, v7);return base64_table;
}
这个函数就是对码表的一个处理,从在里面可以看到,码表在一定条件下会发生变化,具体怎么发生变化要和base64的加密原理联合起来理解,这个函数接收一个参数a1,采用随机数进行计算去作为码表对应字符的下角标,历遍整个码表依次和随机数计算得到的下角标进行替换。给了一个字符集,后面的destination是由这个字符集和a1组成的,组成之后会用上面说到的方法对这个destination进行打乱,之后输出形成一个新的码表。
到这里我们只知道码表怎么生成的,那跳转总共会生成几个码表呢,又或者说进行base64加密的过程中会用到几个码表呢,所以要结合base64加密的原理来理解,也就是这部分代码
v4 = v15++;v12 = *(unsigned __int8 *)(a1 + v4);if ( v15 >= a2 ){v6 = 0;}else{v5 = v15++;v6 = *(unsigned __int8 *)(a1 + v5);}v11 = v6;if ( v15 >= a2 ){v8 = 0;}else{v7 = v15++;v8 = *(unsigned __int8 *)(a1 + v7);}
按3字节一组读取字符,每进行3个字节的加密之后都会重新更新一次码表,那么我们还要知道加密之前的字符串由多长,但是我们只知道加密之后的字符串也就是YkLYv1Xj23X7N0E5eoFgUveKeos1XS8K9r4g有36位,按照base64加密的原理25~27位字符组成的字符串加密之后会变成36位的字符串,原因是可能后面会有两个=的填充,这边元字符串的长度就是3的倍数也就是27。
也就是我们要得到9个码表,我们在更换码表的函数上面下断点
进入动调
这边要写入充分多的字符才能得到足够的码表,输入之后这边是第一个码表
f9继续动调可以获得第二个码表
重复上面的操作就可以获得9个码表
接着就是写代码去解密,但是写完之后发现理解的不是特别到位,被上面说到的提取3个字符之后换一次码表误导了,3个是加密之前的字符串,对应加密之后的应该是4个然后每4个进行一轮解密对应一个码表,上面也说到是反向的base64加密,这边的反向也不是把一整个字符串反转过来,而是,每一轮加密也就是每4个字符串进行一次反转,所以单独提取出来4个字符之后进行一次反转,之后也不是单纯的base64解密,而是通过映射字符串和码表,因为是自定义的码表。
def custom_base64_decode(encoded_strings, key):# 初始化flagflag = ''# 标准base64字符表standard_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"# 遍历每个编码字符串for i in range(len(encoded_strings)):# 从key中提取4个字符并反转if i*4+4 > len(key):breakstr1 = key[i*4:i*4+4][::-1]# 获取当前的编码表current_table = encoded_strings[i]# 创建从自定义表到标准表的映射trans_table = str.maketrans(current_table, standard_base64)# 转换编码字符串decoded_chunk = str1.translate(trans_table)# 进行base64解码try:# Python的base64模块要求输入是4的倍数,如果不是,添加'='填充padding_needed = (4 - len(decoded_chunk) % 4) % 4decoded_chunk += '=' * padding_needed# 解码并添加到flagflag += base64.b64decode(decoded_chunk).decode('utf-8')except:# 如果解码失败,跳过这个块continuereturn flag# 导入base64模块
import base64# 定义要解密的数据
a = ["+86420ywusqomkigecaYWUSQOMKIGECABDFHJLNPRTVXZbdfhjlnprtvxz13579/", "YsVO0tvT2o4puZ38j1dwf7MArGPNeQLDRHUK+SChbFanmklWEcgixXJIq6y5B/9z", "xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ", "YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u", "xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ", "YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u", "xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ", "YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u", "xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ"]b = 'YkLYv1Xj23X7N0E5eoFgUveKeos1XS8K9r4g'# 解密并打印结果
decrypted_flag = custom_base64_decode(a, b)
print("解密后的flag:", decrypted_flag)
[SWPUCTF 2023 秋季新生赛]IDA动态调试
die查一下
ida打开看主函数
输出一个东西之后直接停止程序了,接着追踪看看其他的
看到类似flag设置的地方,这边还有一个函数,后面是对这个函数的结果的验证,给了给if语句后面会释放内存,我们接着看这个函数
这段代码返回的是v2的地址,但是声明v2的地方也就是这个函数,当这个函数执行完成之后v2分配的内存会被自动收回,所以这边会导致一个程序的错乱,我们不妨执行一下这个程序看看
直接这就是空白,纵观整个程序问题就是出在这里,所以我们在这边下一个断点,调试看看,直接追踪v2的值,因为我们是在返回v2处也就是v2被删除的时候下的断点,所以我们这是后看v2的值是在的,也就是上面异或的结果
结果就是flag
[HNCTF 2022 WEEK3]Help_Me!
die查一下
ida打开,直接去看main函数看不到什么东西,这题要去看strings
直接追踪到为函数这边是func函数
这边有提示信息输出flag的格式所以我们要达成整个函数的条件,我们可以运行一下程序
就是在一定条件下选择这几个课,得到最高分,至于选择的逻辑就是要逆向去找了,还是在string里面去找,可以找到真的main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{__int64 v3; // rbx__int64 v4; // r8__int64 v5; // rdxint v6; // ebxint v7; // esiunsigned int v8; // ediint v10; // [rsp+28h] [rbp-F0h]int v11; // [rsp+2Ch] [rbp-ECh]int v12[24]; // [rsp+30h] [rbp-E8h]int v13[34]; // [rsp+90h] [rbp-88h]v3 = 0i64;_main();puts("Welc0me T0 HNCTF!");puts(&byte_488020);puts(&byte_488050);puts(&byte_488093);v4 = 26i64;v12[0] = 26;v12[1] = 59;v12[2] = 30;v12[3] = 19;v12[4] = 66;v12[5] = 85;v12[6] = 94;v12[7] = 8;v12[8] = 3;v12[9] = 44;v12[20] = 0;v12[10] = 5;v12[11] = 1;v12[12] = 41;v12[13] = 82;v12[14] = 76;v12[15] = 1;v12[16] = 12;v12[17] = 81;v12[18] = 73;v12[19] = 32;v13[0] = 71;v13[1] = 34;v13[20] = 0;v13[2] = 82;v13[3] = 23;v13[4] = 1;v13[5] = 88;v13[6] = 12;v13[7] = 57;v13[8] = 10;v13[9] = 68;v13[10] = 5;v13[11] = 33;v13[12] = 37;v13[13] = 69;v13[14] = 98;v13[15] = 24;v13[16] = 26;v13[17] = 83;v13[18] = 16;v13[19] = 26;while ( 1 ){v5 = (unsigned int)v3++;printf("(%d).%d\n", v5, v4);if ( v3 == 20 )break;v4 = (unsigned int)v12[v3];}puts(&byte_4880AF);std::istream::operator>>(refptr__ZSt3cin);puts(&byte_4880BF);if ( v10 <= 0 ){v7 = 1;}else{v6 = 0;v7 = 1;v8 = 0;do{++v6;std::istream::operator>>(refptr__ZSt3cin);v8 += v13[v11];v7 *= v12[v11];}while ( v10 > v6 );if ( v8 > 200 ){printf(aAnd1);exit(0);}}if ( (unsigned int)func(v7) )printf(aOi);elseprintf(aScore);return 0;
}
可以看看到不能超过200,所以重要的还是上面提到的func函数,经过了解这是一个类似背包问题的一种算法,简单来说就是要在一定条件下,使得得到的分数最高,这边的条件就是总共的重量不能超过两百,价值就是v12数组,重量就是v13数组
扔给ai就可以解决
def knapsack_01(values, weights, capacity):n = len(values)# DP表:dp[i][w] 表示前i个物品在容量w下的最大价值dp = [[0] * (capacity + 1) for _ in range(n + 1)]for i in range(1, n + 1):for w in range(1, capacity + 1):if weights[i-1] <= w:# 可以选择拿或不拿当前物品dp[i][w] = max(dp[i-1][w], values[i-1] + dp[i-1][w - weights[i-1]])else:# 当前物品太重,不能拿dp[i][w] = dp[i-1][w]# 回溯找出选了哪些物品selected = []w = capacityfor i in range(n, 0, -1):if dp[i][w] != dp[i-1][w]:selected.append(i-1)w -= weights[i-1]return dp[n][capacity], selected# 给定的数据
v12 = [26, 59, 30, 19, 66, 85, 94, 8, 3, 44,5, 1, 41, 82, 76, 1, 12, 81, 73, 32
]
v13 = [71, 34, 82, 23, 1, 88, 12, 57, 10, 68,5, 33, 37, 69, 98, 24, 26, 83, 16, 26
]
capacity = 200max_value, selected_items = knapsack_01(v12, v13, capacity)print("最大价值:", max_value)
print("选择的物品索引:", selected_items)
print("选择的物品价值:", [v12[i] for i in selected_items])
print("选择的物品重量:", [v13[i] for i in selected_items])
print("总重量:", sum([v13[i] for i in selected_items]))
但是直接用程序又不行,输入之后会直接退出程序,所以还要下断点动调
断点下在跳出之前即可,执行之后慢慢用f8调试