[GXYCTF2019]simple CPP
知识点
解题
首先F12+shift查看字符串, 找到关键代码,
if ( (v23 | v22 | v28 & v29) == (~*v18 & v29 | 864693332579200012i64) && v3 ) { v32 = print(std::cout, (__int64)"Congratulations!flag is GXY{"); v33 = flag; if ( v41 >= 0x10 ) v33 = (void **)flag[0]; v34 = sub_140001FD0(v32, (__int64)v33, v40); print(v34, (__int64)"}"); j_j_free(v6); } else { print(std::cout, (__int64)"Wrong answer!try again"); j_j_free(v6); }
|
经过分析可知,易知v33为flag并最后输出,上翻flag,找到输入sub_140001DE0(std::cin, flag);
接下来一步步分析flag的字符串是如何加密的
第一步
if ( v40 ) { v8 = 0i64; do { v9 = flag; if ( v41 >= 0x10 ) v9 = (void **)flag[0]; v10 = &i_will; if ( (unsigned __int64)qword_140006060 >= 0x10 ) v10 = (void **)i_will; v6[v8] = *((_BYTE *)v9 + v8) ^ *((_BYTE *)v10 + v7 % 27); ++v7; ++v8; } while ( v7 < v40 );
|
发现 flag与i_will变量异或赋值给v6.
i_will变量的值获取不到直接试试动调
输入11111111111111
经过调试得到i_will的变量为i_will_check_is_debug_or_not
由于这里是逐字符异或的,所以猜测,flag
的长度就是key
的长度,len(key)=28
,所以重新输入一个长度为28
的flag
:
‘1’*28
第二步
do { v17 = *v16 + v11; ++v15; ++v16; switch ( v15 ) { case 8: v14 = v17; goto LABEL_23; case 16: v13 = v17; goto LABEL_23; case 24: v12 = v17; LABEL_23: v17 = 0i64; break; case 32: print(std::cout, (__int64)"ERRO,out of range"); exit(1); } v11 = v17 << 8; } while ( v15 < (int)v40 );
|
经过动调上面的程序得,每八个一组,逆序,最后一组进行了一个字节的偏移。
静态分析也可得到类似的结果,如下
*v18 = v14; v18[1] = v13; v18[2] = v12; v18[3] = v11;
|
第三步
f3 = v18[2]; f2 = v18[1]; f1 = *v18; v21 = (__int64 *)operator new(0x20ui64); if (IsDebuggerPresent()) { print(std::cout, (__int64)"Hi , DO not debug me !"); Sleep(0x7D0u); exit(0); }
|
这里绕过IsDebuggerPresent很简单,改EAX为0或者修改je为jmp都可以,具体看绕过IsDebuggerPresent,但之后好像不需要动调了
第四步
下面进行了复杂的运算
可对v18分成的四部分分别赋值为f1,f2,f3,f4,对以下代码做整理
v22 = f2 & f1; *v21 = f2 & f1; v23 = f3 & ~f1; v21[1] = v23; v24 = ~f2; v25 = f3 & v24; v21[2] = f3 & v24; v26 = f1 & v24; v21[3] = v26; if ( v23 != 0x11204161012i64 ) { v21[1] = 0i64; v23 = 0i64; } v27 = v23 | v22 | v25 | v26; v28 = v18[1]; v29 = v18[2]; v30 = v25 & *v18 | v29 & (v22 | v28 & ~*v18 | ~(v28 | *v18)); v31 = 0; if ( v30 == 0x8020717153E3013i64 ) v31 = v27 == 0x3E3A4717373E7F1Fi64; if ( (v27 ^ v18[3]) == 0x3E3A4717050F791Fi64 ) v3 = v31; if ( (v23 | v22 | v28 & v29) == (~*v18 & v29 | 864693332579200012i64) && v3 )
|
条件大概如下
f3 & ~f1 == 0x11204161012 (f3 & ~f2) & f1 | f3 & (f2 & f1 | f2 & ~f1 | ~(f2 | f1))== 0x8020717153E3013 (f3 & ~f1)| (f2 & f1) | (f3 & ~f2) | (f1 & ~f2) == 0x3E3A4717373E7F1F (((f3 & ~f1)| (f2 & f1) | (f3 & ~f2) | (f1 & ~f2)) ^ f4) == 0x3E3A4717050F791F ((f3 & ~f1) | (f2 & f1) | f2 & f3) == (~f1 & f3 | 0xC00020130082C0C)
|
写出z3脚本即可计算出各自的值,然后逆向写出前几步加密
第五步
所有都分析完了,怎么还有。
问题就在于该方程的不具有唯一解,所以发现当时比赛的时候是给了第二部分的提示的也就是deajoa_S
需要替换成e!P0or_a
脚本
from z3 import *
d2 = [] f1, f2, f3, f4 = BitVecs('f1 f2 f3 f4', 64) s = Solver() s.add(f3 & (~f1) == 0x11204161012) s.add((f3 & (~f2)) & f1 | f3 & ((f2 & f1) | f2 & (~f1) | ~(f2 | f1)) == 577031497978884115) s.add((f3 & (~f1)) | (f2 & f1) | (f3 & (~f2)) | (f1 & (~f2)) == 4483974544037412639) s.add((((f3 & (~f1)) | (f2 & f1) | (f3 & (~f2)) | (f1 & (~f2))) ^ f4) == 4483974543195470111) s.add(((f3 & (~f1)) | (f2 & f1) | f2 & f3) == ((~f1) & f3 | 864693332579200012)) s.check() d = s.model() flag1 = hex(d[f1].as_long())[2:].rjust(16, "0") flag2 = hex(d[f2].as_long())[2:].rjust(16, "0") flag3 = hex(d[f3].as_long())[2:].rjust(16, "0") flag4 = hex(d[f4].as_long())[2:-2]
def decode(flag, d): le = len(flag) for i in range(0, le, 2): if flag[i:i+2] != "": d.append(int(flag[i:i+2], 16))
decode(flag1, d2) decode(flag2, d2) decode(flag3, d2) decode(flag4, d2) key = 'i_will_check_is_debug_or_not' fake_flag = "" for i in range(len(d2)): fake_flag += chr(ord(key[i]) ^ d2[i]) print(fake_flag)
right_flag = '' right_flag += fake_flag[:8] right_flag += 'e!P0or_a' right_flag += fake_flag[16:] print(right_flag)
|