hgame 2025 Week1 WriteUp
前言
比赛时间:2025.2.3 - 2025.2.17
签到
TEST NC
1  | cat flag  | 
从这里开始的序章。
1  | I am the flag!  | 
也是我的终章:blush:
Crypto
suprimeRSA
旧附件的题目:
1  | from Crypto.Util.number import *  | 
新附件的题目:
1  | from Crypto.Util.number import *  | 
先是一个小问题 a!+b!=a^b^ ,注意到右式增长速度远大于左式,a、b取值一定较小,试根得到a=2,b=2
根据源码:质数p=k*M+pow(e,a,M),k和a随机生成,几乎无法爆破。
但是发现模数为96位大整数,可以通过二次筛法分解质因数,在msieve计算1h28min后得出结果:
1  | Sat Feb 08 15:00:13 2025 p48 factor: 796688410951167236263039235508020180383351898113  | 
1  | from Crypto.Util.number import *  | 
之后我发现https://factordb.com这个网站把这个数的分解给秒了???
估计是算出来后给记下来了,题目附件也是及时换了,一看发现n有144位,原来考点不在分解因数上?
sieve
题目:
1  | # sage  | 
根据题意,需要算出715849728以内欧拉数之和,其中质数和1的欧拉数再加1。这里当然用筛法做:
欧拉数
先令 ,每找到一个质数p,就将所有kp的欧拉数乘以 这样对于任意一个数n,它的每个质数都能被筛一遍。
那怎么找质数呢?遍历 2~n,如果发现 ,那就说明n没有被更小的素数筛过,那就是质数了。
由于本题要求质数和1的欧拉数再加1,所以代码中筛的时候从2*p开始筛,最后还要加1
这会就是C语言的魅力时刻了,python跑了2个小时左右,C语言半分钟都不用就出来了。
1  | 
  | 
1  | from Crypto.Util.number import *  | 
ezBag | Undo
题目:
1  | from Crypto.Util.number import *  | 
Reverse
Compress dot new
脚本语言是nushell,功能就是哈夫曼编码,解密脚本在此省略huffman_tree
1  | def huffman_decode(encoded_data, huffman_tree):  | 
解出来后还有个彩蛋,知乎上的解释:https://www.zhihu.com/question/20133127?sort=created
Turtle
先手动UPX脱壳
找到源码,下面是我改过:
1  | __int64 sub_401876()  | 
显然,关键函数set_v1、encrypt1、encrypt2,只要逆向后两个函数即可,注意到encrypt1是异或加密,直接可以当作解密函数用,而encrypt2跟对明文的变化只在于一行的 -= ,只要改为 += 就能用了。
1  | int main()  | 
Delta Erro0000ors
IDA打开是这样的,但运行时却有里面没有的东西,看看汇编graph,果然是异常处理。


这种情况直接把异常处理前面的代码给nop掉,这样异常处理的部分就会反编译到源程序的后面。

这是异常处理:

IDA是这样的,只要lea后面接了mov,反编译后就会隐藏这些传参,所以还是要看看汇编理解。
分析前还是得简单了解msdelta是干什么的。当要进行版本升级时,直接发送新版本的文件就太大了,msdelta提供了接口,生成两版本delta和应用delta,这样只需要发送delta文件就可以更新版本了。
ApplyDeltaB是就是应用Delta的方法,后面会校验MD5哈希值是否正确,不正确会触发异常。
程序先读取flag用包裹的部分作为source,用程序中的delta进行差异补丁,由于hash不对触发异常,用户再次输入md5值进行补救,如果成功,就生成key进行xor加密。
这里会发现一个问题,我们要找的就是source,但用来解密source的key却是用source生成的,这就像是把开锁的钥匙给锁起来了。所以就有两个猜想,一个是直接分析Delta,找找有没有破解之道;另一个是推测Delta完全覆盖了原来的source,用伪flag动态调试获得key。
一开始我是打算直接分析Delta的,无奈网络上的资源实在太少了,去逆向msdelta.dll难度大,只能放弃。而第二个更符合re手的直觉,先获取MD5的值再解密。
看其他师傅的WP学到了一点小技巧,在原来的hash处下内存断点,一定会在比较hash的时候被断,找到这个地方就能看到目标MD5。

在这里,此时rcx就指向我们要的MD5值!

取出来MD5:44D292FFE2E91730AE69EB50AE11D04A

在这里断,取出来数据


不过还是取hex的数据吧,最后的\x00字节也是key的一部分。
1  | Input: 3B 02 17 08 0B 5B 4A 52 4D 11 11 4B 5C 43 0A 13 54 12 46 44 53 59 41 11 0C 18 17 37 30 48 15 07 5A 46 15 54 1B 10 43 40 5F 45 5A  | 

Web
Pacman
翻源代码到/static/script/index.js,可以找到两组gift
1  | here is your gift:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ==  | 
base64解码:
1  | haepaiemkspretgm{rtc_ae_efc}  | 
栅栏密码解密:
1  | hgame{pratice_makes_perfect}  | 
