最近比较忙,而且题目越做越快,平时的练习除非特别值得注意的,都不会写的太详细了
serial-150
这题本身的算法没什么难度,主要就是花指令的去除,去除之后就只是简单的字符串比较了,很容易。
1 | EZ9dmq4c8g9G7bAV |
testre
主要就是一个base58,很好做,尝试了一下ghidra的效果发现不尽如人意,所以主要还是采用IDA做题。
1 | flag{base58_is_boring} |
simple-check-100
这题没什么好写的,虽然前面的check函数进行了检测,但是后面的计算和前面的输入没什么关系,并且直接把flag给输出了,所以直接gdb调试改了eax
的值直接输出flag就好了
1 | flag_is_you_know_cracking!!! |
secret-string-400
一直不知道这是什么,后来发现这竟然是个压缩包,里面有个网页和调用的js,js里面可以看到把机器码转成了命令然后执行,所以在执行之前输出一下
1 | console.log(command); |
在console里找到了关键判断
1 | var f=window.machine.registers[1].userinput |
过程非常简单,也是闲着无聊,写了个什么都没有的网页
1 | <!DOCTYPE html> |
1 | function run() { |
点击按钮获得flag
1 | flag is: WOW_so_EASY |
windows_reverse2
首先是脱壳,看雪脱壳工具就可以,也可以手动脱壳,IDA研究之后发现中间一个函数的作用不是很明朗,OD调试一下根据结果猜测可能是16进制转base64,试了一下就对了。
1 | ADEBDEAEC7BE |
Newbie_calculations
这题的就是直接会输出flag,但是进行了大量费事而且毫无意义的计算,需要仔细分析一下每一部分的函数作用,简化计算的过程,总共只有三个运算函数,很简单,就不一一分析了,直接计算flag:
1 | #include<iostream> |
直接输出flag
1 | Your flag is: |
easyre-153
先查壳,发现是upx,脱壳
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
发现用到了简单的子进程和pipe。
pipe的作用就和名字一样,建立一个管道,这个管道一端是读,一端是写,按照规定,pipe[0]
是读,pipe[1]
是写,然后fork了一个子进程,在子进程中,返回值为0,进入if分支通过管道写入了一个字符串,然后退出子进程。在父进程中,返回的是fork出来的子进程的id,跳过if分支,在下面读取了刚刚子进程写入的字符串,关键就是lol这个函数
1 | int __cdecl lol(_BYTE *a1) |
这里的处理过程非常简单,还是正向的处理,但是最后输出的是一个没用的字符串,所以即使是输入子进程的pid,也不会输出真正的flag,所以还是自己动手算出来
1 | a1 = "69800876143568214356928753" |
输出结果
1 | rhelheg |
直接提交不对,要套上外面的格式,试了一下比赛的名称,通过。
asong
这题还是很考验对于算法的逆向能力的
首先IDA打开
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
程序流程是这样,先输入flag,然后验证格式,取中间部分,然后读取另一个文件中的内容进行一些处理,然后将输入的flag和读取出来的内容进行一个比较,输出到out里面,重点来看一下几个函数
1 | int __fastcall readfile(const char *a1, __int64 a2) |
这里明显看到进行了一个词频统计,但是看这个伪代码完全看不出来处理之后的v4和分配出来的a2有什么关联,这点在C伪代码里面不是很明显,但是在汇编里面可以看的很清楚
1 | call sub_400936 |
这里其实很清楚,sub_400936
就是我们用来处理的函数,它的返回值放到了rax
,cdqe
的拓展在这里可以不用考虑,差不多算是个类型转换,ds:0
是之前分配的空间,把ds:0[rax*4]
的地址复制进rdx
,后面的一句并没有什么意义,将这个地址中的值赋给rax
,就是0,add
之后rax的值就是我们需要的地址,接下来把该地址内存的的值取出来,加一然后再放回去,这个操作逻辑很容易理解。
这个函数过后构建了一个词频表,然后进行关键的变换。
1 | unsigned __int64 __fastcall sub_400E54(const char *flag, __int64 that_girl) |
首先就是一个换表,然后就是两个函数的操作之后输出到了out
文件里面,这个是我们已知的,再去看看两个关键函数
1 | __int64 __fastcall sub_400D33(unsigned __int8 *a1) |
这是一个简单的次序上的调整,可以很快的逆出来
1 | _BYTE *__fastcall sub_400DB4(_BYTE *a1, int a2) |
这个函数里有些很有意思的操作,这个或操作看着很熟悉,这是显然是一个换位的操作,但是这里用了前一个数和后一个数之间的交错换位,逆起来就有了一些难度。
我也看过一些其他大佬的wp,这里普遍都是采用了爆破的方法,我不是很喜欢,虽然爆破可能更加省时省力,我还是想更加加深一下逆向位运算的熟练程度。
经过观察我们可以很轻松的发现,只需要将所有的数拆成两部分,再按照反方向运算回去就可以了,非常简单。
这样的话所有的内容就分析完了,这里我用python来做的逆向(后悔没有用C++)
1 | def sub_400936(a1): |
最后一部分最开始是打算爆破,但是想到了一种更简便的方法,就是先把所有的ASCII码值遍历一遍,制作一个表,可以避免双重循环爆破,虽然实际上爆破也并不麻烦。
最后输出flag
1 | QCTF{that_girl_saying_no_for_your_vindicate} |
signin
找一道比较简单的题目保持一下做题的感觉
用Ghidra打开,部分函数的签名有问题,有时候将RDX误当作函数的参数,但是函数只需要两个参数,这里我打开IDA看了一下,IDA做的要比Ghidra好很多,根据汇编修改了一下函数签名,看一下主要的函数部分。
1 | undefined8 FUN_00100a21(void) |
首先是需要我们输入字符串,然后进行了一些大数的操作,这里是利用了一个库,查了一下mannual,很好理解,输入之后有一个函数对我们输入的字符串进行了一些操作,进入这个函数
1 | void FUN_0010096a(char *param_1,long param_2) |
看到分别取低四位和高四位的操作之后立刻反应过来是把字符串转换为16进制串,耐心看一下很好理解。
回到主函数,看到乘幂取模操作,发现是RSA,用yafu分解一下103461035900816914121390101299049044413950405173712170434161686539878160984549
分解的也很快,这个数还是不够大
1 | prp39 = 282164587459512124844245113950593348271 |
然后可以直接在线解密也是可以解出来的,由于gmpy2安装不上,直接在线解密了
1 | suctf{Pwn_@_hundred_years} |
key
过程很复杂,动态调试绕过文件检测之后调试出flag
1 | idg_cni~bjbfi|gsxb |
notsequence
题目还是很不错,逻辑很清楚,仔细观察可以看出来杨辉三角就解决了,一共20层,全部组合起来取md5就可以了
1 | import hashlib |
输出flag
1 | RCTF{37894beff1c632010dd6d524aa9604db} |
zorropub
没想到会用pwntools去爆破一道逆向题……
感觉有些复杂,无脑爆破得到结果
catch-me
这题用了sse
算法,而且整个过程非常复杂,所以猜测了一下其中的两个数相同,然后得到了flag
BabyXor
首先脱壳,然后观察IDA,发现函数很多进行了很多次的异或运算,因此选择直接动态调试,调试到关键函数的最后发现几个运算是算出了flag的三个部分,拼接得到flag
Comments