这个比赛还算友好,而且少见的逆向比web还要多,出题人说之后会放源码和官方writeup,是个不错的学习的机会,这里把做出来的几道题先写一下(然后开始写作业……
web
Welcome to Earth
我对web实际上是毫无兴趣的,但这题实在简单的过分,还是给做了,详细的就不说了,查看源码就可以发现,只要在调用dead
之前进入到应该进去的页面就可以了(直接F12
里debug暂停然后慢慢看就可以了……
pwn
pwn也很久没做了,知识点还停留在刚学的时候,应付一下第一题就完事了
Department of Flying Vehicles
IDA打开(逆向看久了之后发现pwn题目的逻辑真的是简单得要命),漏洞也看得出来,利用的话还是不熟练,需要多练
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
如果想要通过第一个if就必须要输入COOLDAV
,这样的话就过不了第二个输入,看到gets
直接考虑溢出覆盖变量的值,最简单的方法就是输入和其中的一个变量全都为\0
1 | from pwn import * |
就可以拿到flag(但我忘了记录flag得值又懒得再跑一遍拿flag)
Re
Chugga Chugga
IDA打开
1 | void __fastcall __noreturn main_main(__int64 a1, __int64 a2) |
所有的判断条件都在这个函数里,直接根据条件解出来flag就可以了,至于为什么不写具体的过程,因为我是在演草纸上自己动手解的方程,只要耐心分析就可以了
这里有一个疑问,标记一下,解方程的时候可以解出来两解,应该有地方可以排除掉,但我直接根据语义选择的flag
1 | pctf{s4d_chugg4_n01zez} |
Dank Engine
脑洞题(这游戏根本就玩不过去……
走到地图中间怎么都跳不上去,到了最右边发现不能走了但是地图没完,后面接着长长的一条路,所以一直往右边拉,看到pctf
,找到了flag的位置
(然后为了找flag跑崩了四次虚拟机……显卡和cpu看样8太行)
用鼠标上下没法超出屏幕,用alt+f7
移动窗口慢慢找,感觉应该有逆向方法,那个pck包我至今还没解开
1 | PCTF{ITWASTIMEFORTHOMASTOGO_HEHADSEENEVERYTHING} |
来补充一下,IDA可以直接打开pck包,里面有关于人物的设定
1 | '# Global Variables',0Ah |
惊奇的发现下面还有一个上帝模式和开启方法
1 | db 9,'if self.g_cheat_stack == ["P", "U", "R", "G", "0", "0"]:',0Ah |
方法就是按键直接输入PURG00
,打开之后就可以飞和穿墙,直接跑到flag在的地方去看就可以了
(亏我还调窗口大小调了这么久)
Digital Sloth
这题的逻辑很简单
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
直接会输出flag的那种,但是一运行只输出了三个字符,明显是计算大数乘幂的时候算法时间复杂度太高了(O(n^2)),想要算出flag必须手动优化一下算法,利用平方把时间复杂度优化到**O(logn)**,在大数的时候明显优化的不是一点
1 | #include <iostream> |
又是一道分割线,看了其它大佬的wp才知道……直接用python的pow
不就好了……
直接输出flag
1 | pctf{one man's trash is another man's V#x0GFu_Lp%3} |
看到最后一段甚至感觉做错了,到现在没看懂
train_arms
arm一语双关,妙啊
这题就直接看汇编了
1 | .cpu cortex-m0 |
虽然没怎么接触过arm的汇编,但是这里还是很容易的,把flag分奇偶位进行操作,奇数位不动,偶数位异或42,最终结果输出到文件,所以打开文件
1 | 7049744c7b5e721e31447375641a6e5e5f42345c337561586d597d |
明显16进制输出,写个脚本跑一下
1 | target = [0x70, 0x49, 0x74, 0x4c, 0x7b, 0x5e, 0x72, 0x1e, 0x31, 0x44, 0x73, 0x75, 0x64, 0x1a, 0x6e, 0x5e, 0x5f, 0x42, |
直接输出flag
1 | pctf{tr41ns_d0nt_h4v3_arms} |
Little Engine
我觉得这题很不错,难度比较适中,还可以加深对于数据在内存中占用位数的理解
1 | __int64 __usercall main@<rax>(__int64 a1@<rdi>, char **a2@<rsi>, char **a3@<rdx>, __int64 a4@<rbx>, _QWORD *a5@<r12>) |
话好多,第一句说明正确,第二句说明错误,逻辑就很清楚了,if的条件是一个用来判断的函数
程序在sub_1830()
里进行输入,在sub_1510()
进行了一些处理然后判断
1 | __int64 *__fastcall sub_1830(__int64 *a1) |
程序看起来异常复杂,但是经过我的仔细分(tiao)析(shi),发现只是把输入拷贝到了内存里分配好的空间。
1 | __int64 __usercall sub_1510@<rax>(signed __int64 a1@<rdx>, unsigned __int64 a2@<rcx>, __int64 a3@<rbx>, void *a4@<rbp>, __int64 *a5@<rdi>, unsigned __int64 a6@<rsi>, _QWORD *a7@<r12>) |
这又是一个异常复杂的函数,但实际上有用的内容并不多
1 | a6 = 0LL; |
只有这里是对输入的处理,整个处理过程也就只有一个异或而已,这里比较有意思的是循环停止的判断条件,a5
实际上是个数组,里面存放了两个地址,一个是我们输入的字符串开始的地址,另一个是结束的地址,实际上相减出来的值就是字符串的长度,但是看起来就比较复杂,逆向的时候理解起来就有些困难。
这里的处理其实很好办,a1
这个值和我们的输入没关系,是循环中依据算法生成的,我们可以通过同样的算法生成,然后存放在一个数组里。
接下来是判断函数
1 | __int64 __fastcall sub_15A0(__int64 *a1) |
各种操作看着吓人,仔细一看,只有一个直接比较
1 | if ( *((_BYTE *)v1 + 4 * v2) != *(_BYTE *)(v3 + v2) ) |
前面的生成方式很复杂,但是可以不用去管,通过动态调试就可以调试出来目标数组,不过目标生成出来都是64位数据,比较的时候只取最低8位进行比较,还需要进行一些处理。
然后直接把处理过后的目标数组和之前依据相同算法生成出来的数组逐项异或就可以得到flag
1 | #include <iostream> |
输出的flag为
1 | pctf{th3_m0d3rn_st34m_3ng1n3_w45_1nv3nt3d_1n_1698_buT_th3_b3st_0n3_in_1940} |
Comments