当时比赛的时候只做了程序分析,还得是mmr大爹和se大爹最后AK了电子取证还出了个pwn+web直接起飞了。
逆向只有一道题,纯坐牢呜呜呜。:<
LOADER
64bit程序,无壳,IDApro打开以后主函数只有几行
1 2 3 4 5 6 7 8
| int __cdecl main(int argc, const char **argv, const char **envp) { DWORD flOldProtect;
VirtualProtect(&unk_7FF6741C3040, 0x34166ui64, 0x40u, &flOldProtect); sub_7FF6741C1040(&unk_7FF6741C3040); return 0; }
|
查询MSDN可知,VirtualProtect
函数的作用是修改任何进程的访问保护,语法为
1 2 3 4 5 6
| BOOL VirtualProtect( [in] LPVOID lpAddress, [in] SIZE_T dwSize, [in] DWORD flNewProtect, [out] PDWORD lpflOldProtect );
|
再来看主函数的调用,显然是把一段内存中起始地址为0x7FF6741C3040,大小为0x34166的进程设置为可访问,并且下面再调用该进程。
由PE头信息可知,MZ开头的一般是一个新的可执行文件,这里多次尝试dump该文件均失败。。。
于是只能硬上动调,由于程序运行的时候会出现plz input your flag(format: flag{decimal number})
的输入提示,于是想到找到该提示的输出语句,并且在scanf处卡死便可以找到主函数逻辑。
于是一路F8找到跑飞的地方,然后重新启用动调在该处F7步入接着F8按死,经过多次重复操作以后找到了主函数。
其中在sub_7FF624926220
处跑飞,判断输入语句在该函数内。
接着往下看
判断flag为长度42位,开头为flag
的字符串,这几行的作用就是判断其框架和长度,我们可以根据这个构造测试输入,这里拿flag{731926347612678468721647812683469788}
举例子(瞎几把打的)。
label_15
就是格式错误退出程序的地方。
再往下,把中间的36位字符串拿出来,取出前面的18位转化成整形,存在input_1里面
注意:这个input_1
里面存的是一个地址,打开该地址后,由于这是一个int128位整形,所以需要按四次D把数据转化成dq类型,可以看出正好是前18位的内容
还得是SG👴!
后面也有同样的操作
接下来就是判断flag是否符合条件的部分了
1 2 3 4 5 6 7 8 9 10 11
| sub_7FF624934380((__int64)&data_1, 10i64, &xmmword_7FF62494F190); sub_7FF624934380((__int64)&data_2, 10i64, &xmmword_7FF62494F1A0); v30 = _mm_loadu_si128((const __m128i *)&xmmword_7FF62494F190); v29 = _mm_loadu_si128((const __m128i *)&input_1); if ( !(unsigned __int8)sub_7FF6249350C0(&v30, &v29) ) goto LABEL_15; v30 = _mm_loadu_si128((const __m128i *)&input_1); v29 = _mm_loadu_si128((const __m128i *)&xmmword_7FF62494F1A0); if ( !(unsigned __int8)sub_7FF6249350C0(&v30, &v29) ) goto LABEL_15;
|
首先我们找到data_1和data_2的值,分别为72057594037927936和1152921504606846976,运行到sub_7FF624934380
时,发现是一个赋值函数,把数据分别赋值给xmmword_7FF62494F190
和xmmword_7FF62494F1A0
,之后又赋值给v30
和v29
,注意两次比大小的顺序是不一样的。
于是得出
之后的代码分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| v31 = 0i64; v32 = 0i64; v30 = (__m128i)input_1; v29 = (__m128i)input_1; multify(&v30, &v29, (__int64)&v31); input1_mul_input1 = v31; if ( v31 ) *(_QWORD *)(v31 - 16) += 8i64; if ( (_QWORD)ans1 ) { v23 = ans1 - 16; v24 = *(_QWORD *)(ans1 - 16) - 8i64; *(_QWORD *)(ans1 - 16) = v24; if ( v24 <= 7 ) sub_7FF624929580(off_7FF62493A350 + 3, v23); } *(_QWORD *)&ans1 = input1_mul_input1; v33 = 0ui64; BYTE8(ans1) = v32; v30 = (__m128i)input_2; v29 = (__m128i)input_2; multify(&v30, &v29, (__int64)&v33); input2_mul_input2 = _mm_load_si128(&v33); v34 = 0i64; v35 = 0i64; v30 = input2_mul_input2; multify2(&v30, 11i64, &v34); v26 = v34; if ( v34 ) *(_QWORD *)(v34 - 16) += 8i64; if ( (_QWORD)ans2 ) { v27 = ans2 - 16; v28 = *(_QWORD *)(ans2 - 16) - 8i64; *(_QWORD *)(ans2 - 16) = v28; if ( v28 <= 7 ) sub_7FF624929580(off_7FF62493A350 + 3, v27); } *(_QWORD *)&ans2 = v26; v30 = _mm_loadu_si128((const __m128i *)&ans1); BYTE8(ans2) = v35; v29 = _mm_loadu_si128((const __m128i *)&ans2); subtraction(&v30, &v29, &ans3); sub_7FF624934380((__int64)&unk_7FF624939D50, 10i64, &answer); v30 = _mm_loadu_si128((const __m128i *)&ans3); v29 = _mm_loadu_si128((const __m128i *)&answer); if ( sub_7FF624935540(&v30, &v29) ) { qword_7FF62493D6A0 = 1i64; } else { LABEL_15: if ( qword_7FF62493D6A0 != 1 ) return sub_7FF6249256F0((__int64)&unk_7FF624939D00, 1i64); } return sub_7FF6249256F0((__int64)&unk_7FF624939D28, 1i64); }
|
于是把两段代码合起来我们可以得到一个大整数方程和不等式:
input_1
和input_2
分别为36位flag的前18位和后18位。
crypto✌说这个叫pell方程(佩尔方程),可以用脚本,也可以用wolframalpha
在线解方程
由于x和y的长度加起来少了一位,应该是在y前面加个0,于是构造flag{118936021352508390035860559716724409}
纯纯废物了这下QWQ。