Re几道入门题
逆向目前我还是刚开始专门学,现在只懂点c语言。有错误 | 更好的方法就请各位师傅多多指教了orz。第一篇文章有一些过程会写的很细,因为我第一次(用ai)打逆向是在去年10月的pctf,当时ai还只是deepseek,还是要自己懂点操作的,当时我还是跟着别人手把手教的写了几个题,这里也是想回报一下吧。目前ai很强了,不过为了线下我觉得还是要学学,下面就正式开始吧。题目在这里PCTF2025 - Review::CTF。
PCTF2025------flag
放ida里看看

这里直接shift+f12搜一下字符串就找到了

PCTF2025------xor
下好附件解压后放ida里

这里可以按f5反编译看伪c代码。

这里就是c语言代码,整体逻辑就是先进行flag的解密,然后比较我们输入的flag和他的flag是不是一样,先看看他怎么解密的。

对着变量按n可以重命名变量,整体逻辑就是把encrypted_flag里的每个字符对着密钥进行异或,并且把异或后结果放进s2里。因为异或是可逆的,这里有两种解法,第一种是动态调试。不过他是elf文件,可能要配一下远程linux调试,或者直接gdb调,远程可以看看这篇文章由一道逆向题而引发,IDA调试ELF文件 - DorinXL - 博客园,这里是re就不讲gdb了吧。总之配好了之后我们点一下strcmp这行伪代码按f2下断点,不过我的环境有点问题,要在汇编下断点不然断不下来。在汇编上下也是一样,点strcmp这行伪代码按tab切换汇编,也可以再按空格好看一点。


然后直接f9调试,这里要输入一个数据,直接在虚拟机上输入数据即可,右上角就是flag

还有第二种方法就是自己写一个python脚本去解密,这里不能只看注释的字符串,因为有不可打印的字符,要点两下encrypted_flag去看他的原始数据,这里他有一部分是16进制数据有一部分是字符串,我们直接对着字符串按d就可以也转化成16进制了,然后选中后按shift+e导出

这样直接复制就可以在python里写列表了,简单写个python脚本解决

脚本如下:
a=[0x3E,0x5C,0x33,0x38,0x28,0x36,0x2A,0x3F,0x35,0x38,0x3A,0x14,0x3D,0x36,0x2A,0x6F,0x37,0x31,0x30,0x37,0x3A,0x22,0x31,0x3D,0x30,0x25,0x1A,0x30,0x2B,0x6F,0x25,0x3A,0x32,0x2E,0x3E]
key=b'X0R_SECRET_KEY'
flag=""
for i in range(len(a)):
flag+=chr(a[i]^key[i%len(key)])
print(flag)
PCTF2025------debugme
依然是拿到附件先放ida看看

这里逻辑函数先输入数据,如果输入的数据长度是32就会解密出flag,最后让输入的数据与flag进行比较。这里的解密函数我们如果双击点进去看会发现

上面的ida的注释也告诉了我们,这就是一种混淆的办法:控制流扁平化。简单来说就是写程序的人不想让我们这么简单就逆向出他的程序,所以就加混淆/壳。当然有混淆就有去混淆。目前就先不去混淆了,这题名字就是提升我们动态调试,我们去那个比较flag的地方下断点f9动态调试就可以。这题是exe程序,可以在windows本地运行。

右上角就是flag
PCTF2025------upx
这里如果我们还是拿到附件就放ida看,就发现怎么奇奇怪怪的,这里就遇到了刚才所讲的壳了。这里我们就需要查壳工具DIE了,我们把附件放进DIE看看

可以看见这里告诉我们壳是upx壳,upx是有脱壳工具的。各位可以下载一下,然后运行这个命令

这样就把壳脱出来了,此时再在DIE看也是没看见壳了

然后我们放ida看看

这里程序就不带着逆了,因为跟之前的差不多,多点点几个函数即可。还是一个异或,这题也可以动态调试。直接解密脚本如下:
a=[0x3E,0x5C,0x33,0x38,0x28,0x36,0x2A,0x3F,0x35,0x38,0x3A,0x14,0x30,0x29,0x20,0x6F,0x37,0x31,0x30,0x37,0x3A,0x22,0x31,0x3D,0x30,0x25,0x1A,0x30,0x2B,0x6F,0x25,0x3A,0x32,0x2E,0x3E]
key=b'X0R_SECRET_KEY'
flag=""
for i in range(len(a)):
flag+=chr(a[i]^key[i%len(key)])
print(flag)
Base64编码的原理
base64其实还是很简单的,他的左右结束传输一些文件,因为文件内可能有不可打印字符,这个编码就可以把不可打印字符变成可打印字符。原理很简单,我们知道一个字符其实是以ascii码的值储存在计算机中,也就是8个bit。而base64就是只用6个bit,并且0-63用可打印的字符表进行编码。标准的表是ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/。编码的过程就是对我们要发送的字节按3个为一组(因为6和8的最小公倍数是24,3*8=24,6 *4=24)。3个字节就有了24个二进制位,前6个二进制位就是一个索引,然后去表上找。如果不满足3字节怎么办呢?就会自动补0,并且在0的时候不会进行索引,而是在后面加=。加=因为base64编码后必须为4的倍数(这是规定,其实是为了解码器更好还原)
我就以flag为例子人工编码一下f的ascii码是0x66(16进制更好转2进制),l的ascii码是0x6c,a的ascii码是0x61,g的ascii码是0x67他们的二进制就是
f l a g
01100110 01101100 01100001 01100111
按6给bit划分就是011001 100110 110001 100001 011001 110000(这里后面就没有二进制位了自动补0)
这6个bit的10进制就是25 38 49 33 25 48
根据索引找表我就用python的列表演示了。

