逆向每日一练,题目地址:攻防世界 (xctf.org.cn)
- 题目来源:DDCTF
- 题目描述:windows逆向,提交的flag形式为flag{XXXX}。
- 难度:3
解题分析
查壳:ASPack
ESP定律即可脱壳,这里就不展开了
动态调试可以直接让程序跑起来,进入输入函数之后,x86dbg里点暂停,即可断在输入之后的位置,直接就到了我们需要逆向的位置上
这里静态分析来分析这道题
check1
首先是第一个check,功能很简单,就是判断输入是否为0-9,A-F这几个字符,并且输入长度是偶数,如果满足,则返回1,往下继续执行,函数内容比较简单就不进入展开分析了
check2
再往下是个字符串生成函数
里头主要就是一个循环,主要内容如下,每次取2个字节,计算该字符对应的数值,保存起来
然后分别将两个值,合并起来保存到缓冲区(就是把字符串形式的十六进制转换成数值类型)
转换完成之后,调用函数生成字符串:
主要内容是这个大循环,会执行多次,内容是:
- 先从刚刚生成的十六进制数据缓冲区里取出来3个字节
- 然后进行一系列位运算,得到4个值
- 这4个分别作为索引去数组里拿到一个字节的数值
- 将数值异或’v’之后,就保存起来用于返回
仔细分析一下,不难发现,这一堆位运算实际在做的事情是:把3个字节的数据拆分成4个数值,就是base64编码算法的那种操作,这里是编码过程,编码完成之后的结果保存起来
flag&注册机
最后得到刚刚根据类似base64编码的算法生成的字符串,sprintf函数将其拼接起来,然后进行strcmp循环,比对最后的结果是不是DDCTF
这意味着,我们输入的值,通过算法之后生成的字符串需要为这个固定值
既然知道算法的结果,和计算过程,那就可以反向生成我们需要的输入
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
unsigned char byteArr[64] = {
0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3E, 0x3F, 0x3C, 0x3D, 0x3A, 0x3B, 0x38, 0x39, 0x26,
0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2E, 0x2F, 0x2C, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10,
0x11, 0x1E, 0x1F, 0x1C, 0x1D, 0x1A, 0x1B, 0x18, 0x19, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00,
0x01, 0x0E, 0x0F, 0x0C, 0x46, 0x47, 0x44, 0x45, 0x42, 0x43, 0x40, 0x41, 0x4E, 0x4F, 0x5D, 0x59
};
int GetIndex(unsigned char c) {
for (int i = 0; i < sizeof(byteArr); i++)
{
if (c == byteArr[i]) return i;
}
}
// generate "reve"
string GenStr(const char * input) {
string res = "";
unsigned char index1 = GetIndex(input[0] ^ 'v');
unsigned char index2 = GetIndex(input[1] ^ 'v');
unsigned char index3 = GetIndex(input[2] ^ 'v');
unsigned char index4 = GetIndex(input[3] ^ 'v');
unsigned char c1 = (index1 << 2) + (index2 >> 4);
unsigned char c2 = ((index2 & 0x0F) << 4) + (index3 >> 2);
unsigned char c3 = ((index3 & 0x3) << 6) + index4;
char tmp[100] = { 0 };
sprintf(tmp,"%X%X%X",c1,c2,c3);
res += tmp;
return res;
}
int main()
{
cout << GenStr("reve");
cout << GenStr("rse+");
}
计算得到flag:ADEBDEAEC7BE