hgame2021-hellore2题解

程序基本逻辑

通过使用OD运行和IDA查看大体的内容,可以发现,是要求输入两个符合要求的password

然后会生成答案hgame{password1_password2}

运行环境得有vc8因此不能用winxp的那个虚拟机

  1. 输入前半段flag,验证
  2. 验证正确后,利用此正确的前半段代码生成后半段flag
  3. 输入后半段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

前置知识

逆向加密算法:

image-20210220140027138

OD多线程调试:

进程1解读

一共涉及三个线程

password1验证线程

用于保存共享的中间密钥的线程

password2的验证线程

在正确的password1输入后,一步步执行

创建进程并且把这个进程挂起了,进程pid为5408(每次运行都不一样需要自行动调)

image-20210220204616670

createfilemapping经过查询是共享内存的函数

image-20210220164348671

把password1进行xor操作得到,需要修改jnz或者跳过这一行到下面这一行,这里的跳转是对检测是否用了反编译软件的跳转

可以在运行时使用SuspendThread函数来挂起线程,然后使用ResumeThread函数来唤醒线程,值得注意的是,可以使用SupendThread多次挂起线程,线程有一个挂起计数器,初始值是0,挂起一次(含初始挂起),计数器就自增1,ResumeThread以此就自减1,当此计数器为0的时候,线程就被唤醒开始从挂起的地方开始执行。

然后这个线程就把之前挂起的验证password2的线程唤醒,当前进程就挂起了。(通过IDA的graph模式逻辑理解,之后被唤醒的线程进行password2的验证),因此这个处理过的字符串应该就是新线程加密程序的一部分要素2c210f;h8;n<66(此处的ds:[0xC14390]是用于保存共享的中间密钥的线程的内存空间,之后password2线程会通过它的pid找到它的进程然后获取对应的中间密钥)

image-20210220171841073

进程2解读

修改成对应的运行到00C1114E处修改eax的值为对应的中间进程pid

image-20210220205453974

顺利跳转后,通过关键字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

image-20210220205954765

加密后会进行比较,其中ds:[C130E0]就是正确加密后的结果,查询可得

B7 FE FE D9 07 76 79 65 3F 4E 5F 62 D5 02 F6 7E

image-20210220210557324

注册机

参考了上文提到的那一篇博客

以及思雨师兄的注册机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}

Last modification:March 3, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)