6月24日中午看雪CTF2019晋级赛Q2结束,消失的岛屿一题的被攻破数位列第二,所以还算是比较简单的,晋级赛Q2一共十道题,我只做出来此题,有两道是找到关键点了,但分析不出答案来,只好作罢。从某种程度上来说大神和菜鸟的差距还是很大的,攻击方前几名是六七百分,而我们这些菜鸟一百多名的只能解出一两道简单题,只有三四十分,真的是差距蛮大的,还是有很长的路要走,下面就开分析这道题。
双击运行exe文件
随便输入后,错误则显示“Please Try Again”
IDA打开exe
直接来到主方法。
F5查看伪代码
可以看到是base64加密输入的字符串后,和”!NGV%,$h1f4S3%2P(hkQ94==”进行strcmp函数计算
strcmp函数是用于比较两个字符串
设这两个字符串为str1,str2,
若str1=str2,则返回零;
若str1<str2,则返回负数;
若str1>str2,则返回正数。
所以只有两者相等才能打印 “success”,所以输入字符加密后的密文必须是!NGV%,$h1f4S3%2P(hkQ94==
但base64加密后密文中并不会有!%,$(这些特殊字符,所以这个并不是普通的base64,直接查看base64_encode函数
base64加密函数
因为我本人并未系统学过C语言,所以只好在网上找了个C写的base64加密
#include <stdio.h>
#include <string.h>
// 全局常量定义
const char * base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char padding_char = '=';
/*编码代码
* const unsigned char * sourcedata, 源数组
* char * base64 ,码字保存
*/
int base64_encode(const unsigned char * sourcedata, char * base64)
{
int i=0, j=0;
unsigned char trans_index=0; // 索引是8位,但是高两位都为0
const int datalength = strlen((const char*)sourcedata);
for (; i < datalength; i += 3){
// 每三个一组,进行编码
// 要编码的数字的第一个
trans_index = ((sourcedata[i] >> 2) & 0x3f);
base64[j++] = base64char[(int)trans_index];
// 第二个
trans_index = ((sourcedata[i] << 4) & 0x30);
if (i + 1 < datalength){
trans_index |= ((sourcedata[i + 1] >> 4) & 0x0f);
base64[j++] = base64char[(int)trans_index];
}else{
base64[j++] = base64char[(int)trans_index];
base64[j++] = padding_char;
base64[j++] = padding_char;
break; // 超出总长度,可以直接break
}
// 第三个
trans_index = ((sourcedata[i + 1] << 2) & 0x3c);
if (i + 2 < datalength){ // 有的话需要编码2个
trans_index |= ((sourcedata[i + 2] >> 6) & 0x03);
base64[j++] = base64char[(int)trans_index];
trans_index = sourcedata[i + 2] & 0x3f;
base64[j++] = base64char[(int)trans_index];
}
else{
base64[j++] = base64char[(int)trans_index];
base64[j++] = padding_char;
break;
}
}
base64[j] = '\0';
return 0;
}
可以看到区别最大的是网上的((sourcedata[i] » 2) & 0x3f)与IDA中的 charEncrypt((bindata[i] » 2) & 0x3F),多了charEncrypt函数,直接查看:
可以看到是对加密后的char经过了加减运算,其中负数二进制表示在补码后得到其实是一个正数。
而且aTuvwxtulmnopqr中存储的是’tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/’,0
和正常Base64的”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”;不一样,是打乱顺序了。
解密
所以加密顺序是明文->base64字符->乱序字符->运算后的密文
所以密文!NGV%,$h1f4S3%2P(hkQ94==要按加密顺序反向解密得到真正的base64字符串
写个java程序跑一下:
class Untitled {
public static void main(String[] args) {
String secret="!NGV%,$h1f4S3%2P(hkQ94";
String special="tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/";
String nomal="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
StringBuffer stringBuffer=new StringBuffer();
for (int i = 0; i < secret.length(); i++) {
stringBuffer.append(nomal.charAt(special.indexOf(String.valueOf((char)getInt(secret.charAt(i))))));
}
stringBuffer.append("==");
System.out.println(stringBuffer.toString());
}
public static int getInt(int m){
if (m==121)return 47;
if (m==119)return 43;
if (m-50>47 && m-50<=57){
return m-50;
}
if (m+64>96 && m+64<=122){
return m+64;
}
if (155-m>64 && 155-m<=90){//负数-101的补码为155
return 155-m;
}
return m;
}
}
运行得到明文的Base64是 S2FuWHVlMjAxOWN0Zl9zdA==
解码后明文是KanXue2019ctf_st,输入后返回Success
总结
本题还是比较简单的,用到了古典密码中的置换密码和代替密码,需要对Base64有个整体的了解,了解负数的表示,还是比较容易得到明文的。