攻防世界————asong
今天做了几道攻防世界的逆向题,有一道题比较麻烦,虽然不难,但耗费了不少时间,其中有很多时间是可以节省的,所以这次就把这道题记录下来。
本来打算直接静调出结果,但中间卡在了一个点上,不得不动调,虽然最后发现,动调并没有给多大的帮助0- 0
放到ida,我已经分好了类,首先输入,然后判断一下开头结尾,之后建立一个字典,最后对输入进行算数处理,逻辑很清晰,没有任何阻碍
建立字典里面有个函数说一下,算一个小坑吧,没在这里绊很长时间。
首先,他是从that_girl文件里面根据单词出现的频率,每个单词对应相应的出现次数,然后建立了表格,当时我在这里犹豫了一会儿,在这个我标记的case函数里面,他将单词asc改成了相应下标值,我以为需要考虑,可到后来才发现,其实完全不需要考虑他们的位置,只需要记住对应的词频率就ok这里卡了一会的
建立词频率的函数图
他在建立函数时,大小写全都放到一个词频数组里面去的,也就是说不区分大小写
然后运算处理也很简单,首先将我们的各个输入的字符,转化成相对应的频率,再位置变换,再移位交换,最后得到结果
python代码如下
flag="QCTF{" #建立头 #将给的out文件里面的值取出来 fd=open("out","rb") #这里注意rb取得是数 buf=fd.read(1) arr=[];arr1=[] while(buf): arr.append(ord(buf)) buf=fd.read(1) fd.close() #最后的移位操作 for i in range(len(arr)): if(i): arr1.append(((arr[i]>>3)+((arr[(i-1)]<<5)&0xe0))) else: arr1.append(((arr[i]>>3)+((arr[len(arr)-1]<<5)&0xe0))) #print(arr1) #置换操作 shun=[22,0,6,2,30,24,9,1,21,7,18,10,8,12,17,23,13,4,3,14,19,11,20,16,15,5,25,36,27,28,29,37,31,32,33,26,34,35] #这个库之前找错了。。。。。。。。卡了很久,我太蠢了_(:з」∠)_ #置换算法1————暴力循环算法 for j in range(37): #因为是一个循环置换,所以让他循环一整个次数,就能回到最初点 i=0 p=arr1[0] while(shun[i]): arr1[i] = arr1[shun[i]] i=shun[i] arr1[i]=p #print(arr1) #置换算法2————逆向算法 j=1 p=arr1[j] for i in range(37): #这个就是倒回去一个一个找 arr1[j]=arr1[shun.index(j)] j=shun.index(j) arr1[j]=p #查找词频 (这里我没用python的dict字典方法,我感觉有点麻烦,还要用到zip函数,这里我建立了两个匹配数组,一样可以实现,个人认为dict也是建立了两个数组emmmmmmmm) dic="abcdefghijklmnopqrstuvwxyz_\'\n " #第一个字符串数组 arr0=[0]*len(dic) fw=open("that_girl","r") buf=fw.read(1) while(buf): arr0[dic.index(buf.lower())]+=1 #第二个词频数组 buf = fw.read(1) fw.close() #出结果 for i in range(len(arr1)): flag+=dic[arr0.index(arr1[i])] #根据词频去查之前的字典就得到相应的flag flag+="}" print(flag)
最后祭奠一下卡了半天出错的地方的地方,这里他因为第二个数是0,所以出现了align8(意思是8个0)这个不合理的玩意 _ (:з」∠) _ _ (:з」∠) _ ,没有看到,所以没找到数组里的0,还以为逻辑有错,_ (:з」∠) _卡时候的心态爆炸,发现之后更心态爆炸
align8图
d键之后的图
可见有时候看到数据连在一起,先d一d是一个好习惯,_ (:з」∠) _(一种植物)
攻防世界————testre(西湖论剑预选赛)
首先这道题是base58,俺看出来了它是一个进制转换的函数,但是原理搞了半天也没搞懂,还是算法基础太差,唉,不过虽然思考的时间很久,但最后自己有了一个很明确很好的理解思路,我感觉这也算是有收获,也算是一种成功吧,嘿嘿O(∩_∩)O~
这个题就是一个base58的加密解密,base58和base64不同的地方是,base58是通过大进制转换,将其他进制转化成58进制,而base64,32,16都是通过2进制位数8位变6位实现的,所以这段代码就是一个进制转换代码,其中有很多混淆迷惑的部分,我们这里就只说在ida分析出来的这个进制算法。
接下来就来分析一下他的进制换算的算法思路吧~~~
ida中的核心算法图(横线是作用代码,未标横线便是混淆代码)
v21是我们输入的值;v11是输出数组
循环方式:
外循环:输入字符循环
内循环:每一个字符做进制运算,先%再\一直到为0为止,然后分别赋到v11的各个数组里面去
开始我按照算法进行推理,但发现如果直接计算,是看不出什么规律的甚至越看越乱,一直到我在纸做了一下推理,才发现原理很简单。
首先,我们回顾一下,十六进制0xabcd转十进制,首先要得到十进制末尾,很简单0xabcd%10,我做了一下实践,求余符合分配率,所以我可以分解成 0xa000 % 10 + 0xb00 % 10 + 0xc0 % 10 + 0xd % 10,然后想得到倒数第二位,那么就是0xabcd \ 10 == X1,再让 X1 % 10,以此类推,怎么样是不是跟我们的上面的式子很是类似?接下来,回到我们的58进制
用十六进制转58进制,要注意的一个问题是,这里我们的十六进制是按照两个字节为一个整体(因为我们输入的字符asc是word型的,所以就按照word进行计算),其实可以说是256进制,我们就姑且称之为双十六进制,然后进行计算。假设我们输入值为0xabcd,按照算法逻辑,它先是进来0xab,V11[22] = 0xab % 58 ,然后的整除放到一边,我们先跳出第一层for循环,看下一个for循环,进来v21=0xcd 这时再一次计算 V11[22] = ((V11[22] << 8) + 0xcd) % 58,然后得出58进制的末尾
这里不要直接算,把它拆分成如下
(((0xab % 58) << 8) + 0xcd ) % 58
=((0xab00 % 58) + 0xcd ) % 58
=0xab00 % 58 +0xcd % 58 #(这里已经和我们上面十六进制转十进制完全一致了,下面更清晰一点)
=0xabcd % 58
ps:公式用到的原理
a % b << 8 == a << 8 % b ===== a % b == a00 % b (俺感觉是交换律)
a % b % b = a % b
到这里就很清楚了,然后就是整除,再给第二位,之后根据下标对应字符,balabala的,就不多解释了,最后放一下我自己写的base58的py
def str_hex(a): return "".join([hex(ord(a[i])).replace("0x","") for i in range(len(a))]) #字符串转16进制 def encode_58(a): #加密 c="" while(a!=0): c=dic[a%58]+c #取末尾 a=a//58 #整除进位 return c def decode_58(a): c=0 for i in range(len(a)): c+=pow(58,i)*dic.index(a[len(a)-i-1]) #解密这里我换了个方法,跟之前不一样了,因为从头直接翻译字符串有时候会出错。他的第一位可能是没用的,所以我们从最后一位开始就是最好的选择 out="";div=c while(div>0): div, mod = divmod(div, 256);out=chr(mod)+out return out def hex_str(a): return ''.join([chr(int(a[i:i+2],16)) for i in range(len(a)-2,-1,-2)]) #16进制转字符串 print() input_en="flag{63510cf7-2b80-45e1-a186-21234897e5cd}" input_de="BiJZHKtNhHyDe8kSF878CyFgq7h44x1MqUEXiUFJW3gRfQX1MjePwuKHB8" dic="ABCDEFGHJKLMNPQRSTUVWXYZ123456789abcdefghijkmnopqrstuvwxyz" #表 output=encode_58(int(str_hex(input_en),16)) print("encode ========> ",output) output=decode_58(input_de) print("decode ========> ",output)
效果图
从不同的思维方向去看待问题,问题便会得到解决,这次学习有很大的帮助,继续努力O(∩_∩)O~
攻防世界————echo-server(XCTF 3rd-NJCTF-2017)
花指令的简单去除,学习一下,深入学习将会在null比赛的逆向的wp中写出,这道题简单的介绍一下一些小的花指令,首先ida分析看到f5的一些不正常表现可以分析出来有花,去花
花简单来说就是junk_data,做一些无意义的汇编操作,并且让ida反汇编失败,这里因为花很简单,所以我们直接手动patch就行了,看到一些奇怪的汇编指令,比如突然jmp,毫无意义的地址,clc等等,都可以对其进行重组,以下是patch点。
这里会是一个call loc,nop e8,后面的数c成代码下面的图同理,那个F1@g字符串是key,千万别nop
最后效果图
然后逻辑就很简单了简单的异或,这里就不说了,只是说一下花,就不浪费时间做异或练习了
攻防世界————first(XCTF 3rd-NJCTF-2017)
开始有个线程把我吓一跳,后来发现对题目难度没什么影响,扔到ida里面分析,首先输入,然后将我们输入的值进行了一个归总异或运算,得到一个值v11
后面循环建立线程
其中有一个start_routine,关键函数,进去以后发现会对我们输入的值进行一个函数改造,然后判断,这里我坑了一会,这个函数是md5看出来了,但是出来的判定结果都是16位,我以为是按照[8:-8]的格式得到的16位md5,没想到直接取得开头16位,emmmmm
然后就是爆破脚本
import time import hashlib ch="flag";p=["beac2821ece8fc5c", "ad749265ca7503ef", "4386b38fc12c4227", "b03ecc45a7ec2da7", "be3c5ffe121734e8"] time_start=time.clock() flag="juhu" for ans in p: m1=0x30;m2=0x30;m3=0x30;m4=0x30 while(m1<=0x74): m=hashlib.md5() ch=chr(m1)+chr(m2)+chr(m3)+chr(m4) m.update(ch) if(ans in m.hexdigest()): flag+=ch break if(m4<=0x74): m4+=1 else: m4=0x30 if(m3<=0x74): m3+=1 else: m3=0x30 if(m2<=0x74): m2+=1 else: m2=0x30 m1+=1 print flag time_end=time.clock() print "use time====>" + str(time_end-time_start)
得到juhuhfenlapsdunuhjifiuer,这里的脚本顺序是不太对的,因为他分了线程,线程的先后顺序是不一样的,题目解题的要求是最后res_md5的顺序按照,0,4,8,14,10,c(十六进制),排出来的,所以要调整一下顺序,flag就出来了,或者可以直接再写个脚本,把之后的那个鉴定给爆破出来,也可以直接动调一步一步调,很简单就出来了goodjobyougetthisflag233
我开始是直接动调的,但这里附上脚本把还是,算是练习一下
后来在写脚本的时候遇到了点问题,后来才明白这个题目一个根本意思,开始根据我们输入计算得出来的v11的输入顺序是按照他给的md5表的顺序来的,然后后面赋值的顺序又会发生变化,与之前的计算v11的输入顺序是不一样的,所以说是有两个顺序,如果这里他弄得恶心点,让第二次赋值输入顺序和md5顺序完全不一样,那可能就需要点时间了,有点恶心,但感觉这个题出的恰到好处,挺有意思的,稍微恶心点,就偏脑筋急转弯了。
ps:这里的脚本让我同时学到了一个找出所有排序的一个递归算法,真的牛逼,也会记录在逆向杂项里面
脚本:
#排序算法:这里就是用了一个换头的方法,我让a做第一位,剩下的递归,一直到只有一位,返回这一位 def px_re(s=''): if(len(s)==1): return [s] #递归的末尾值 arr=[] #盛放每一次递归的出来的顺序数组 for i in range(len(s)): for j in px_re(s[0:i]+s[i+1:]): #这里就是讲开头的值取出,进行递归,比如'abcd',最开头可能取a\b\c\d四个值,用一个len把开头的可能性全取出来,然后往后递归 arr.append(s[i]+j) #数组拼接 return arr #返回每次递归产生的数组 #解题思路 ans='juhuhfenlapsiuerhjifdunu';s=[];s1=[] #之前爆破脚本得到的字符串 #建立一个对应字典,这里就是因为直接用数组没法经过px_re,只能用字符串,所以我们可以用字典来替代 for i in range(len(ans)//4): s.append(ans[i*4:i*4+4]) for i in range(len(ans)//4): s1.append(chr(ord('a')+i)) dic_arr=dict(zip(s1,s)) #print(dic_arr) #此题产生v11加密算法 def code(s): res=0 for i in range(len(s)): res^=(ord(s[i])+i) return res cons1=code(ans) #print(cons1) px_arr=px_re('abcdef') #创建序列到px_arr res_arr=[] for i in range(len(px_arr)): tmp="" for j in range(6): tmp+=dic_arr[px_arr[i][j]] res_arr.append(tmp) #print(res_arr) dic=[254 ,233 ,244 ,226 ,241 ,250 ,244 ,228 ,240 ,231 ,228 ,229 ,227 ,242 ,245 ,239 ,232 ,255 ,246 ,244 ,253 ,180 ,165 ,178] #题目的字典建立 #print(res_arr) #下面为最终爆破flag算法 for i in range(len(res_arr)): res="" for j in range(len(res_arr[i])): res+=chr(ord(res_arr[i][j])^dic[j]^cons1) print(res,res_arr[i],hex(cons1))
结果: