Beijing 32位ELF, 运行输出一堆乱码, IDA静态分析后发现输出一堆数字异或值。
根据第一个异或过程, 前一个字符为’4’, 后一个字符为’f’
case 6 : v2 = byte_804A02D ^ byte_804A02C;
第二个异或过程, 前一个字符为’ ‘(空格), 后一个字符为’l’
猜测每次异或的后一个数字为flag中字符的十六进制, 查看所有异或值可以看到flag{}
都在里面
我们直接用 IDApython patch一下
import ida_bytesbg = 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的函数框中搜索, 这里是因为即便变量名被混淆但仍然能基本看出函数功能
这里截取部分函数的汇编代码:
可以看到, 每个函数都需要两个参数, 而函数名基本只有中间部分不同, 并且以_D3src__T3encVAyaa3_313131ZQsFNaNfQuZQx
为例, 我们可以猜测encVAyaa3
中3表示后面有3位, 正好313131
为3位。
这个意义在进入函数后可以明白, 所有函数操作基本一致, 以_D3src__T3encVAyaa3_313131ZQsFNaNfQuZQx
为例:
这里可以看到其中有个函数D3std5range__T5cycleTAyaZQlFNaNbNiNfQpZSQBnQBm__T5CycleTQBjZQl
, 有着的参数为三个, 第二个为3
, 第三个为"111"
, 和函数名一致, 因此我们只需要分析一个函数, 就可以根据函数名知道所有函数的操作
我们用IDA断在开头, 直接set ip
到encrypt函数中, 修改rsi和rdi的值分别为"333"
和3
, 接着断在第二个xor处, 直接查看此时参与异或的值:
继续运行可能由于堆栈不平的关系, 发生错误, 则这里我们可以猜测这第一个函数用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 cycledef 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
启动后如下:
随便输入一串字符串进行验证
静态分析 根据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 }
对比原结果可以认为进行了循环移位的操作。
接下来进入一个循环:
首先将掩码 作用于输入(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
计算常量数据和操作后的输入之间的“psadbw 绝对差值之和 ”, 更新xmm5
的值(0x7c96
)
执行psadbw xmm5, xmm2
后xmm5
的值
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
接着把结果的高低位存入EDI(0x7c9a-0x7ca7
)
我们直接跳到0x7cab
, 查看edi
的值
gef➤ p $edi $12 = 0x24a02ef
对比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
到这里我们可以知道这次循环要求的是
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
计数器减一并返回, 查看是否还有更多字节要检查
此时查看si
值
根据接下来的指令
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 binasciiimport structxmm5_start = binascii.unhexlify('220f02c883fbe083c0200f10cd0013b8' ) 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 ] 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 ) s = '' for j in range (8 ): if j == 7 -i: 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: 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()) 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
要求我们点1000次换一个字符, 一般遇到这种题首先想的就是直接修改点击次数之类的。.Net 32位的题, dnspy 32启动!
我们先不中断, 运行以后先点一次flag, 然后再点击暂停, 然后单步步过调试, 因为一开始肯定在输入函数中。
接着, 我们先步过一直到判断逻辑函数中。
稍微看下逻辑就能发现flag就是this.context
, 直接看局部变量就能看到flag了
得到flag: flag{ca201ed0-9e07-11e8-b6dd-000c29dcabfd}
give a try 根据IDA的反汇编结果, 重点函数在0x00401103
这里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] ) break ; }
首先flag长度为42 然后获得flag的字符和 设置srand, 这里dword_40406C
应该是个可预测的常数 接着根据srand设置值跟flag每位字符进行运算, 最终等于常数 由于利用srand和rand产生的随机数并不是真正的随机数, 只要传递给srand的随机数种子是确定的, 所产生的随机数在每次运行时都是确定的, 这个叫做伪随机数。这就奠定了可以使用爆破的方式求解
这里直接对dword_40406C
查交叉引用, 可以看到
初始值
.data:0040406C dword_40406C dd 0
根据这一段的函数名:TlsCallback_0
, 猜测使用了TLS技术, tls在函数线程执行之前可以获得优先执行的权利, 一般用于反调试, 也可以用于数据的保护。本题pizza大哥应该是用于反调了, 用了NtSetInformationThread
函数。
我们用OD调试程序, 查看dword_40406C
的值为0x31333359
那么接下来就是写脚本进行爆破了, 最后一点是:由于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, 0F AC96621h 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}
matricks IDA 打开, 主逻辑如下:
首先获得flag
, 长度为49
然后将flag
进行xor
操作, 把结果存放在savedregs_192
中, 其中 7 * (n_23 / 7) + n_23 % 7
的结果还是n_23
, 不过根据操作可以认为变成了[x][y]这样, 即可猜测savedregs_192
是一个n
行7
列的矩阵 同理初始化同样是n
行7
列的矩阵的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}