攻防世界————asong

今天做了几道攻防世界的逆向题,有一道题比较麻烦,虽然不难,但耗费了不少时间,其中有很多时间是可以节省的,所以这次就把这道题记录下来。

本来打算直接静调出结果,但中间卡在了一个点上,不得不动调,虽然最后发现,动调并没有给多大的帮助0- 0

放到ida,我已经分好了类,首先输入,然后判断一下开头结尾,之后建立一个字典,最后对输入进行算数处理,逻辑很清晰,没有任何阻碍
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中的核心算法图(横线是作用代码,未标横线便是混淆代码)
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)

效果图
base58效果图

从不同的思维方向去看待问题,问题便会得到解决,这次学习有很大的帮助,继续努力O(∩_∩)O~

攻防世界————echo-server(XCTF 3rd-NJCTF-2017)

花指令的简单去除,学习一下,深入学习将会在null比赛的逆向的wp中写出,这道题简单的介绍一下一些小的花指令,首先ida分析看到f5的一些不正常表现可以分析出来有花,去花
ida图

花简单来说就是junk_data,做一些无意义的汇编操作,并且让ida反汇编失败,这里因为花很简单,所以我们直接手动patch就行了,看到一些奇怪的汇编指令,比如突然jmp,毫无意义的地址,clc等等,都可以对其进行重组,以下是patch点。
这里会是一个call loc,nop e8,后面的数c成代码下面的图同理,那个F1@g字符串是key,千万别nop
ida图
ida图
ida图

最后效果图
去花图
去花图

然后逻辑就很简单了简单的异或,这里就不说了,只是说一下花,就不浪费时间做异或练习了

攻防世界————first(XCTF 3rd-NJCTF-2017)

开始有个线程把我吓一跳,后来发现对题目难度没什么影响,扔到ida里面分析,首先输入,然后将我们输入的值进行了一个归总异或运算,得到一个值v11
归总值
后面循环建立线程
创建线程
其中有一个start_routine,关键函数,进去以后发现会对我们输入的值进行一个函数改造,然后判断,这里我坑了一会,这个函数是md5看出来了,但是出来的判定结果都是16位,我以为是按照[8:-8]的格式得到的16位md5,没想到直接取得开头16位,emmmmm
sr函数

然后就是爆破脚本

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))

结果:
爆破出flag


一个好奇的人