【攻防世界】simple-check-100
收获
了解数据的大端序、小端序存放
了解
* (4 * i + v4)
写法的含义(不一定是顺序数组,可能是好几个地址的值组合起来作为数组的一个元素)数据的存放方式转化为十六进制看得更清楚(IDA 快捷键:h)
例如:-559038737(0xDEADBEEF)、106(0x6A)、-51(0xCD)等数据转化为十六进制进行异或时,位数应该相同 (32 位对 32 位,8 位对 8 位)
Windows 下 OllyDBG 调试
Linux 下 GDB 调试
思路一
给出了三个文件,分别是一个 32位 exe,一个 32位 elf,一个 64位 elf
将 64位 elf 拖入 IDA
输入的 key 存放在 v9 的位置,check_key(v9)
对输入的 key 做了一个检测,若 check_key(v9)
返回 true,则调用 interesting_function(v7)
查看 check_key(v9)
:
即对 key 的每一位进行相加求和,若和为 -559038737(十六进制:0xDEADBEEF),就返回 true
查看 interesting_function(v7)
:
调用的这个函数只有一个输出,这里的 putchar 输出的应该就是 flag,而这个函数的处理过程与 key 没有关系,所以可以通过这个代码直接求解出 flag
注意这里 *(4 * i + v4)
的写法:
- v4 = a1,a1 又是作为形参传入函数
int __fastcall interesting_function(__int64 a1)
的,所以 a1、v4 的值为v7[]
的首地址 (4 * i + v4) 以 v7[]
的首地址作为基地址,偏移量为4 * i
,即:将首地址的后 4 个地址的值作为一组- 根据
v2 = *(4 * i + v4) ^ 0xDEADBEEF
可知,*(4 * i + v4)
的值的长度应该跟0xDEADBEEF
一样,是一个 32 位的数据【 DWORD,全称 Double Word 】
根据 v7[]
的定义(将值转化为 16 进制)v7[]
中每一个地址存放的数据长度是 2 个十六进制数据
v7[0] = 0x54;
v7[1] = 0xC8;
v7[2] = 0x7E;
v7[3] = 0xE3;
v7[4] = 0x64;
v7[5] = 0xC7;
v7[6] = 0x16;
v7[7] = 0x9A;
v7[8] = 0xCD;
v7[9] = 0x11;
v7[10] = 0x65;
v7[11] = 0x32;
v7[12] = 0x2D;
v7[13] = 0xE3;
v7[14] = 0xD3;
v7[15] = 0x43;
v7[16] = 0x92;
v7[17] = 0xA9;
v7[18] = 0x9D;
v7[19] = 0xD2;
v7[20] = 0xE6;
v7[21] = 0x6D;
v7[22] = 0x2C;
v7[23] = 0xD3;
v7[24] = 0xB6;
v7[25] = 0xBD;
v7[26] = 0xFE;
v7[27] = 0x6A;
将首地址的后 4 个地址的值作为一组,则一组正好是 8 个十六进制数(一个十六进制数为 4位,故 8 个十六进制数共 32 位 DWORD 数据)
循环条件 for ( i = 0; i <= 6; ++i )
可以看出总共异或了 7 次,所以 *(4 * i + v4)
应该有 7 组,一组占 v7[]
的 4 个地址
也就是说 v7[0] ~ v7[3]
为一组,v7[4] ~ v7[7]
为二组,v7[8] ~ v7[11]
为三组,… … ,v7[24] ~ v7[27]
为七组
这里需要注意,异或后的值是从高地址处的字节开始异或的,又由于小端存放的原因,每一组数据按小端顺序:
*(4 * 1 + v4) = 0xE37EC854
*(4 * 2 + v4) = 0x9A16C764
*(4 * 3 + v4) = 0x326511CD
*(4 * 4 + v4) = 0x43D3E32D
*(4 * 5 + v4) = 0xD29DA992
*(4 * 6 + v4) = 0xD32C6DE6
*(4 * 7 + v4) = 0x6AFEBDB6
v2 为异或的结果,v3 为 v2 的首地址,*(v3 + j)
为每一轮异或的结果
再与 flag_data[4 * i + j]
异或,得到异或之后的 *(v3 + j)
的结果:
*(v3 + 1) = 0x3dd376bb
*(v3 + 2) = 0x44bb798b
*(v3 + 3) = 0xecc8af22
*(v3 + 4) = 0x9d7e5dc2
*(v3 + 5) = 0x0c30177d
*(v3 + 6) = 0x0d81d309
*(v3 + 7) = 0xb4530359
这里要注意:*(4 * i + v4)
和 0xDEADBEEF
异或得到的 *(v3 + j)
也是一个 32 位(8 个十六进制)的数据
而 flag_data[4 * i + j]
是 8 位(2 个十六进制),因此 for 循环 for ( j = 3; j >= 0; --j )
分四轮,每次用 *(v3 + j)
中的 2 个十六进制与 flag_data[4 * i + j]
的 2 个十六进制进行异或
之所以循环条件写的是 for ( j = 3; j >= 0; --j )
,是为了按小端序取 flag_data[4 * i + j]
中的数据来异或
看似 *(v3 + j)
中的数据是按正序来取的,但其实前面在处理 *(4 * i + v4)
的时候,已经将 v7[]
的小端序写成了大端序
所以 *(v3 + j)
正序其实对应的是 *(4 * i + v4)
的正序,也就是 v7[]
的反序
即:大地址和大地址异或,小地址和小地址异或,一一对应
根据以上条件将代码复现一遍即可,程序运行后输出的就是 flag
脚本
C++
#include <iostream>
#include <sstream>
#include <string>
#include <math.h>
#include <string.h>
using namespace std;
std::string tohex(int num, int width)
{
std::stringstream ioss; //定义字符串流
std::string s_temp; //存放转化后字符
ioss << std::hex << num; //以十六制形式输出
ioss >> s_temp;
if(width > s_temp.size())
{
std::string s_0(width - s_temp.size(), '0'); //位数不够则补0
s_temp = s_0 + s_temp; //合并
}
std::string s = "0x" + s_temp.substr(s_temp.length() - width, s_temp.length()); //取右width位
return s;
}
unsigned int Hex_to_Dec(string s, int index, int length){
int sum = 0;
int tmp[length]; // 存放十六进制的每一位字符转换后对应的十进制数
for(int i=0; i<length; i++){
if(s[index]>=97 && s[index]<=102){ // 处理a-f
tmp[i] = 9 + s[index] - 96;
}
else if(s[index]>=65 && s[index]<=70) { // 处理A-F
tmp[i] = 9 + s[index] - 64;
}
else{ // 处理0-9
tmp[i] = s[index] - 48;
}
sum = sum + tmp[i] * pow(16,length-1 -i);
index++;
}
return sum;
}
int main(){
unsigned int key[7] = {0xE37EC854,0x9A16C764,0x326511CD,0x43D3E32D,0xD29DA992,0xD32C6DE6,0x6AFEBDB6};
string key_data[7];
unsigned int flag_data[28] = {
0xDC, 0x17, 0xBF, 0x5B, 0xD4, 0x0A, 0xD2, 0x1B, 0x7D, 0xDA, 0xA7, 0x95, 0xB5, 0x32, 0x10, 0xF6,
0x1C, 0x65, 0x53, 0x53, 0x67, 0xBA, 0xEA, 0x6E, 0x78, 0x22, 0x72, 0xD3
};
unsigned int key_xor;
for (int i = 0; i <= 6; ++i )
{
int loop=0;
key_data[i] = tohex(key[i] ^ 0xDEADBEEF,8);
for (int j = 3; j >= 0; --j ) {
key_xor = Hex_to_Dec(key_data[i],2 + loop,2);
loop += 2;
printf("%c",(key_xor ^ flag_data[4 * i + j]));
}
}
return 0;
}
Python
key_data = [0xE37EC854, 0x9A16C764, 0x326511CD, 0x43D3E32D, 0xD29DA992, 0xD32C6DE6, 0x6AFEBDB6]
flag_data = [0xDC, 0x17, 0xBF, 0x5B, 0xD4, 0x0A, 0xD2, 0x1B, 0x7D, 0xDA, 0xA7, 0x95, 0xB5, 0x32, 0x10, 0xF6, 0x1C, 0x65, 0x53, 0x53, 0x67, 0xBA, 0xEA, 0x6E, 0x78, 0x22, 0x72, 0xD3]
def decode():
flag = ""
for i in range(7):
tmp = hex(key_data[i] ^ 0xDEADBEEF)
if len(tmp) < 10:
tmp = '0x' + '0' * (10 - len(tmp)) + tmp[2:]
print
"The XOR value:" + tmp
for j in range(4):
flag += chr(int('0x' + tmp[2 * j + 2:2 * j + 4], 16) ^ flag_data[4 * i + (3 - j)])
print("flag:" + flag)
if __name__ == "__main__":
decode()
结果
flag_is_you_know_cracking!!!
思路二
除直接解算法以外,还可以通过 OllyDBG 调试,绕过 if 判断语句,让程序自己输出 flag
将 32位 exe 程序拖入 OllyDBG,定位到 if 条件的位置
手动将这里的判断条件 test eax,eax
给 nop 掉
执行程序,key 随便输入即可
程序输出的 flag 是乱码,为:潇g??礰Dn:,=瀋?h肀t
这里其实是 Windows 平台下的程序有问题,在 key_data
赋值那一段是错误的,导致绕过 check_key
之后也得不到正确的结果,换成 elf 文件来看才是正确的
尝试在 linux 下进行调试
将 64 位 elf 程序拖入 Kali,执行语句:gdb task9_x86_64_46d01fe312d35ecf69c4ff8ab8ace75d080891dc
进行调试
输入 b main
在 main 函数的位置下断点,输入 r
执行程序
权限不够,发现文件没有执行权限,通过 chmod
增加执行权限,继续 r
输入 n
单步执行,一直单步执行,到达输入 key
的地方,随便输入一个 key
值,继续:
接下来继续单步执行 直到判断语句 test eax,eax
处,查看 eax 寄存器的值:i r eax
把 test eax,eax
改为真就行,发现 eax
寄存器的值为 0,修改 eax
寄存器的值为 1,指令:set $eax=1
输入 c
,直接执行到程序结束
结果
flag_is_you_know_cracking!!!