beginCTF
前言
这次不太好评价,感觉对我来说是降维打击,首先感谢队里的所有朋友们,是你们的陪伴和互助我才能坚持下去。
0x01红白机
看文件标题,猜测是6052汇编语言,作为上世纪的古老汇编,我自然是看不懂的,这里有个网址能运行6052汇编
如果感兴趣还有教学,该博客对6052汇编的介绍还是有理有据的。
flag{6502_I_LOVE_u}
0x02xor
出题人说是简单的异或,我点进去一看一坨,不想看了,既然说是异或那就用异或试试看。
找到这个极其可疑的字符串
点进去打个断点调试,要你输入flag时,就把这串字符串输入进去,结果肯定是错的,但是由于异或的可逆性,这时候查看enc的值,就是正确的flag,手抄下来得到答案(偷懒取巧的做法,不可取:( )
flag{Virus_gonna_be_terminated!}
0x03real checkin xor
借这又一道异或题记录一下静态做法,这题的函数相比之下就人性化许多了,一眼能看出加密过程
记录一下源码
1 | def verify_func(ciper,key): |
根据这个verify_func函数,复制过来改一下,就能得到解密脚本了捏
1 | secret = [7, 31, 56, 25, 23, 15, 91, 21, 49, 15, 33, 88, |
begin{3z_PY7hoN_r3V3rSE_For_TH3_Be9inNEr!}
0x04俄语学习
先找到主要的逻辑函数,观察后发现这个程序的主要逻辑是,先答对30道俄语题目然后输入flag并验证,为了便捷地动态调试这个程序,我先把前面三十道题目关于正误的判断全patch,变成答错能前进。
if 判断里有个主要的函数,这个函数接收input(不是原本输入的),然后和encry后的enc比较,这里的enc能直接调试得到
1 | enc = [ 0x38, 0x9A, 0xFA, 0xC3, 0x5B, 0x89, 0xA5, 0xE6, 0xD0, 0xAB, |
然后按X查看input的引用
发现这样一个函数,input是put和need_shuzu计算得到的(这些数组都被我改过名字),最后再encry。查找put 的引用不难发现是输入的用来验证的flag,encry函数的逻辑很简单,key可以直接调试得到
1 | key = [ 0x35, 0xF1, 0xDA, 0x19, 0x7A, 0xF6, 0x31, 0x9C, 0xD9, 0x2C, |
1 | def decry(key, enc): |
这下只需知道need_shuzu,就能写出逆向脚本解了。
查找一下need_shuzu 的引用
不难发现,need_shuzu定义于这个sub_C4B4B4( )函数,且从未被修改,那么这就好办了,直接调试进去找到need_shuzu的值
1 | need = [0x35, 0x6D, 0x35, 0x64, 0x35, 0x77, 0x35, 0x64, 0x35, 0x62, |
最终脚本
1 | key1 = [ 0x35, 0xF1, 0xDA, 0x19, 0x7A, 0xF6, 0x31, 0x9C, 0xD9, 0x2C, |
主要逻辑就是,由enc,反推回去input,然后input再反推回去put,很好理解,其中的need_shuzu,和key通过调试能得到。这个做法看起来挺冗杂的,应该有更好的办法
flag{Russian_is_so_easy}
0x05 stick game
一个网页小游戏,根据之前搭载博客时对网页的一点点理解,觉得主要的逻辑函数应该在js文件里面。
先看看游戏,说达到1337427分就能获得flag,当然正常是不能去玩的,如果js文件没有加变量名字混淆的话,存储着分数的变量应该叫score,打开文件ctrl+F找一下score变量
喜报,代码没有经过格式化,一坨。后面请教大佬朋友在vscode扩展下载了一个插件Prettier - Code formatter ,下载后选中格式化。
继续查找score,发现有个疑似修改分数的代码,这里的_0x3c8400作为一个变量名暂存分数,然后这个0x3a2009是一个判断是否双倍得分的参数,若为True就加2分,false就加1分,那一坨0x10a5….计算出来是2,下面-0x257…算出来是1,手动给他们后面加上1337426,这样一来,如果加一分,就相当于加了1337427分。不过这些都是建立在这个代码就是控制得分的代码的基础上的假设。
下面还有一个疑似修改分数的代码,不过这个算出来score = 0,应该不太可能是加分的代码,更像是重置分数的代码。修改完后打开游戏试试,果然成功一次就加了1337427分,这时候直接跳崖,让游戏在1337427分结束,网页弹窗显示出了flag。
完全不懂javascript,静态分析对我来说太难了,如果懂的话找下弹窗函数,和其他flag的加密函数或许能解出来,不过由于代码变量名混淆,我也看不出flag在js文件里到底长什么样。
begin{y0u_re4l1y_g07_1337427_66f1ebde263de1e039063aa402d22977}
0x06 ezpython
这题真的坑,题干一直在说key有问题,我也尝试过去找有没有函数修改了key,没想到竟然是gmssl库的sm4加密解密函数里,一个叫set_key的函数被修改了,key在填入前先做了和37的异或,确实搞到我了,flag也搞我,唉,采购。除了这个就是正常的py解包反编译,不过要用3.8python解包,不然会丢失文件(因为这题是3.8python写的)(py解包反编译梗概)。
反编译出他的ezpython文件,加以修改就能出flag了,早该如此,key 的错误一直卡死我了,唉。
ezpython.py
1 | #!/usr/bin/env python |
secret.py
1 | key = 'BeginCTFBeginCTF' |
flag{Pay_M0re_@ttention_to_th3_key!!}
0x07 出题人的密码
一开始做的时候被一坨看不懂的东西卡住了,后面才发现那个是没用的,引以为戒。
程序点进去无法f5,一步一步看,后面才发现是有花指令,把那些call的脏字节和无意义的jnz和jn全部都nop掉,然后反编译就能看到main函数了。
正常来说这里有很多sub函数,混淆我们找加密函数,我做的时候直接找了最后拿来比较的input和enc(input 是我自己命名的)中input 的引用,反解回去两个加密函数的
encry1
1 | int sub_C98090() |
encry2
1 | int sub_C97F10() |
其中encry1不多讲了,encry2是一个主要的加密函数,他的主要逻辑就是,将enc(48个字节),按小端序存储分为6个8字节大小的长整形,然后判断整形是否为负数(其实就是看最高位是否为1),若为负数,在左移一位的基础上再异或v2(v2是用魔法得到的),若不为负数,则就只是左移1位。
这个加密的难点在于根据加密后的数才判断加密前的数是正数还是负数,因为符号位在左移一位后已经丢失了。但是由此同时左移一位后的数的最低位变成了 0,恰v2的最低位也是0。因此,若加密后的数最低位是1,说明最低位经过0 ^ 0 =1 的异或,说明加密前的数是负数(因为经过异或),若加密后的数最低位是0,说明左移后没有和v2异或过,加密前是正数。
有了这个思路,逆向思路就很明了了。
1 |
|
由于涉及到数据的溢出和大小长度,建议还是用C写脚本
begin{Th1s_reverse_pr0blem_may_t@ke_some_time#!}
0x08 superguesser
其实这题我动调了半天都不知道意义何在,一来随便点点就看到了疑似加密的主函数
这里的静态代码真的看不懂,不过大概率是while循环那里,给明文依次加密,最后再比较,把一大坨密文抄一下,(v0-48)就是输入input开始的地址,长度为(v0-24)=37;然后(v0-20)=0(类似于循环里的i),(v0-25)=0x33(调试出来的)。下面那个loc_46EEF4猜测就是cmp函数。
1 | while ( *(v0 - 20) < *(v0 - 24) ) |
这里有个疑点,就是这个(v0-26)动调出来是1,但是实际计算根据这个1算不出来。
动调研究一下原本的汇编代码,看看哪里是有问题的。看完汇编代码发现计算时找不到这个17 *(v0-26)的操作,反而 +0x33 的操作变成了 +0x44,原因是(v0-26) = 1这一步经过位移操作后变成0x11与0x33相加,导致结果变成了0x44,感觉被反调试过了,动调出来的东西不太可信。
一位一位爆破试出来(v0-26) = 0,写出脚本就很简单了
1 | enc = [81,81,82,95,89,67,93,95,89,73, |
begin{debugging_is_an_anathor_choice}