180827 逆向-网鼎杯(3-1)
这两周比赛接着比赛打到头爆。。。。
网鼎杯的题目质量感觉都还行,也练习到了不少东西
SimpleSMC
这里的0x400aa6函数点过去一看就可以发现它是乱的字节无法执行,根据题目SMC(Self-Modifying Code)可以猜到这个函数就是被修改的目标了
查看它的交叉引用可以发现有两个函数有调用 sub_400c48
关键代码如下
for ( i = 0; *((_BYTE *)sub_400AA6 + i) != 0xC3u; ++i )
{
v1 = *((unsigned __int8 *)sub_400AA6 + i);
*((_BYTE *)sub_400AA6 + i) = v1 ^ *((_BYTE *)sub_41E1B0 + i);
}
是一个函数字节码异或的固定调用,直接写个IDC脚本解就行
查看这个函数的交叉引用是在init_array中,所以是在调用之前调用的
sub_400bac
signed __int64 __fastcall sub_400BAC(char *key)
{
int i; // [rsp+Ch] [rbp-Ch]
for ( i = 0; *((_BYTE *)sub_400AA6 + i) != 0xC3u; ++i )
*((_BYTE *)sub_400AA6 + i) ^= key[i % 7];
return 1LL;
}
根据key进行逐字节异或,这里的key从调用方可以看出是Input的一部分,所以要猜key
根据密码学常识,单表替换即key仅有一个字节的时候通常是根据词频统计来解决的,在机器码中一般00
较多
而本题是类似于维吉尼亚的多表替换,循环长度为7,如果当函数字节足够长的话也是可以用上述方式解决的–将整个密文分成7段,每段内独立进行词频统计。尝试了一下,本题由于密文不够大,因此统计规律应用的不是很顺畅,仅能解出**的最后一个字节e
既然词频统计不好使的话,就得从一些固定字节来思考了
这个题目里key不是input的开头,因此没有固定的内容
但对于一个被加密的函数而言,如果非手写汇编的话,通常会以push rbp;mov rbp, rsp
开头,通过这几个字节进行异或可以得到一个全可见字符的串
于是根据这个key也可以写出一个解密脚本:
#a = [0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec]
addr = 0x400aa6
i = 0
while(Byte(addr+i)!=0xc3):
PatchByte(addr+i, Byte(addr+i)^Byte(0x41e1b0+i))
i += 1
#for i in range(7):
# print(chr(Byte(addr+i)^a[i])),
key = "aaa@qq.com"
i = 0
while(Byte(addr+i)!=0xc3):
PatchByte(addr+i, Byte(addr+i)^ord(key[i%7]))
i += 1
要注意的一点是,这两个异或顺序是不可变的–因为它们判断结束的标志是加密前的0xC3,而先异或0x41e1b0时是不会产生0xc3的中间字节的,但是如果先异或key,会产生0xC3的中间字节,导致第二次异或时被截断
当然如果将解密循环改为固定长度就可以规避这个BUG
解密得到
_BOOL8 __fastcall sub_400AA6(__int64 a1)
{
_BOOL8 result; // rax
__int64 v2; // rdx
__int64 v3; // rcx
unsigned __int64 v4; // rt1
__int64 v5; // [rsp+10h] [rbp-30h]
__int64 v6; // [rsp+18h] [rbp-28h]
__int64 v7; // [rsp+20h] [rbp-20h]
__int64 v8; // [rsp+28h] [rbp-18h]
char v9; // [rsp+30h] [rbp-10h]
unsigned __int64 v10; // [rsp+38h] [rbp-8h]
v10 = __readfsqword(0x28u);
sub_4009AE(a1, 32LL, 64LL);
v9 = 0;
v5 = 0x3851081D0B070A66LL;
v6 = 0x281A0A3038145C1FLL;
v7 = 0x1F012224240C5939LL;
v8 = 0xA1505083A1D731ELL;
result = (unsigned int)sub_400360((__int64)&v5, a1) == 0;
v4 = __readfsqword(0x28u);
v3 = v4 ^ v10;
if ( v4 != v10 )
sub_443260(&v5, a1, v2, v3);
return result;
}
其中a1是input,sub_4009AE很明显对它进行处理,然后sub_400360进行校验
那么我们首先看一下check方法
emmmm有点乱,我觉得不太对劲,退回去看一眼
这是plt段的跳转,也就是说这是一个静态链接的库函数
再根据它的另一个函数来想,很明显就是strcmp啦
那么只需要专心看sub_4009AE是怎么变换的了
点进去看注意这里有花指令,按D将它们调成数据后,将脏字节E8改成90(NOP)即可
然后即可F5了
这里是一个63x5的迭代
内层计算同样有花指令,同样清除以后即可反编译
__int64 __fastcall calc_in(char *a1, int a2)
{
int i; // [rsp+1Ch] [rbp-4h]
if ( !a2 )
return 0LL;
for ( i = 0; i < a2; ++i )
a1[a2 + i] ^= a1[i];
return calc_in(a1, a2 >> 1);
}
外层计算由于不影响参数,因此只是重复计算
另一方面,由于内层计算全部都是异或,因此偶数次计算会使得数组还原。我觉得是出题人的失误23333不过影响也不大就是了
内层计算的逆运算比较简单,逆序异或即可
r = bytes.fromhex("660a070b1d0851381f5c1438300a1a2839590c242422011f1e731d3a0805150a")
r = list(r)
# print(r)
# print(len(r))
def foo(s, n):
print(s)
if(n==32):
return
for i in range(n):
s[n+i] ^= s[i]
foo(s, n<<1)
foo(r,1)
print("".join([chr(i) for i in r]))
准备飞了,我们明天再见。。。
上一篇: 车厘子怎么保存才新鲜?有什么好方法?
下一篇: 如何建设一个高端大气的网站