收获

  • 了解数据的大端序、小端序存放

  • 了解 * (4 * i + v4) 写法的含义(不一定是顺序数组,可能是好几个地址的值组合起来作为数组的一个元素)

  • 数据的存放方式转化为十六进制看得更清楚(IDA 快捷键:h)
    例如:-559038737(0xDEADBEEF)、106(0x6A)、-51(0xCD)等

  • 数据转化为十六进制进行异或时,位数应该相同 (32 位对 32 位,8 位对 8 位)

  • Windows 下 OllyDBG 调试

  • Linux 下 GDB 调试


【攻防世界】simple-check-100


思路一

给出了三个文件,分别是一个 32位 exe,一个 32位 elf,一个 64位 elf

将 64位 elf 拖入 IDA

攻防世界-simple-check-100 1.png

输入的 key 存放在 v9 的位置,check_key(v9) 对输入的 key 做了一个检测,若 check_key(v9) 返回 true,则调用 interesting_function(v7)

查看 check_key(v9)

攻防世界-simple-check-100 2.png

即对 key 的每一位进行相加求和,若和为 -559038737(十六进制:0xDEADBEEF),就返回 true

查看 interesting_function(v7)

攻防世界-simple-check-100 3.png

调用的这个函数只有一个输出,这里的 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!!!

攻防世界-simple-check-100 4.png


思路二

除直接解算法以外,还可以通过 OllyDBG 调试,绕过 if 判断语句,让程序自己输出 flag

将 32位 exe 程序拖入 OllyDBG,定位到 if 条件的位置

攻防世界-simple-check-100 5.png

手动将这里的判断条件 test eax,eax 给 nop 掉

攻防世界-simple-check-100 6.png

执行程序,key 随便输入即可

攻防世界-simple-check-100 7.png

程序输出的 flag 是乱码,为:潇g??礰Dn:,=瀋?h肀t

这里其实是 Windows 平台下的程序有问题,在 key_data 赋值那一段是错误的,导致绕过 check_key 之后也得不到正确的结果,换成 elf 文件来看才是正确的


尝试在 linux 下进行调试
将 64 位 elf 程序拖入 Kali,执行语句:gdb task9_x86_64_46d01fe312d35ecf69c4ff8ab8ace75d080891dc 进行调试

攻防世界-simple-check-100 8.png

输入 b main 在 main 函数的位置下断点,输入 r 执行程序

攻防世界-simple-check-100 9.png

权限不够,发现文件没有执行权限,通过 chmod 增加执行权限,继续 r

攻防世界-simple-check-100 10.png

攻防世界-simple-check-100 11.png

输入 n 单步执行,一直单步执行,到达输入 key 的地方,随便输入一个 key 值,继续:

攻防世界-simple-check-100 12.png

接下来继续单步执行 直到判断语句 test eax,eax 处,查看 eax 寄存器的值:i r eax

test eax,eax 改为真就行,发现 eax 寄存器的值为 0,修改 eax 寄存器的值为 1,指令:set $eax=1

攻防世界-simple-check-100 13.png

输入 c,直接执行到程序结束

攻防世界-simple-check-100 14.png


结果

flag_is_you_know_cracking!!!