hgame2021-hellore2题解
程序基本逻辑
通过使用OD运行和IDA查看大体的内容,可以发现,是要求输入两个符合要求的password
然后会生成答案hgame{password1_password2}
运行环境得有vc8因此不能用winxp的那个虚拟机
- 输入前半段flag,验证
- 验证正确后,利用此正确的前半段代码生成后半段flag
- 输入后半段flag,验证
password1
关键解读
puts("Hello Re Player");
puts("input password 1:");
lpEnvironment[3] = (LPVOID)100;
sub_401060(lpEnvironment[1], (unsigned int)xmmword_4043A0);
if ( strlen(xmmword_4043A0) != 16
|| (unsigned __int16)_mm_movemask_epi8(_mm_cmpeq_epi8(_mm_load_si128((const __m128i *)xmmword_4043A0), (__m128i)xmmword_4030F0)) != -1 )
{
lpEnvironment[3] = "password 1 error";
LABEL_48:
puts((const char *)lpEnvironment[3]);
return 0;
}
puts("password 1 correct !");
大致意思就是比较输入值和xmmword_4030F0的值是否相等。
其值有定义:39383162303261336136653563306232h
是一串字符串,用十六进制表示了,只要转成字符就行了
注册机
将其值转换为字符串为39383162303261336136653563306232h
,代码如下(注意大小端要反过来吗,每两位为最小单位)
str = '39383162303261336136653563306232'
before = ''
for i in range(31, -1, -1):
if i % 2 == 1:
# print(i)
print(chr(int(str[i-1:i+1],16)), end='')
# 2b0c5e6a3a20b189
也可以使用以下这种更好的写法(搞password2的时候学会的写法).
注意:这里是IDA读的数据是原始数据,是小端顺序即little,需要转换为正常显示的大端,OD中的数据是处理过顺序的为大端big
str = 0x39383162303261336136653563306232
before = str.to_bytes(length=16, byteorder='little').decode(encoding='utf-8')
print(before)
# 2b0c5e6a3a20b189
再次输入后显示正确,来破译第二段的password
password2
前置知识
逆向加密算法:
- 本题使用到的aes cbc模式python下加密解密可以参考这篇博客:https://www.cnblogs.com/niuu/p/10107212.html
OD多线程调试:
进程1解读
一共涉及三个线程
password1验证线程
用于保存共享的中间密钥的线程
password2的验证线程
在正确的password1输入后,一步步执行
创建进程并且把这个进程挂起了,进程pid为5408(每次运行都不一样需要自行动调)
createfilemapping经过查询是共享内存的函数
把password1进行xor操作得到,需要修改jnz或者跳过这一行到下面这一行,这里的跳转是对检测是否用了反编译软件的跳转
可以在运行时使用SuspendThread函数来挂起线程,然后使用ResumeThread函数来唤醒线程,值得注意的是,可以使用SupendThread多次挂起线程,线程有一个挂起计数器,初始值是0,挂起一次(含初始挂起),计数器就自增1,ResumeThread以此就自减1,当此计数器为0的时候,线程就被唤醒开始从挂起的地方开始执行。
然后这个线程就把之前挂起的验证password2的线程唤醒,当前进程就挂起了。(通过IDA的graph模式逻辑理解,之后被唤醒的线程进行password2的验证),因此这个处理过的字符串应该就是新线程加密程序的一部分要素2c2
10f;h8;n<66
(此处的ds:[0xC14390]是用于保存共享的中间密钥的线程的内存空间,之后password2线程会通过它的pid找到它的进程然后获取对应的中间密钥)
进程2解读
修改成对应的运行到00C1114E处修改eax的值为对应的中间进程pid
顺利跳转后,通过关键字AES,changingmodetoCBC得知使用了aes的cbc加密模式
根据该函数的使用手册
NTSTATUS BCryptEncrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
VOID *pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG *pcbResult,
ULONG dwFlags
);
可以得知道edi对应的内存地址的值就是传入值中的第五个也就是pbiv偏移值,运行到对应地方右键跳转得到iv的值(16位)为:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
加密后会进行比较,其中ds:[C130E0]就是正确加密后的结果,查询可得
B7 FE FE D9 07 76 79 65 3F 4E 5F 62 D5 02 F6 7E
注册机
参考了上文提到的那一篇博客
以及思雨师兄的注册机python版
# window: pip install pycryptodome
# liunx: pip install pycrypto
from Crypto.Cipher import AES
# aes加密解密要求的文本格式为bytes,格式转换
key = b'2c2`1`0f;h8;n<66'
iv = 0x000102030405060708090A0B0C0D0E0F.to_bytes(length=0x10, byteorder="big")
ciphertext = 0xB7FEFED9077679653F4E5F62D502F67E.to_bytes(length=0x10, byteorder="big")
# aes解密过程
cryptos = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
bytes_ans = cryptos.decrypt(ciphertext=ciphertext)
# aes处理过程中的文本格式都为bytes,解码
ans = bytes_ans.decode(encoding='utf-8')
print(ans)
# 7a4ad6c5671fb313
答案拼接
即:hgame{2b0c5e6a3a20b189_7a4ad6c5671fb313}