Beijing

32位ELF, 运行输出一堆乱码, IDA静态分析后发现输出一堆数字异或值。

根据第一个异或过程, 前一个字符为’4’, 后一个字符为’f’

case 6:
v2 = byte_804A02D ^ byte_804A02C;

第二个异或过程, 前一个字符为’ ‘(空格), 后一个字符为’l’

猜测每次异或的后一个数字为flag中字符的十六进制,
查看所有异或值可以看到flag{}都在里面

beijing-numbers

我们直接用 IDApython patch一下

import ida_bytes
bg = 0x804848A
end = 0x08048604


def patch(begin):
buff = [0xb9, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90]
ida_bytes.patch_bytes(begin, str(bytearray(buff)))


def next_instr(addr):
return addr + ItemSize(addr)


addr = bg

while addr < end:
next = next_instr(addr)
MakeCode(next)
if "movsx ecx" in GetDisasm(addr):
patch(addr)
print(addr)
addr = next

直接运行得flag: flag{amazing_beijing}

advance

非预期解

这道题如果正常去逆向的话是真的麻烦, 结果师傅发现了非预期解。

正常运行程序会输出一串 identification , 拖进 IDA 里面看发现是静态字符串 %02x输出, 不知道跟 flag 有什么关系, 但是程序正常逻辑中有多次逻辑相似的异或操作, 因此尝试对该字符串异或 flag{发现规律, 每次都是异或[45, 44]

s = b"K@LKVHr[DXEsLsYI@\\AMYIr\\EIZQ"

flag = bytearray()
for i in range(0, len(s), 2):
flag.append(s[i] ^ 45)
flag.append(s[i+1] ^ 44)

print(flag)

参考

CSAW CTF Quals 2016

分析

在题目中有很多函数被定义, 本能告诉作者(这大概就是题做多了吧……)有一个加密函数, 因此可以用objdump -x先找函数加密函数, 也可以直接在IDA的函数框中搜索, 这里是因为即便变量名被混淆但仍然能基本看出函数功能

advanced-find_encrypt_function

这里截取部分函数的汇编代码:

advanced-encrypt_function

可以看到, 每个函数都需要两个参数, 而函数名基本只有中间部分不同, 并且以_D3src__T3encVAyaa3_313131ZQsFNaNfQuZQx为例, 我们可以猜测encVAyaa3中3表示后面有3位, 正好313131为3位。

这个意义在进入函数后可以明白, 所有函数操作基本一致, 以_D3src__T3encVAyaa3_313131ZQsFNaNfQuZQx为例:

advanced-fun_111

这里可以看到其中有个函数D3std5range__T5cycleTAyaZQlFNaNbNiNfQpZSQBnQBm__T5CycleTQBjZQl, 有着的参数为三个, 第二个为3, 第三个为"111", 和函数名一致, 因此我们只需要分析一个函数, 就可以根据函数名知道所有函数的操作

我们用IDA断在开头, 直接set ip到encrypt函数中, 修改rsi和rdi的值分别为"333"3, 接着断在第二个xor处, 直接查看此时参与异或的值:

advanced-fun1_v12

advanced-fun1_v12

继续运行可能由于堆栈不平的关系, 发生错误, 则这里我们可以猜测这第一个函数用python表示为:

def fun_111(input, input_length):
result = ""
for c in input:
result += chr(ord(c)) ^ 0x31 ^ input_length)

这里0x31根据函数名的不同而不同, 如果为"313031303130", 那就是

def fun_101010(input, input_length):
result = ""
for c, k in zip(input, cycle("101010"):
result += chr(ord(c)) ^ ord(k) ^ input_length)

因此根据函数名可以写出解题脚本为:

from itertools import cycle
def xor(key, enc, enc_len):
return ''.join(chr(ord(a) ^ ord(b) ^ enc_len) for a, b in zip(cycle(key), enc))
keys = [str(x)*3 for x in range(1, 500)]