a='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
c=a[25]+a[38]+a[49]+a[33]+a[25]+a[48]
print(c)
结果ZmxhZw补上=号就是ZmxhZw==解码后就是flag。所以流程大概就是这样,这里加解密的python脚本我也写好了,可以大概看看,总之能用就行,也不是来追求速度的。
while True:
choice=input("输入你想加密(1)或解密(2)输入0退出")
if int(choice)==1:
word=input("输入想加密的字符串")
table='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
resul=[]
for i in range(0,len(word),3):
buf=word[i:i+3]
c=0
for j in buf:
c=(c<<8)+ord(j)
if len(buf)==3:
index=[(c>>18)&0x3f,(c>>12)&0x3f,(c>>6)&0x3f,c&0x3f]
resul.extend(table[i] for i in index)
if len(buf) == 2:
c<<=8
index = [(c >> 18) & 0x3f, (c >> 12) & 0x3f, (c >> 6) & 0x3f]
resul.extend(table[i] for i in index)
resul.append('=')
if len(buf) == 1:
c<<=16
index = [(c >> 18) & 0x3f, (c >> 12) & 0x3f]
resul.extend(table[i] for i in index)
resul.append('=')
resul.append('=')
en=''.join(resul)
print(en)
if int(choice)==2:
word=input("输入想解密的字符串")
table='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
refle={c:i for i,c in enumerate(table)}
resul=bytearray()
for i in range(0,len(word),4):
buf=word[i:i+4]
c=0
long=0
for j in buf:
if j=='=':
break
c=(c<<6) | refle[j]
long+=1
if long==4:
resul.append((c>>16)&0xff)
resul.append((c>>8) & 0xff)
resul.append(c & 0xff)
if long==3:
resul.append((c>>10)&0xff)
resul.append((c>>2) & 0xff)
if long==2:
resul.append((c>>4)&0xff)
print(resul.decode())
if int(choice)==0:
break
知道了原理,那写题就随便写了,无非就是换了个语言实现了这个原理/改了点东西。我们看看题
Pctf2025------base64
附件先放DIE,没看见壳。然后放ida看看前面就不看了,直接看加密函数

整体逻辑就是输入字符串,加密后跟已编码的字符串是不是一样。这个实现可以看见跟上面原理是一样的,用int类型存储3个byte的二进制位,用>>和&取6个二进制位,查表用索引,写回字符串用指针。就是标准的base64。因为base64编码可逆,我们把编码的字符串解码就可以了,也不一定要用我那个脚本,用cyberchef或者随便找个网站解码一样的。

PCTF2025------base64_pro
这题就不是标准的base64了,die没看见壳,放ida看看

main函数前面逻辑很简单就不看了,整体逻辑就是输入字符串,进行加密跟已加密的字符串比较。这里可以看见流程还是base64,但是加密的表换了。换表也一样解,我们用cyberchef或者用我那个脚本把表换了一样解。如果直接换表解会发现居然错了。不管是cyberchef还是我的脚本都解不出来,我的脚本可以错cyberchef总不可能错吧。所以这题肯定有什么奇奇怪怪的地方,但是我们跟着main函数看也没看出来什么东西阿?这里在左边列表我们可以看见一个函数,他没有出现在main函数里,但他是不是就不会被调用呢?我们看看

可以看见他对这个表进行了各种操作,我们可以ctrl+x看看哪里调用了他

可以看见他在初始化的数组上,所以这其实是个预主函数。也就是在main函数之前就已经调用了他了,也就是说我们现在看见的base64的表跟程序实际运行main函数时的表其实是不一样的。这里如果我们想对这个函数进行分析也可以,但这有点太复杂了,这里虽然有个随机数,但没有以时间为种子(其实也不可能,如果表会变,那密文应该也要跟着动态加载,不然flag就解不出来了)所以我们可以动态调试,直接把表拿到再解密。这个文件是elf文件,需要配置一下,在他取表的地方下个断点

然后f9动调一下

就可以看见表了,我们用cyberchef/我写的脚本把表换了就能解出来了

change_base64
这题在这里下网络空间安全执法技术实战 - SDPC::CTF,主逻辑还是挺简单就不看了,我们看加密过程

可以看见,首先这个表换了,然后从表取值的索引,他这里^了0x15,如果只是换表,那用网站还是能写的,不用写脚本。这里改了一下就肯定要写脚本了,当然理解了逻辑就很简单,因为异或可逆,我们取索引的时候再异或一下就可以了,换了表我们解密也把表换了就可以了。

所以逆向还是挺开心的,当然现在其实还没怎么逆,还是在写写套路题。后面慢慢来吧,Re:0