攻防世界-polyre

zhugeshi 发布于 2025-04-01 95 次阅读


攻防世界-polyre WP 难度5

大概的流程

刚开始直接ollvm混淆吓死你

1.首先使用deflat.py去除控制流平坦化,这里给出使用示例:

(angr-dev) <path>/deflat/flat_control_flow$ python3 deflat.py -f samples/bin/check_passwd_x8664_flat --addr 0x400530
*******************relevant blocks************************
prologue: 0x400530
main_dispatcher: 0x400554
pre_dispatcher: 0x40099b
retn: 0x40098f
relevant_blocks: ['0x40086a', '0x40080d', '0x4008ee', '0x40094f', '0x40084e', '0x400819', '0x400886', '0x40095b', '0x4007ec', '0x40092e', '0x4008a9', '0x4008cc', '0x40091b', '0x40097c', '0x400837']
*******************symbolic execution*********************
-------------------dse 0x40086a---------------------
-------------------dse 0x40080d---------------------
-------------------dse 0x4008ee---------------------
-------------------dse 0x40094f---------------------
-------------------dse 0x40084e---------------------
-------------------dse 0x400819---------------------
-------------------dse 0x400886---------------------
-------------------dse 0x40095b---------------------
-------------------dse 0x4007ec---------------------
-------------------dse 0x40092e---------------------
-------------------dse 0x4008a9---------------------
-------------------dse 0x4008cc---------------------
-------------------dse 0x40091b---------------------
-------------------dse 0x40097c---------------------
-------------------dse 0x400837---------------------
-------------------dse 0x400530---------------------
************************flow******************************
0x40084e:  ['0x40086a', '0x40095b']
0x40086a:  ['0x400886', '0x40094f']
0x400530:  ['0x4007ec']
0x4008a9:  ['0x4008cc', '0x40094f']
0x400886:  ['0x4008a9', '0x40094f']
0x4007ec:  ['0x400819', '0x40080d']
0x40091b:  ['0x40098f']
0x40080d:  ['0x40084e']
0x40092e:  ['0x40094f']
0x4008ee:  ['0x40091b', '0x40092e']
0x400819:  ['0x400837']
0x40094f:  ['0x40097c']
0x40095b:  ['0x40097c']
0x40097c:  ['0x40098f']
0x400837:  ['0x4007ec']
0x4008cc:  ['0x4008ee', '0x40094f']
0x40098f:  []
************************patch*****************************
Successful! The recovered file: check_passwd_flat_recovered

2.这个时候我们就可以得到第一步去混淆的代码,但是仔细看这里的代码还是有点不够清晰,所以我们接着动调,看看函数的逻辑是怎么执行的。

3.注意这个地方的代码,这里的条件是恒不成立的,意味着我们可以修改汇编中的代码,改变函数跳转的逻辑从而继续去除混淆。

3.我们跟踪进汇编看看,可以发现有个jnz指令,并且每次都会执行这个指令,我们干脆直接改为jmp,来简化流程。

4.使用ida python脚本(以后要着重学习一下)

st = 0x0000000000400620 #main开始
end = 0x0000000000402144 #main结束

def patch_nop(start,end):
    for i in range(start,end):
        ida_bytes.patch_byte(i, 0x90)       #修改指定地址处的指令  0x90是最简单的1字节nop

def next_instr(addr):
    return addr+idc.get_item_size(addr)     #获取指令或数据长度,这个函数的作用就是去往下一条指令

addr = st
while(addr<end):
    next = next_instr(addr)
    if "ds:dword_603054" in GetDisasm(addr):  #GetDisasm(addr)得到addr的反汇编语句
        while(True):
            addr = next
            next = next_instr(addr)
            if "jnz" in GetDisasm(addr):
                dest = idc.get_operand_value(addr, 0)       #得到操作数,就是指令后的数
                ida_bytes.patch_byte(addr, 0xe9)     #0xe9 jmp后面的四个字节是偏移
                ida_bytes.patch_byte(addr+5, 0x90)   #nop第五个字节
                offset = dest - (addr + 5)  #调整为正确的偏移地址 也就是相对偏移地址 - 当前指令后的地址
                ida_bytes.patch_dword(addr + 1, offset) #把地址赋值给jmp后
                print("patch bcf: 0x%x"%addr)
                addr = next
                break
    else:
        addr = next

这里详细讲一下

idc.get_operand_value(addr, 0) #获取操作数的值

应该是得到jnz指令后面标号的地址。

然后就可以得到进一步去混淆的代码了。

5.跳转仍然有些奇葩,接下来交给我们万能的gpt大人。

for (i = 0; i < 64; ++i) {
    if (s[i] == '\n') {
        s[i] = '\0';
        break;
    }
}

for (j = 0; j < 6; ++j) {
    int64_t v = *(_QWORD *)&s[8 * j]; // 取 8 字节数据
    for (k = 0; k < 64; ++k) {
        if (v < 0)
            v = (v * 2) ^ 0xB0004B7679FA26B3LL; // 负数时异或
        else
            v *= 2; // 正数时左移
    }
    *(_QWORD *)&s1[8 * j] = v; // 存入结果
}

6.解密脚本

s=[0xBC8FF26D43536296, 0x520100780530EE16, 0x4DC0B5EA935F08EC,0x342B90AFD853F450, 0x8B250EBCAA2C3681, 0x55759F81A2C68AE4]
res=""
key=0xB0004B7679FA26B3
for item in s:
    for j in range(64):
        f=item&1
        if f==1:
            item=(item^key)//2
            item = item | 0x8000000000000000
        else:
            item=item//2
    n=hex(item)[2:]
    print(hex(item))
    for j in range(len(n)-1,-1,-2):
        res += chr(int("0x" + n[j-1:j + 1], 16))
print(res)

cpp的时候要注意用unsigned标号,不然会有点怪。

#include <iostream>
#include <cstdint>

using namespace std;

const unsigned long long XOR_CONST = 0xB0004B7679FA26B3LL;

unsigned long long reverse_hash(unsigned long long hashed_value) {
    for (int k = 63; k >= 0; --k) {  // 逆向 64 轮
        int f = hashed_value & 1;  // 取最低位
        if (f == 1) {
            hashed_value = (hashed_value ^ XOR_CONST) / 2;
            hashed_value |= 0x8000000000000000LL;  // 强制恢复符号位
        }
        else {
            hashed_value /= 2;
        }
    }
    printf("hashed_value: %llx\n", hashed_value);
    return hashed_value;
}

int main() {
    unsigned long long s1[6] = {
        0xBC8FF26D43536296,
        0x520100780530EE16,
        0x4DC0B5EA935F08EC,
        0x342B90AFD853F450,
        0x8B250EBCAA2C3681,
        0x55759F81A2C68AE4
    };

    char res[48] = { 0 };

    for (int j = 0; j < 6; ++j) {
        unsigned long long original_value = reverse_hash(s1[j]);
        memcpy(res + j * 8, &original_value, 8); // 复制 8 字节数据
    }

    cout << "恢复的字符串: " << res << endl;
    return 0;
}

7.得到最终的flag

0x6666367b67616c66
0x63362d3039333932
0x2d363563342d3032
0x3539612d30376162
0x6631643365383537
0x7d38
flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}

参考链接 https://blog.csdn.net/weixin_52369224/article/details/122524328

超级大菜鸡!!!
最后更新于 2025-04-01