enc = "K@LKVHr[DXEsLsYI@\\AMYIr\\EIZQ"
for key in keys:
enc_len = len(enc) & 0xFF;
enc = xor(key, enc, enc_len)
print enc

得到flag:flag{d_with_a_template_phew}

blend

参考

CSAW CTF 2017 - Realism (400 pts)

启动

文件给了一个MBR, 因此需要用qemu模拟启动, -s表示开启端口调试使得gdb可以远程attach上去

qemu-system-i386 -drive format=raw, file=main.bin -s

启动后如下:

blend-interface

随便输入一串字符串进行验证

blend-interface2

静态分析

根据write up, First, we load the MBR into IDA, and after some tinkering, realize it gives a nice disassembly in 16-bit mode, 因此我们用16位模式加载他。但其实根据百度百科MBR中的分区信息结构中的每个分区表的项目是16个字节也可以猜测使用16位模式加载。这里是使用小端序。

根据MBR的主要功能及工作流程

启动PC机时, 系统首先对硬件设备进行测试, 测试成功后进入自举程序INT 19H, 然后读系统磁盘0柱面、0磁头、1扇区的主引导记录(MBR)内容到内存指定单元0:7C00地址开始的区域, 并执行MBR程序段

因此修改段地址为0x7C00, 最终结果(这里附上write up的代码, 常数部分不同)

程序在0x7C6F处检查flag

seg000:7C6F                 cmp     dword ptr ds:1234h, 'galf'
seg000:7C78 jnz loc_7D4D
seg000:7C7C movaps xmm0, xmmword ptr ds:1238h
seg000:7C81 movaps xmm5, xmmword ptr ds:loc_7C00
seg000:7C86 pshufd xmm0, xmm0, 1Eh
seg000:7C8B mov si, 8
seg000:7C8E
seg000:7C8E loc_7C8E: ; CODE XREF: seg000:7CC1↓j
seg000:7C8E movaps xmm2, xmm0
seg000:7C91 andps xmm2, xmmword ptr [si+7D90h]
seg000:7C96 psadbw xmm5, xmm2
seg000:7C9A movaps xmmword ptr ds:1268h, xmm5
seg000:7C9F mov di, ds:1268h
seg000:7CA3 shl edi, 10h
seg000:7CA7 mov di, ds:1270h
seg000:7CAB mov dx, si
seg000:7CAD dec dx
seg000:7CAE add dx, dx
seg000:7CB0 add dx, dx
seg000:7CB2 cmp edi, [edx+7DA8h]
seg000:7CBA jnz loc_7D4D
seg000:7CBE dec si
seg000:7CBF test si, si
seg000:7CC1 jnz short loc_7C8E

程序在输入flag以后, 猜测检查开头是否为flag。如果正确则把后面部分加载入xmm0, 并把loc_7c00处的数据载入xmm5, 然后对xmm0进行pshufd 压缩双字乱序.

接下来的操作基本上就是把循环遍历输入字节, 对每个字节执行xmm操作, 并将操作和MBR末尾处的字节序列进行比较。本地的重点就是了解执行了哪些XMM操作。

每个XMM寄存器长128位, 通常用于快速浮点操作。本题用于flag的混淆。

动态分析

我们通过下列指令启动gdb调试

gdb -ex 'target remote localhost:1234' \
-ex 'set architecture i8086' \
-ex 'break *0x7c6f' \
-ex 'continue'

这里四条指令的意思分别是: 连接调试端口, 设置指令集架构, 在0x7c6f处(cmp dword ptr ds:1234h, 'galf')下断点以及程序继续运行。

我们输入开头为flag的字符串, 如flag{12345678912345}

输入后我们执行到movaps xmm0, xmmword ptr ds:1238h, 既为地址0x7C7C执行后查看xmm0, 即为我们输入的值

gef➤  p $xmm0
$2 = {
.....
uint128 = 0x7d35343332313938373635343332317b
}

