January 8, 2023

MoeCTF & MSSCTF 出题验题记录

本来打算等mss办了之后两个一起发的,但是mss的题都快烂手里了,还是算了先把moe扔这。。。(((

MoeCTF 2022

MoeCTF算是我第一次做题的比赛,也是第一次出题的比赛了。自从总被出题人折磨之后就一直想着我什么时候也能出题折磨别人
不过这次下手还是挺轻的吧,没有把我那些奇奇怪怪的想法加进去,按照track神给的知识点按部就班出的。

checkin和begin就不说了,都是点击即送。

D flat

其实很早就想出一个C#的题,类似于写一个小游戏那种,就算做不出来题还可以玩玩游戏
不过track神当时写异架构只写了RISC-V和安卓,所以我是最后出完题了看逆向题好像有点少就整了一个C#。
出这题最犹豫的不是写什么加密算法,而是题目叫什么名字(
苦思冥想的时候突然想起我断了一年多还没修的琴弦我学的稀烂的乐理,C#也可以解读为升C(?,跟降D同音,于是就有了这个题目名233

嗯,然后就是去简单学了一下C#语法,出了一道白给题。
DLL拖进DnSpy,把那一串数字拿出来转字符就是flag。

不过其实出这题最麻烦的大概是在linux配置.NET的环境吧,主要是答应了track神每一题都放一个ELF的附件的。

EquationPy

pyc逆向解方程,算是每年moe逆向必备节目了吧(逃

fake key

track神说要出一个必须用调试器才能做的题目,那么当然最初想到的就是随机数,不过感觉单一个随机数好像太简单了(?
后来加了一个init函数,在运行过程中会改掉最初的key,调试起来的话就会发现key被追加了一段字符,在题目描述里也强调了key会被偷偷改掉(连题目都是fake key

动态调试去取真正的key和随机数写解密即可。

寄汤来咯

逆向必备节目之花指令。

最初是搜了一下花指令,随便去当了一段汇编代码过来贴在函数里,但是死活报错。去搜了一下,都是说内联汇编要用att啥的,但是att实在是太反人类了啊啊啊啊啊啊啊啊啊,没耐心看了,主要是看别人的代码插花指令都插的x86,所以我坚信这样是可以过编译的(逃

后来忘了在哪个犄角旮瘩看到的,要用VS的32位平台才行,试了一下,一遍过,芜湖

源代码里写了两个加密函数,后来验题的时候发现VS给我两个函数全扬了,代码都在主函数里,非常的抽象,后来出完fake code把编译优化全关了又试了一下,总算是正常了一些,没那么抽象了。
要出简单真是不容易啊

Art

track神说要出一道异或取模爆破的题目,某个傻子恍惚间想起了VNCTF,似乎回到了当初被时空飞行支配的恐惧。
当时辣鸡的我就是因为不会写深搜爆破,遂放弃了这道题。时隔多年我不再是被支配的傻子,而成了要把这玩意出到题里的出题人。
说实话还是有点心理压力的,毕竟要不是带善人PZ师傅在VNCTF的WP里贴了深搜爆破的代码,我可能也还是不会写深搜爆破感觉算法对于萌新还是太难了吧呜呃呃
而且异或取模有多解,顺便用一个哈希验证,就把哈希算法识别也考了(虽然没什么用,可以直接越过哈希来爆破所有字符= =

验题的时候往IDA里一拖,核心代码就两行,看起来那么的人畜无害感觉太朴素了,想起来还没出过壳的题目,遂整了个UPX壳。
不过考虑到写算法的难度(指难受程度)以及哈希算法的考点,正在学魔改壳的我还是没有对这个壳做什么奇怪的事情(不然我就把UPX头和入口的pushad扬了

问题就在于,当我整ELF附件的时候,upx压缩给我报错了!
去搜了一下,说是upx不能压缩低于40kb的文件,因为太小了没法再压缩了。我看着我没压缩前的exe文件,19kb,和压缩后的exe文件陷入沉思。在自我怀疑和怀疑这说法之前来回徘徊之际,突然发现人家说的是Linux,也就是说只有ELF有这要求。好的,那没事了(逃

所以怎么办呢,当然是静态链接来增加可执行文件的大小啦。
但是抽象的事情来了。
首先是静态编译去符号表,main都被扣没了,IDA反编译出来start函数里面一堆抽象的sub。不过这个问题都不大,因为通过字符串还是能很快定位主函数。

但是更抽象的事情来了。

exe拖进IDA反编译的核心代码是这样的:

1
2
for ( i = 1; i <= 27; ++i )
Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;

跟源码不能说是一模一样,只能说是毫无区别。如此简介,如此朴素,如此稚嫩(啊不是

但是elf反编译的核心代码:

1
2
3
4
5
6
7
8
9
10
11
for ( j = 1; j <= 27; ++j )
{
v11 = (unsigned __int8)v17[j - 1];
LOWORD(v10) = (char)v11;
v12 = 121 * v10;
LOWORD(v12) = (unsigned __int16)(121 * (char)v11) >> 8;
v13 = v12;
LOBYTE(v13) = (char)v12 >> 3;
v10 = v11 - 17 * (v13 - (unsigned __int8)(v17[j - 1] >> 7));
v17[j - 1] ^= (unsigned __int8)(v10 + v17[j]) ^ 0x19;
}

额……
只能说,作为一个出题人,我差点以为我是不是放错了代码……

异或和加都还好,但是对17取余变成这么抽象的一堆?…… 不知道的会不会以为我在欺负萌新啊喂
编译优化关了也没用,放弃 :) let it go~

fake code

track神给的考点里有汇编。其实最初想的是编写一段代码,然后用IDA反汇编,把汇编代码当出来存到一个txt里当作附件发出来。
以前也做过这样的题目,但是自从国赛之后就对这种印度人题深恶痛绝了,想了很久还是觉得SEH比较适合,因为只有except块中的内容不会被反编译,只需要看except里的几句汇编代码就可以了,还能顺便让萌新适应一下伪代码和汇编一起看。
这题本来是交给DX的,不过他写平台太忙了(因为DX实在是太强了),所以我就接过来这题了。
九点多开始出题,本来以为十点能出完,结果搞到两点多……

VS里写好SEH,然后也测试好了,debug版本跑起来一切正常,但是一release就全g了。我真的是百思不得其解啊,去搜为什么debug版和release版行为不一致,一堆人说是变量名写错了或者没有初始化之类的问题。

☁️:我8可能写错。

当时真是想自暴自弃直接用debug版算了,但是当我把debug版的程序拖进IDA,看到那一堆熟悉的抽象代码,我突然明白了为什么以前做过的有些题的代码能那么抽象,因为那些出题人绝对绝对用的是debug版:)

但是我不能这么做,我不能欺负萌新,我要往简单了出呜呜呜呜呜呜呜呜

没辙,只能怀疑是SEH的问题。在谷歌某个角落找到了,说是编译器会把SEH优化掉,必须把编译优化关了才行。
关掉编译优化,一遍过

Broken hash

啊我实在是太菜了太菜了太菜了

题目的大概思路就是取输入的每一个字符算哈希(用的是SHA1,改了一些常数),取出前64bit跟密文比较,然后输出wrong或者right。
解题思路就是把输出patch成check过的长度,然后写python脚本交互爆破。

感觉这样太简单了所以整了点活:如果check失败就触发一个除零异常,然后在catch块里解码(按位取非,就是异或0xff)真正输出的字符串wrong并且输出。所以说patch后面的wrong是没用的,因为后面那个wrong根本没用到,要patch catch块里的输出才行。
wrong字符串的密文是附在哈希密文后面的,整体是一个数组。也就是说我密文开了长度为九十几的一个数组,前88位存flag的哈希,后面是字符串wrong的密文。

嗯,然后这样调试过去会触发除零异常,又感觉这样有点明显。所以本来想写一个只有调试会触发的catch块,把触发除零异常的函数hook掉,但是因为一些原因中间一部分写的有些问题,改了好多次也还是不行,wtcl wtcl,遂放弃了,555555555555。
最后写了个TLS,设置了一个标志位,如果在调试就更改标志位,然后根据标志位来hook触发异常的函数。(但是直接写在主函数里,太明显了啊喂

这样子如果patch了后面的输出,调试过程中没有绕过反调的话,输出的就是ptach过的结果,但是正常运行还是会输出wrong,所以就这样造成调试结果和实际运行结果不一致。

MssCTF

mss还没办,你在期待什么(x