2019 SUCTF
SUCTF由SU战队举办,是XCTF分站赛的最后一站了
这次De1ta拿了冠军,可喜可贺(撒花
比赛时间:2019-08-17 09:00:00——2019-08-18 21:00:00
题目下载地址
RE
signup
gmp库计算RSA
直接在factordb分解N
1 | from Crypto.Util.number import inverse |
hardcpp
用ollvm混淆过的c++代码。
开头给了一个类似哈希的东西,先不管。
ollvm中应该开了运算混淆,流程平坦化和一些虚假分支,调试一下发现主要流程就在那一堆lamda那里。
输入一共21位长度,从下标为1开始加密,和之前一位进行一些四则运算,然后和enc比较,enc一共20位。
这些四则运算都是可逆的,所以知道一位就能求出下一位,只需要爆破下标为0的字符,即可求出flag
1 | enc = [ 0xF3, 0x2E, 0x18, 0x36, 0xE1, 0x4C, 0x22, 0xD1, 0xF9, 0x8C, |
查一下md5发现是井号,也就是第一个字符。
Akira Homework
程序有多处反调试,以及多处check,通过这些check会还原一个dll,后面多线程会进入这个dll最终到一段aes的逻辑得到flag。
程序中的字符串都被某一个字符异或加密了,所以搜不到字符串,但是可以对key数组查找引用找到所有加密的字符串。
做法以调试为主,先把地址随机化关掉,然后在所有Isdebuggerpresent和exit处下断点查看,基本遇到的反调试直接nop/jmp掉
开始的tls回调函数会解密四个字符串NtQueryInformationProcess,ZwQueryInformationThread,NtQueueApcThread和ntdll.dll。查找这些字符串的引用可以在后面看到用GetProcAdreee获取这些函数地址。但是使用ida调试的时候,tls回调函数被多次调用了,可能是后面多线程的关系,导致这些字符串被重复加密,到最后就没被还原去使用了,所以在这里打断点,第一次停下的时候执行解密,之后每次停下都直接set ip到最后ret
main函数逻辑较为简单:开头起了多线程,先看主线程的逻辑:
先输入一串passwd,经过一串简单的加密,解密出来是
1 | Akira_aut0_ch3ss_! |
之后第二个check会获取当前目录,在后面加一个:signature后打开,比如/WinRev.exe:signature,从中获取内容并md5校验。md5解出来是Overwatch
问了下队里师傅,冒号说是文件流,可以通过一下指令写入:
1 | type 1.txt >> WinRev.exe:signature |
1.txt中放要写入的内容。把"Overwatch"字符串写入后就能通过check。
注意到两个check通过后都会调用sub_140006C10
函数,里面调用了某个函数,下断跟进后发现是这两个函数
sub_140008910
和sub_1400089E0
他们对全局变量unk_1400111A0
进行了解密,然后SetEvent一个Handles变量,这个变量一共又三个。通过查找他的交叉引用以及sub_140006C10
函数的引用,发现在开头起的多线程里面sub_140008B20
又被调用了。简
单分析下这个函数,发现这里md5了什么东西并和一些md5值校验,相等则直接exit。这里可以猜到md5的可能是进程名,如果有ida.exe等进程则退出,通过下断点调试也能发现,在退出时可以看到md5的内容是ida64.exe。通过进程名校验后会调用sub_140006C10
解密。判定了一个全局变量,所以只会解密一次。
全部通过这三个校验并完成,会看到解密完的结果有pe头。dump出来是个dll
之后main函数就没啥用了,sleep挂起。为了方便调试,可以修改sleep的时间,调大一些。
接下来主要是另一个线程中干的事了。分析beginthreadex的起始函数,注意到里面有个sub_140009850
中信息很多。发现了DllInput以及校验了MZ字符。开头他在等待三个Handles设定完毕。但进入这个函数的条件byte_140016198
一直没找到在哪设置。分析sub_140008D20
函数,他会调用参数一函数指针,查找这个全局变量的引用,看到它是在sub_140009C20
中被设置,同样的还有qword_140016178
和qword_140016180
,他们最终被sub_140008850
设置成一开始tls回调函数解密的NtQueryInformationProcess,ZwQueryInformationThread和NtQueueApcThread,当他们都被成功设置后,就能成功进入sub_140009850
的逻辑了。
如果wait到一个258的信号,会提示time out并推出,所以之前sleep要改长一点。
单步调试发现到sub_140007D80
里面会获取输入,逐步f8跟进最终来到sub_180002880
是最后的逻辑:
1 | __int64 sub_180002880() |
其中sub_180002800
很容易看出是aes,密文是之前另一个线程里面看起来很像密文的东西,key就在这里。由于这里获取输入后直接跟解密后的明文比较,所以不需要自己解密,在strcmp下断点就能看到flag了:
1 | flag{Ak1rAWin!} |
babyunic
使用unicorn引擎,翻一下unicorn源码可以得知几个函数及参数的意思
https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/unicorn.h
https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/mips.h
可以得知架构是mips,小端序
输入的flag与结果分别被写到两个地址,分别作为指针通过a0和a1传入,然后设置了fp和sp的值。代码写到另一个地址,然后开始执行。最后从结果处读数据与常量对比。
ida自带有mips小端序处理器模块,使用retdec插件可以反编译,但是效果不是很好。
不过代码逻辑特别简单,很容易能看懂。
先是循环左移三位,然后异或下标,最后互相加减计算出42个结果。
因此只需解42元方程组。
编写脚本提取出方程组:
1 | bg = 0x00000378 |
然后用文本操作提取出矩阵,解出flag
1 | from numpy import * |
Rev
不太懂c++,瞎调。
首先在00000001400016A3
有两个check,一开始不知道干啥的,随便试
后面看到sub_140002B80
里面有isspace和ispunct,猜测跟符号有关。瞎调调出来是用符号分割成几部分,第一个校验3部分,第二个校验第一部分的长度10
之后在0000000140001763
附近把第一组异或了0xAB,然后和常量对比。然而常量只有5字节,实在想不出还有啥东西了,就直接跳过了
下一部分校验长度4,然后校验大写字母,然后是A-G,后一字节依次比前一字节大2,得出ACEG
最后一部分先是atoi,然后校验偶数,和两个方程。直接z3求出。
1 | from z3 import Solver |
得到flag:
1 | suctf{ACEG31415926} |
Pwn
babystack
为方便本地测试,先可选头中的地址随机化选项关掉。
开始让你输入一个数,这里有栈溢出,但是没用,因为最后是直接exit掉的。
注意到0040853C
有一处花指令,实际上这里就是获取下一行的地址,然后把输入减去这个地址,然后输入除以它。
这时想到,如果除以零会怎样,于是输入0040853C,发现通过异常处理进入了新的函数sub_407F60
分析这里的功能,它提供了10次任意地址读取。输入选项yes和no的时候有栈溢出,同时如果输入的不是yes或no,调用fgets又能栈溢出。
开头写死了两个1。结束时会把他们相加然后与三校验,正确会输出flag。我们输入的字符串是在这两个数据高位的,所以不能溢出覆盖到他们。
同时由于最后也是exit掉的,所以也不能覆盖返回地址。
测试了下任意地址读取,如果输入非法地址会异常,进入一个异常处理函数。
观察一下函数开头的代码:
1 | .text:00407F60 push ebp |
security_cookie是全局变量上一个值,每一个进程自始至终是固定的。它异或到了ebp-8的数据上,调试一下发现这里指向SEH结构体,之后又异或了ebp放到ebp-1Ch作为canary。
想到可以在栈上伪造一个seh结构体,然后把ebp-8覆盖成我们伪造的结构体,结构体中的异常处理函数改成程序中的后门地址。由于这个地址异或了cookie,所以我们还要读取cookie的值。
经过多次调试发现还有一些栈上的值不能变,通过计算偏移覆盖或者直接用任意地址读取后覆盖。需要注意的是ebp-4应为0。
1 | from pwn import * |
Crypto
RSA
lsb Oracal attack
1 | from pwn import * |