可以看到我们输入的结果, 继续执行过movaps xmm5, xmmword ptr ds:loc_7C00, 查看xmm5的值

gef➤  p $xmm5
$3 = {
.....
uint128 = 0x220f02c883fbe083c0200f10cd0013b8
}

执行过pshufd xmm0, xmm0, 1Eh查看结果:

gef➤  p $xmm0
$4 = {
.....
uint128 = 0x3332317b373635347d35343332313938
}

对比原结果可以认为进行了循环移位的操作。

接下来进入一个循环:

  1. 首先将掩码作用于输入(0x7c91

    gef➤  x/2x ($esi+0x7d90)
    0x7d98: 0xffffffffffffff00 0xffffffffffffff00

    执行andps xmm2, xmmword ptr [si+7D90h]后查看xmm2的值

    gef➤  p $xmm2
    $9 = {
    uint128 = 0x3332317b373635007d35343332313900
    }
    // 这里对比原来0x3332317b373635347d35343332313938可以发现第7位字节和第15位字节被与为0

    查看0x7D90的值:

    seg000:7D90                 dd 0FFFFFFFFh
    seg000:7D94 dd 0FFFFFFFFh
    seg000:7D98 dd 0FFFFFF00h
    seg000:7D9C dd 0FFFFFFFFh
    seg000:7DA0 dd 0FFFFFF00h
    seg000:7DA4 dd 0FFFFFFFFh
  2. 计算常量数据和操作后的输入之间的“psadbw 绝对差值之和”, 更新xmm5的值(0x7c96

    执行psadbw xmm5, xmm2xmm5的值

    gef➤  p $xmm5
    $10 = {
    ......
    v2_int64 = {0x24a, 0x2ef}, uint128 = 0x2ef000000000000024a
    }

    根据上述链接查看psadbw意义:

    PSADBW instructions when using 128-bit operands:
    TEMP0 ABS(DEST[7-0] - SRC[7-0]);
    * repeat operation for bytes 2 through 14 *;
    TEMP15 ABS(DEST[127-120] - SRC[127-120]);
    DEST[15-0] SUM(TEMP0...TEMP7);
    DEST[63-6] 000000000000H;
    DEST[79-64] SUM(TEMP8...TEMP15);
    DEST[127-80] 000000000000H;

    因此根据xmm5原值0x220f02c883fbe083c0200f10cd0013b8可以知道有如下等式, a[x]表示第x位字节:

    abs(a[0]-0x22) + abs(a[1]-0x0f) + abs(a[2]-x0x02) + abs(a[3]-0xc8) + abs(a[4]-0x83) + abs(a[5]-0xfb) + abs(a[6]-0xe0) + abs(0 - 0x83) = 0x2ef

    abs(a[8]-0xc0) + abs(a[9]-0x20) + abs(a[10]-0x0f) + abs(a[11]-0x10) + abs(a[12]-0xcd) + abs(a[13]-0x00) + abs(a[14]-0x13) + abs(0-0xb8) = 0x24a
  3. 接着把结果的高低位存入EDI(0x7c9a-0x7ca7)

    我们直接跳到0x7cab, 查看edi的值

    gef➤  p $edi
    $12 = 0x24a02ef
  4. 对比EDI和常量数据(0x7cb2)

    0x7cab-0x7CB0都是寻址操作可以不理, 我们直接跳到0x7cb2

    查看此时[edx+7DA8h]的值

    gef➤  x/x $edx+0x7da8
    0x7dc4: 0x03110304

    我们到IDA中对应位置查看, 刚好可以发现一堆常数

    seg000:7DA8                 dd 2DD02F6h
    seg000:7DAC dd 2DC02E8h
    seg000:7DB0 dd 2D802EDh
    seg000:7DB4 dd 2CE02E2h
    seg000:7DB8 dd 2C402E2h
    seg000:7DBC dd 2D402DBh
    seg000:7DC0 dd 2D902CDh
    seg000:7DC4 dd 3110304h
  5. 到这里我们可以知道这次循环要求的是

    abs(a[0]-0x22) + abs(a[1]-0x0f) + abs(a[2]-x0x02) + abs(a[3]-0xc8) + abs(a[4]-0x83) + abs(a[5]-0xfb) + abs(a[6]-0xe0) + abs(0 - 0x83) = 0x304

    abs(a[8]-0xc0) + abs(a[9]-0x20) + abs(a[10]-0x0f) + abs(a[11]-0x10) + abs(a[12]-0xcd) + abs(a[13]-0x00) + abs(a[14]-0x13) + abs(0-0xb8) = 0x311

    常数从0x7DC4对比至0x7DA8

  6. 计数器减一并返回, 查看是否还有更多字节要检查

此时查看si

gef➤  p $si
$16 = 0x8

根据接下来的指令

seg000:7CBE                 dec     si
seg000:7CBF test si, si
seg000:7CC1 jnz short loc_7C8E ; 这里刚好是我们循环开始的地址

从而可知这种循环一共执行8次, 根据前面我们查看的0x7D90的值, 可以理解循环每次与成0的输入位分别从[7->0][15->8]

Z3求解

我们可以将输入的每个字节看作单独的变量, 从a[0]a[15]由于步骤2指令的性质, 每次循环都给我们提供了两个方程, 因此我们一共拥有2 * 8 = 16 个方程从而可以求解出这16个变量的结果

虽然结果设计绝对值, 即它是非线性的, 但这并不影响我们使用求解器Z3求解其结果

我们先用python模拟出他的结果, 这里我们照着wp改常数就可以了, 当然理解了流程的我们也可以完全可以自己编写了, 这里修改输出方便我们直接贴到第二个解题脚本中进行Z3求解:

import binascii
import struct

# Initial value of xmm5
xmm5_start = binascii.unhexlify('220f02c883fbe083c0200f10cd0013b8')

# The data stored at 0x7DA8 and compared against esi
esi_consts = [
'f602dd02', 'e802dc02', 'ed02d802', 'e202ce02', 'e202c402', 'db02d402', 'cd02d902', '04031103', ]
esi_consts = [struct.unpack('<I', binascii.unhexlify(c))[0] for c in esi_consts]
esi_consts = esi_consts[::-1]

# Our 16 variables ('a[0]' through 'a[15]')
variables = [("a[" + str(i) + "]") for i in range(16)]

def esi_to_xmm5(esi):
s1 = esi % (1 << 0x10)
s2 = (esi - s1) >> (0x10)
w = struct.pack('>Q', s1) + struct.pack('>Q', s2)
return w

def print_constraints():
for i in range(8):
prev_esi = esi_consts[i-1]
xmm5 = esi_to_xmm5(prev_esi)
if i == 0:
xmm5 = xmm5_start

esi = esi_consts[i]
s1 = esi % (1 << 0x10)
s2 = (esi - s1) >> (0x10)

# sum of absolute differences between xmm5 and our flag
s = ''
for j in range(8):
if j == 7-i:
# This is the masking step
s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
continue
s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
s += '0 == {}'.format(s1)
print("s.add(" + s + ")")

s = ''
for j in range(8, 16):
if j-8 == 7-i:
# This is the masking step
s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
continue
s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
s += '0 == {}'.format(s2)
print("s.add(" + s + ")")

if __name__ == '__main__':
print_constraints()

得到输出:

s.add(abs(a[0]-34) + abs(a[1]-15) + abs(a[2]-2) + abs(a[3]-200) + abs(a[4]-131) + abs(a[5]-251) + abs(a[6]-224) + abs(0-131) + 0 == 772)
s.add(abs(a[8]-192) + abs(a[9]-32) + abs(a[10]-15) + abs(a[11]-16) + abs(a[12]-205) + abs(a[13]-0) + abs(a[14]-19) + abs(0-184) + 0 == 785)
s.add(abs(a[0]-0) + abs(a[1]-0) + abs(a[2]-0) + abs(a[3]-0) + abs(a[4]-0) + abs(a[5]-0) + abs(0-3) + abs(a[7]-4) + 0 == 717)
s.add(abs(a[8]-0) + abs(a[9]-0) + abs(a[10]-0) + abs(a[11]-0) + abs(a[12]-0) + abs(a[13]-0) + abs(0-3) + abs(a[15]-17) + 0 == 729)
s.add(abs(a[0]-0) + abs(a[1]-0) + abs(a[2]-0) + abs(a[3]-0) + abs(a[4]-0) + abs(0-0) + abs(a[6]-2) + abs(a[7]-205) + 0 == 731)
s.add(abs(a[8]-0) + abs(a[9]-0) + abs(a[10]-0) + abs(a[11]-0) + abs(a[12]-0) + abs(0-0) + abs(a[14]-2) + abs(a[15]-217) + 0 == 724)
s.add(abs(a[0]-0) + abs(a[1]-0) + abs(a[2]-0) + abs(a[3]-0) + abs(0-0) + abs(a[5]-0) + abs(a[6]-2) + abs(a[7]-219) + 0 == 738)
s.add(abs(a[8]-0) + abs(a[9]-0) + abs(a[10]-0) + abs(a[11]-0) + abs(0-0) + abs(a[13]-0) + abs(a[14]-2) + abs(a[15]-212) + 0 == 708)
s.add(abs(a[0]-0) + abs(a[1]-0) + abs(a[2]-0) + abs(0-0) + abs(a[4]-0) + abs(a[5]-0) + abs(a[6]-2) + abs(a[7]-226) + 0 == 738)
s.add(abs(a[8]-0) + abs(a[9]-0) + abs(a[10]-0) + abs(0-0) + abs(a[12]-0) + abs(a[13]-0) + abs(a[14]-2) + abs(a[15]-196) + 0 == 718)
s.add(abs(a[0]-0) + abs(a[1]-0) + abs(0-0) + abs(a[3]-0) + abs(a[4]-0) + abs(a[5]-0) + abs(a[6]-2) + abs(a[7]-226) + 0 == 749)
s.add(abs(a[8]-0) + abs(a[9]-0) + abs(0-0) + abs(a[11]-0) + abs(a[12]-0) + abs(a[13]-0) + abs(a[14]-2) + abs(a[15]-206) + 0 == 728)
s.add(abs(a[0]-0) + abs(0-0) + abs(a[2]-0) + abs(a[3]-0) + abs(a[4]-0) + abs(a[5]-0) + abs(a[6]-2) + abs(a[7]-237) + 0 == 744)
s.add(abs(a[8]-0) + abs(0-0) + abs(a[10]-0) + abs(a[11]-0) + abs(a[12]-0) + abs(a[13]-0) + abs(a[14]-2) + abs(a[15]-216) + 0 == 732)
s.add(abs(0-0) + abs(a[1]-0) + abs(a[2]-0) + abs(a[3]-0) + abs(a[4]-0) + abs(a[5]-0) + abs(a[6]-2) + abs(a[7]-232) + 0 == 758)
s.add(abs(0-0) + abs(a[9]-0) + abs(a[10]-0) + abs(a[11]-0) + abs(a[12]-0) + abs(a[13]-0) + abs(a[14]-2) + abs(a[15]-220) + 0 == 733)

z3求解脚本:

from z3 import *

def abs(x):
return If(x >= 0, x, -x)

s = Solver()

a = IntVector('x', 16)

# 把上面的贴下来

for i in range(16):
s.add(a[i] >= 0x20)
s.add(a[i] <= 0x7e)

if s.check() == sat:
mod = s.model()
chars = []
for i in range(16):
chars.append(mod[a[i]].as_long())

# print chars
flag = ''.join([chr(w) for w in chars])
flag = flag[::-1]
print('flag' + flag[12:] + flag[8:12] + flag[0:4] + flag[4:8])

得flag: flagmbr_is_funny__eh

babyre

babyre-start

要求我们点1000次换一个字符, 一般遇到这种题首先想的就是直接修改点击次数之类的。.Net 32位的题, dnspy 32启动!

我们先不中断, 运行以后先点一次flag, 然后再点击暂停, 然后单步步过调试, 因为一开始肯定在输入函数中。

接着, 我们先步过一直到判断逻辑函数中。

babyre-flag-1

稍微看下逻辑就能发现flag就是this.context, 直接看局部变量就能看到flag了

babyre-flag

得到flag: flag{ca201ed0-9e07-11e8-b6dd-000c29dcabfd}

give a try

根据IDA的反汇编结果, 重点函数在0x00401103

give_a_try-code

这里IDA对v7 = v6 * (unsigned __int64)v6 % 0xFAC96621后的反汇编有些问题, 重点看:

if ( strlen(flag) != 42 )
return MessageBoxA(0, aThinkAgain, 0, 0);
v2 = 0;
v3 = *flag;
v4 = flag + 1;
while ( v3 )
{
v2 += v3;
v3 = *v4++;
}
srand(dword_40406C ^ v2);
for ( i = 0; i != 42; ++i )
{
v6 = (unsigned __int8)flag[i] * rand();
.....
if ( result != checkNumber[i] ) // result为上述操作的结果
break;
}
  • 首先flag长度为42
  • 然后获得flag的字符和
  • 设置srand, 这里dword_40406C应该是个可预测的常数
  • 接着根据srand设置值跟flag每位字符进行运算, 最终等于常数

由于利用srand和rand产生的随机数并不是真正的随机数, 只要传递给srand的随机数种子是确定的, 所产生的随机数在每次运行时都是确定的, 这个叫做伪随机数。这就奠定了可以使用爆破的方式求解

这里直接对dword_40406C查交叉引用, 可以看到

give_a_try-xor_const

初始值

.data:0040406C dword_40406C    dd 0

根据这一段的函数名:TlsCallback_0, 猜测使用了TLS技术, tls在函数线程执行之前可以获得优先执行的权利, 一般用于反调试, 也可以用于数据的保护。本题pizza大哥应该是用于反调了, 用了NtSetInformationThread函数。

我们用OD调试程序, 查看dword_40406C的值为0x31333359

give_a_try-xor_const_od

那么接下来就是写脚本进行爆破了, 最后一点是:由于IDA的f5反编译的差距较大, 关于这道题利用内联汇编的形式写脚本:

#include<iostream>
#include<cstdlib>
#include<cstdio>
const int BUFF_LEN = 255 * 50 * 50;
int* pbuff = nullptr;

unsigned int checkNumber[42] = {
0x63B25AF1, 0x0C5659BA5, 0x4C7A3C33, 0x0E4E4267, 0x0B611769B,0x3DE6438C, 0x84DBA61F, 0x0A97497E6, 0x650F0FB3, 0x84EB507C,0x0D38CD24C, 0x0E7B912E0, 0x7976CD4F, 0x84100010, 0x7FD66745,0x711D4DBF, 0x5402A7E5, 0x0A3334351, 0x1EE41BF8, 0x22822EBE,0x0DF5CEE48, 0x0A8180D59, 0x1576DEDC, 0x0F0D62B3B, 0x32AC1F6E,0x9364A640, 0x0C282DD35, 0x14C5FC2E, 0x0A765E438, 0x7FCF345A,0x59032BAD, 0x9A5600BE, 0x5F472DC5, 0x5DDE0D84, 0x8DF94ED5,0x0BDF826A6, 0x515A737A, 0x4248589E, 0x38A96C20, 0x0CC7F61D9,0x2638C417, 0x0D9BEB996 };

unsigned int fun(int a1, int a2)
{
__asm {
mov eax, dword ptr[ebp + 8] //第一个参数
movzx ecx, byte ptr[ebp + 12] //第二个参数
mul ecx
mov ecx, 0FAC96621h
push eax
xor edx, edx
div ecx
pop eax
push edx
mul eax
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
pop edx
mul edx
div ecx
mov eax, edx
}
}

int main()
{
pbuff = new int[BUFF_LEN];
std::string flag[0x7e + 1];
for (int sum = 0x20 * 42; sum <= 0x7E * 42; sum++)
{
srand(sum ^ 0x31333359);
for (int j = 0; j < 42; j++)
pbuff[sum * 42 + j] = rand();
}

for (int num = 0; num < 42; num++)
{
for (int i = 0x20; i <= 0x7E; ++i)
{
for (int sum = 0x20 * 42; sum <= 0x7E * 42; sum++)
{

if (fun(pbuff[sum * 42 + num], i) == checkNumber[num])
{
flag[int(sum / 42)].push_back((char)i);
printf("%c", (char)i);
}
}
}
}

for (int i = 0x20; i <= 0x7E; i++)
{

if (flag[i].length() == 42) {
std::cout << flag[i];
break;
}

}
printf("\nend\n");
return 0;
}

得到flag:flag{wh3r3_th3r3_i5_@_w111-th3r3_i5_@_w4y}

give_a_try-flag

matricks

IDA 打开, 主逻辑如下:

matricks-code

  • 首先获得flag, 长度为49
  • 然后将flag进行xor操作, 把结果存放在savedregs_192中, 其中 7 * (n_23 / 7) + n_23 % 7的结果还是n_23, 不过根据操作可以认为变成了[x][y]这样, 即可猜测savedregs_192是一个n7列的矩阵
  • 同理初始化同样是n7列的矩阵的savedregs_128
  • 接着进入三层循环, 最里层循环是一个累加操作, 实际上就是矩阵相乘, 相乘后与数组check_num对比

根据上述分析, 本质上矩阵相乘就是一堆线性方程, 所以z3直接解(虽然也可以求逆矩阵, 但毕竟抄算法不费脑子)

from z3 import *
check_num = [0xAA, 0x7A, 0x24, 0x0A, 0xA8, 0xBC, 0x3C, 0xFC, 0x82, 0x4B, 0x51, 0x52, 0x5E, 0x1C, 0x82, 0x1F, 0x79, 0xBA, 0xB5, 0xE3, 0x43, 0x04, 0xFD, 0xAC, 0x10, 0xB5, 0x63, 0xBD, 0x8D, 0xE7, 0x35, 0xD9, 0xD3, 0xE8, 0x42, 0x6D, 0x71, 0x5A, 0x09, 0x54, 0xE9, 0x9F, 0x4C, 0xDC, 0xA2, 0xAF, 0x11, 0x87, 0x94]
xor_num = list(map(ord, "some legends r told, some turn to dust or to gold"))
input = [BitVec("x%d" % i, 8) for i in range(49)]
length = 49

savedregs_128 = [0 for i in range(length)]
savedregs_192 = [0 for i in range(length)]
n_23 = 23
for i in range(length):
savedregs_192[n_23] = input[i] ^ n_23
savedregs_128[i] = xor_num[n_23] ^ i
n_23 = (n_23 + 13) % length
i1 = 3
i2 = 4
i3 = 5
i4 = 41
s = Solver()
for i in range(7):
for j in range(7):
sum = 0
for k in range(7):
sum += savedregs_128[7 * i3 + i2] * savedregs_192[7 * i1 + i3]
i3 = ( i3 + 5 ) % 7
s.add(sum == check_num[i4] ^ i4)
i4 = (i4 + 31) % length
i2 = (i2 + 4) % 7
i1 = (i1 + 3) % 7
if s.check() == sat:
m = s.model()
flag = ""
for i in range(length):
flag += (chr(m[input[i]].as_long()))
print(flag)

flag: flag{Everyth1n_th4t_kill5_m3_m4kes_m3_fee1_aliv3}