如果你是逆向入门的选手,那恭喜你现在我们是一样的,我也没有任何这方面的天赋能力值,和我组队一起寻找这片大陆的最深领域吧。
第一章 欢迎来到异世界,Hacker
2024年3月18日
我穿越到的地方是一个叫做NSSCTF的城邦,这里收藏着各色各样的武功秘籍,奇人异士搭建了不同的工坊来训练入门弟子。这个世界里充斥着信息网,有些人会根据你的信息来锁定你的个人身份(例如家庭住址、电话号码、上学学校…),所以一个id是必须的,再开始之前想想你的名字吧。
作为穿越之子的我,必然有着不和他人一样的悟性,我切记实力是依存下去的根源。只可惜到这个世界的我也已成年,倘若我是婴儿时期。。。。。此处省略无数不实际的幻想。没事!作为主角,应该还有加持。
我摸索了很多,注册了一下身份信息
这里大部分都是免费试炼靶场,所以作为菜鸟又是穷鬼的我当然是白嫖普通靶场。
里面的靶场竟然这么多,我直接被震撼住了,如若我掌握了这些秘笈我不就能称霸整个大陆吗。功法的类型有多个方向,但我听说边上选职业的选手,说什么"得逆向者得天下”。我一听,这太适合我了,我要选就得选有难度的。
来吧,咱虽然信心足,但第一道还是来选个这个叫<<简简单单的解密>>,看看是什么实力。这里顺带提一手,idapro是免费武器(专门解逆向的)到<csdn>魔法商店问下领取方法就可以了。看到下载附件,看看里面是什么内容,什么情况,这不就是我前世没怎么学过的python语言吗,老天这简直戏耍我,我要转职业(我要当web手)。。。硬着头皮看,好枯燥的代码,这时候我想到了一个妙招,拿出前世我老师的秘诀:“好记性不如烂笔头”.我把每句话都进行注释一下就容易懂了,哈哈哈我太聪明了。
这里我给大家贴这了
题目
import base64,urllib.parse
key = "HereIsFlagggg" #key是密钥
flag = "xxxxxxxxxxxxxxxxxxx" #暂时看不到,但我们要求的就是flag。
s_box = list(range(256)) #创建一个名为 s_box 的列表,其中包含从 0 到 255 的数字。
j = 0
for i in range(256): #从0到255
j = (j + s_box[i] + ord(key[i % len(key)])) % 256 #s_box[i]=i,后面规律按位取key的ascill码。
s_box[i], s_box[j] = s_box[j], s_box[i] #交换
res = []
i = j = 0
for s in flag: #取flag的每一位
i = (i + 1) % 256
j = (j + s_box[i]) % 256 #根据i来取s_box[i]来决定j
s_box[i], s_box[j] = s_box[j], s_box[i] #交换
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]
res.append(chr(ord(s) ^ k)) #存入转为对应异或过k的flag单位字符ASCII字符的
cipher = "".join(res) #连起来
crypt = (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) #为了避免报错进行utf-8加解密
enc = str(base64.b64decode(crypt),'utf-8')
enc = urllib.parse.quote(enc)
print(enc)
# enc = %C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA
总体看下来,像是一种加密,有一个按序增长的数组,经过第一层密钥加密,第二层flag加密,已经知道解密后的字符。
我试着先把enc进行url解密成第二步后的密文.
enc = urllib.parse.unquote(enc)
再反过来求flag,但这里他加密的时候用了加密的s_box数组,所以我们要先求出原来的s_box。
剖析一下,最终i和j交换了位置,那我是不是把i和j交换回来就可以了?应该没错吧,但这j应该等于什么呢,
原来的i是按顺序的,j开始也是从0开始,我把j=0带入进去试试
j=0时,key[0]=H,对应ascii码值是72,那么j===>72
j=1时,key[1]=e,对应为101,那么j===>(72+1+101)=174
j=2时,key[2
怎么没找到规律,我不是主角吗,不是应该秒懂吗,什么情况。我这时候发现,试炼靶场后是有前辈留下的心得的,我马上就是学会了前人种树,后人乘凉。让我想到了高中老师说的,这就是那句至理名言:“抄着抄着就会了”.
在心得里我发现,原来密钥是加密s_box数组的作用,flag就用这个加密后的来进行加密,那么我们解决加密前的准备就是求出加密后的s_box数组,那这里加密的时候这个算法已经在这了,我们要的就是它
j=0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
同样的代码再进行一遍就是需要的数组。再开始解密,这里看res收取的是异或后的数据,那这里我从心得里知道异或的技能只要你再发动一次异或就可以变为原来的,至于s这里没有变化过,k的值也是根据i和j来决定,i和j一直都是有规律的,那我们重新算一次,但这次加密的enc,加密就等于机密,重复一遍就好
res = []
i = j = 0
for s in flag: #取flag的每一位
i = (i + 1) % 256
j = (j + s_box[i]) % 256 #根据i来取s_box[i]来决定j
s_box[i], s_box[j] = s_box[j], s_box[i] #交换
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]
res.append(chr(ord(s) ^ k))
这里的内循环就是
所以这里通过招式就是把enc放最前面加密一次
import base64,urllib.parse
key = "HereIsFlagggg"
enc = "%C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA"
enc = urllib.parse.unquote(enc)
print(enc)
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
res = []
i = j = 0
for s in enc:
i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]
res.append(chr(ord(s) ^ k))
flag="".join(res)
print(flag)
# enc = %C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA
什么东西,就这么简单,以为有多难,我拿着flag去提交点,我以为会爆出什么惊天大奖励
只出现了一个提示显示完成。。。原来在这世界的人都叫做CTFer
完成了我的新世界首杀,我信心倍增,喊出小说里的口号:“这一辈子我要拿回属于我的一切”!非常中二。我马上找到第二个训练靶场.
第二章 这只是新生赛,孩子
进来一看,这对手实在太矮小了,我不由的笑出声,但我想着学下大佬,给进行注释一下
题目
flag = 'xxxxxxxxxxxxxxxxxx'
list = [47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25]
result = ''
for i in range(len(list)):
key = (list[i]>>4)+((list[i] & 0xf)<<4)
result += str(hex(ord(flag[i])^key))[2:].zfill(2)
print(result)
# result=bcfba4d0038d48bd4b00f82796d393dfec
(list[i]>>4): 这将list[i]的位向右移动4位,有效地分离了高4位。
((list[i] & 0xf)<<4): 这对list[i]和0xf(二进制中的00001111,有效地分离了低4位)进行按位与操作,然后将结果向左移动4位,有效地将低4位移到了高位。
result就是先进行异或再hex编码去掉前缀0x.
我们就是反过来,key的顺序没变所以还是一样,但result需要先进行int转为10进制,然后再异或key就可以了。
list = [47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25]
result='bcfba4d0038d48bd4b00f82796d393dfec'
flag=''
for i in range(len(list)):
key = (list[i]>>4)+((list[i] & 0xf)<<4)
flag += chr(int(("0x"+result[2*i:2*i+2]),16) ^key)
print(flag)
So easy,照这情况看起来我马上要几何倍增长经验了,我继续寻找第三个靶场,这次不同前两个,这次给了我一个程序让我破解,领的ida武器终于能用了,让我看看这武器杀伤力怎么样,看看使用方法,把文件直接拖入ida。
[SWPUCTF 2021 新生赛]PYRE
铺天盖地的函数出现在我面前,woc这就是逆向吗,这怎么做的了,我也没有系统啊。
该怎么办,只能看心得了,”查找main函数“,原来是这样。马上依葫芦画瓢。
crtl+f
召唤搜索雷达,呃这这不是c语言吗.what can I say ? **** out
给挂科的男主角整这一手,可能换了世界我的c语言实力暴涨了100倍,我侥幸继续看看。发现点击函数是可以进去的,我就勉强研究下。
这时候我进去看了函数,我愈发觉得不太对劲,我好像意识到什么,看了看了逆向入门手册上的建言
养成习惯,不管它是啥,都先查壳
同样是新手大礼包里面可以领取的,这是一个查文件信息工具(EXeinfo PE),发现这是python写的程序
ida只能反编译c语言的,呃。。。至少用了下ida还算称手。
反编译的第一步是将exe文件转换成pyc文件,这里使用的是pyinstxtractor,项目地址:
https://github.com/countercept/python-exe-unpacker/blob/master/pyinstxtractor.py
详细教程
https://blog.csdn.net/m0_37552052/article/details/88093427
经过我的一系列操作,我折腾了两个多小时还是没有解决这个问题,甚至没用反编译成功。。我觉得先行放弃,以后再战。我变的有些疲惫,但我还是继续寻找新的靶场,但不能找到那些太复杂的,因为可能就会反噬,晋级的时候不能过于着急了,想到了大师傅说的一句名言,希望大家引以重视
为了避免这两种情况我打算先撤一步,感受下海阔天空。
就选这个了,[LitCTF 2023]enbase64,感觉可以,吃一堑长一智,先查一手是c语言
出来吧,idapro(32位的程序用die.exe看一下)!!进来时汇编语言,作为新手马上F5进行反编译,如果你要秀一把自己汇编水平也是可以的。
厉害的你肯定能看出来source是base64的码表,还有下面的字符串要求长度是刚好33位,有一个base64check我们进去看看什么门道。
是一个比较字符串,很明显后面这个就是密文了,Str1就是我们输入的数据经过处理的。那现在知道结果,反求输入,就要看看加密魔法是什么了。
看起来很复杂的样子,但没事同学们,记住它是base64加密就可以了,因为…没有为什么,你记住就行了。
这里知道密文,知道base64码表,就可以求原文了,但这里码表经过了变换
qmemcpy(&Source[1], &aAbcdefghijklmn[-(Source - &Source[1])], 4 * (((Source - &Source[1] + 65) & 0xFFFFFFFC) >> 2));
qmemcpy 是一个内存复制函数,类似于 memcpy,用于将内存块从一个位置复制到另一个位置。
&Source[1] 表示 Source 数组的第二个元素的地址。
&aAbcdefghijklmn[-(Source - &Source[1])] 是一个地址,这个地址的计算可能有些复杂,需要进一步解释:
(Source - &Source[1]) 计算了 Source 数组的地址和第二个元素的地址之间的偏移量。
aAbcdefghijklmn[-(Source - &Source[1])] 根据上述偏移量,从数组 aAbcdefghijklmn 中取得一个位置的地址。
4 * (((Source - &Source[1] + 65) & 0xFFFFFFFC) >> 2) 是复制的字节数。这个表达式的计算也比较复杂:
(Source - &Source[1] + 65) 计算了从第二个元素到 Source 数组之间的偏移量,并且加上 65。
& 0xFFFFFFFC 将结果向下舍入到最接近的能被 4 整除的数。
>> 2 将结果右移两位,相当于除以 4。
最终乘以 4,得到最终的字节数。
总的来说,这行代码的作用是将从 aAbcdefghijklmn 数组中某个位置开始的一段内存(长度由复杂的表达式计算得出)复制到 Source 数组的第二个元素之后的位置。
怎么样,懂了吗,我知道你不懂,不要不懂装懂,这里有一神奇的魔法武器可以解决这一情况。
动态调试
这里调试教程自己搜索一下吧
先找到base64函数这里,发现有一个换表,我们打一个断点先,再在result打一个
进去之后发现这里返回的result就是source粘贴过去,source又可以由v3得出,所以我们只要看destination变量的值就知道码表了
双击后看到栈上的值,拿到码表gJ1BRjQie/FIWhEslq7GxbnL26M4+HXUtcpmVTKaydOP38of5v90ZSwrkYzCAuND
拥有了这把钥匙我们就可以打开这扇门了
这里用一个替换技能,不会是肯定的,但总能从大佬那偷学点,自己当裁缝就可以拼接出来了。
from base64 import b64decode
def decode(s):
old_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
new_table = 'gJ1BRjQie/FIWhEslq7GxbnL26M4+HXUtcpmVTKaydOP38of5v90ZSwrkYzCAuND'
t=''
for i in s:
t+=old_table[new_table.index(i)]
return b64decode(t)
print(decode("GQTZlSqQXZ/ghxxwhju3hbuZ4wufWjujWrhYe7Rce7ju"))
经过这场训练,我终于学会了base64换表和基本动调这两项技能,但我的眼睛变得有点涩,腰有点疲劳,这就是变强的征兆,但不能一直练下去,要学会休息一下。
第三章 地锅鸡的力量
更新ing…