【2024春节】解题领红包之web题writeup
一年一度的吾爱春节ctf开赛了
原视频:吾爱破解【2024年春节】解题领红包之Web题
flag1
来源于视频00:02-00:03
几帧,下图圈上的位置白点中间的字就是flag1。
推荐把视频下载下来,使用播放器/剪辑软件逐帧看会更明显。
flag1{52pj2024}
flag2
视频00:04-00:07
里有四部分被切开的二维码
全部截图出来,放进PS,使用多边形套索工具圈出图片里的二维码,然后拼到一起。
这里发现,二维码定位点是白色,因此需要做一次反色处理
把4个残缺的二维码合并到同一图层,然后点击上方图像-调整-反相
这样就能拿到正常的二维码
直接扫码,得到一个网址:https://2024challenge.52pojie.cn/
直接尝试打开,发现无法打开。
想到去年的思路,同属.52pojie.cn,应该和主站在同一台服务器。
直接nslookup 52pojie.cn
得到主站ip,然后去系统hosts内添加一条
124.232.185.97 2024challenge.52pojie.cn
就可以打开了。
先打开浏览器f12,来到网络面板,再输入网址尝试进入。
flag2就在第一个请求的响应头内的X-Flag2
。
flag2{xHOpRP}
flag3
视频00:00-00:01
是横向移动的雪花,中间有串暂停就看不见的文字,就是flag3.
flag3{GRsgk2}
这段文字自身横向移动方向和背景相反,从而能被人眼识别到。
类似实现的还有这类视频:
https://www.bilibili.com/video/BV1qF411u7sc/
https://www.bilibili.com/video/BV1GM4y1b7JB/
flag4
继续看上一步的网络面板,可以看到请求了一个名为flag4_flag10.png
,直接预览就能看到flag4
flag4{YvJZNS}
可以在这里先把图片保存到本地,为寻找flag10做准备。
这里记录个大坑:最好使用curl等工具下载该图片到本地,最新版Edge自带了个“Edge Image Viewer”,其中的“另存为”功能会导致图片丢失信息,无法找到flag10
flag5
浏览器f12切到元素面板,发现了个<pre>
元素,上方注释了flag5和flag9
鼠标选中该元素,取消勾选这三个就能在网页上方看到文字(这三个主要作用为将其内部文字颜色设为白色并且使用户无法选中该段文字)
粗略一看,有flag四个字母零散放置。
打开文本编辑器,找到l的位置,换行,随后在a的位置换行
可以发现刚好字符数量能对齐,那就按照等字符数量在相同的位置换行(这里一定要使用等宽字体)
可以发现第一列竖着看刚好是flag5,右侧是flag9的字符画。
flag5{P3prqF}
flag9{KHTALK}
flag6
直接点击网页内flag6链接打开,检查元素,发现下方有js代码
看下方js逻辑,大致为寻找md5是1c450bbafad15ad87c32831fa1a616fc
的字符串$str
,然后拼接为形如flag6{$str}
的字符输出。
打开cmd5.com,搜一下这串md5就能推出结果。
flag6{20240217}
(或者,直接点击上方按钮,然后像论坛每次开放注册一样,等几分钟?)
(实测2.17号18:20跑87s可以出结果,不到2分钟).
flag7
视频00:21
下方出现github仓库网址
https://github.com/ganlvtech/52pojie-2024-challenge
打开提交历史,查看diff就能找到flag7
flag7{Djl9NQ}
flag8
回到https://2024challenge.52pojie.cn/
直接进入flag8&flagB页面,可以看到是个2048游戏,flag8需要总计积分(金币)达到10000,不算太多,可以直接玩几局拿到(我大概玩了7分钟)
直接购买并使用即可拿到
flag8{OaOjIK}
flag9
和flag5同时拿到,获取流程见flag5
这里给出字符画原文
f.______________________________________________________________________________________________________________________________________________________________________________________________________________.............
l..______________________________________________________________________________________________________________________________________________________________________________________________________________............
a..________/\\\\\__/\\\\\\_____________________________________/\\\\\\\\\__________________/\\\________/\\\__/\\\________/\\\__/\\\\\\\\\\\\\\\_____/\\\\\\\\\_____/\\\______________/\\\________/\\\_____________...........
g...______/\\\///__\////\\\___________________________________/\\\///////\\\________/\\\\\_\/\\\_____/\\\//__\/\\\_______\/\\\_\///////\\\/////____/\\\\\\\\\\\\\__\/\\\_____________\/\\\_____/\\\//___/\\\\\_____..........
5...._____/\\\_________\/\\\_____________________/\\\\\\\\____/\\\______\//\\\_____/\\\///__\/\\\__/\\\//_____\/\\\_______\/\\\_______\/\\\________/\\\/////////\\\_\/\\\_____________\/\\\__/\\\//_____\////\\\____.........
{.....__/\\\\\\\\\______\/\\\_____/\\\\\\\\\_____/\\\////\\\__\//\\\_____/\\\\\____\//\\\____\/\\\\\\//\\\_____\/\\\\\\\\\\\\\\\_______\/\\\_______\/\\\_______\/\\\_\/\\\_____________\/\\\\\\//\\\________/\\\_____........
P......_\////\\\//_______\/\\\____\////////\\\___\//\\\\\\\\\___\///\\\\\\\\/\\\__/\\\\\\_____\/\\\//_\//\\\____\/\\\/////////\\\_______\/\\\_______\/\\\\\\\\\\\\\\\_\/\\\_____________\/\\\//_\//\\\______\//\\\\\\_.......
3.......____\/\\\_________\/\\\______/\\\\\\\\\\___\///////\\\_____\////////\/\\\_\/////\\\____\/\\\____\//\\\___\/\\\_______\/\\\_______\/\\\_______\/\\\/////////\\\_\/\\\_____________\/\\\____\//\\\______/\\\///__......
p........____\/\\\_________\/\\\_____/\\\/////\\\___/\\_____\\\___/\\________/\\\______/\\\_____\/\\\_____\//\\\__\/\\\_______\/\\\_______\/\\\_______\/\\\_______\/\\\_\/\\\_____________\/\\\_____\//\\\____\//\\\____.....
r.........____\/\\\_______/\\\\\\\\\_\//\\\\\\\\/\\_\//\\\\\\\\___\//\\\\\\\\\\\/______\///\\\\\_\/\\\______\//\\\_\/\\\_______\/\\\_______\/\\\_______\/\\\_______\/\\\_\/\\\\\\\\\\\\\\\_\/\\\______\//\\\__/\\\\\_____....
q..........____\///_______\/////////___\////////\//___\////////_____\///////////__________\/////__\///________\///__\///________\///________\///________\///________\///__\///////////////__\///________\///__\/////______...
F..........._______________________________________________________________________________________________________________________________________________________________________________________________________________..
}............_______________________________________________________________________________________________________________________________________________________________________________________________________________.
flag9{KHTALK}
flag10
回到flag4那里下载到的flag4_flag10.png
既然是一个png,png有个特点是有多个颜色通道,其余颜色通道内可能还有内容
思路来源:PNG - CTF Wiki (ctf-wiki.org),LSB部分
PNG采用LSB隐写隐藏内容也是个ctf常见套路
这里使用一个工具Stegsolve,来查看不同颜色通道的内容
下载地址:http://www.caesum.com/handbook/Stegsolve.jar
需要有java环境,java -jar运行
打开图片,xor就能看见flag10
flag10{6BxMkW}
flag11
观察网页源代码可知,要对css内两个变量取到合适的值,从而拼好图片
如果为了方便做题,可以考虑将网页和图片保存到本地后,在网页body内加两个拖动条来更改var1和var2,类似下方这种
<div>
<label for="slider1">Variable 1:</label>
<input type="range" id="slider1" min="0" max="100" value="0">
</div>
<div>
<label for="slider2">Variable 2:</label>
<input type="range" id="slider2" min="0" max="100" value="0">
</div>
<script>
document.getElementById('slider1').addEventListener('input', function () {
document.documentElement.style.setProperty('--var1', this.value);
});
document.getElementById('slider2').addEventListener('input', function () {
document.documentElement.style.setProperty('--var2', this.value);
});
</script>
最终结果:
:root {
--var1: 71;
--var2: 20;
}
flag11{HPQfVF}
flag12
来到flag12页面
看网络请求了个wasm就可以猜到这题要进行wasm逆向了
先看网页里的js逻辑
大致思路就是调用wasm里get_flag12函数拿到一个num,然后经过一些运算变成flag12
所以问题的关键就是拿到wasm内该函数的返回值
我不懂wasm,这里求助了GPT,尝试分析wasm的代码
(module
- 表示代码开始定义一个wasm模块。
(memory $memory (;0;) (export "memory") 16)
- 这行代码定义了一个名为$memory的内存,并将其导出为"memory"。16表示分配了16个WebAssembly 页面的内存,每个页面大小为64KiB,所以总共分配了1MiB (16 * 64KiB) 的内存空间。
(global $__stack_pointer (;0;) (mut i32) (i32.const 1048576))
- 定义了一个名为$__stack_pointer的全局变量,为可变的32位整数(i32),初始值为1048576。这通常表示栈指针的起始位置。
(global $__data_end (;1;) (export "__data_end") i32 (i32.const 1048576))
- 定义并导出了名为"__data_end"的全局变量,它是一个32位整数值,初始值同样为1048576。这个变量标记了数据段的结束位置。
(global $__heap_base (;2;) (export "__heap_base") i32 (i32.const 1048576))
- 定义并导出了一个叫"__heap_base"的全局变量。它的值也是1048576,标识了堆的起始地址。
(func $get_flag12 (;0;) (export "get_flag12") (param $var0 i32) (result i32)
- 这里定义了一个名为get_flag12
的函数,并将其导出。该函数接受一个i32类型的参数var0
,并返回一个i32类型的结果。
i32.const 1213159497
- 在函数内部,首先将常数1213159497压入栈顶。
i32.const 0
- 紧接着将常数0压入栈顶。
local.get $var0
- 获取传入的参数"$var0"的值,并压入栈顶。
i32.const 1103515245
- 将常数1103515245压入栈顶。
i32.mul
- 将栈顶的两个i32类型的值相乘(即常数1103515245和参数"$var0"的值),并将结果压入栈顶。
i32.const 1
- 将常数1压入栈顶。
i32.eq
- 比较栈顶的两个值是否相等(即乘法结果和常数1是否相等),并将比较结果(0或1)压入栈顶。
select
- 基于栈顶的比较结果选择其下的两个值中的一个压入栈顶(如果比较结果为1,则选择1213159497,如果为0,则选择0)。
)
- 函数定义的结束括号,此时函数返回栈顶的值,作为结果。
简单总结,"get_flag12"函数计算($var0 * 1103515245) == 1
的表达式,如果计算结果为真(true),则返回1213159497,如果为假(false),则返回0。
显然返回0不能得到flag,那就直接取1213159497作为给js的返回值
之后js逻辑照抄原页面,放到浏览器控制台运行就有答案了
let num = 1213159497;
let str = '';
while (num > 0) {
str = String.fromCodePoint(num & 0xff) + str;
num >>= 8;
}
console.log(`flag12{${str}}`);
flag12{HOXI}
如果想找到这个符合条件的输入值,也可以使用数学方法。
我们需要找到一个整数$var0
,它乘以1103515245
后模32位整数溢出后的结果必须等于1。虽然可以使用一个简单的循环来搜索这样的整数,不过考虑到32位整数的范围非常大,从-2^31
到2^31-1
,穷举可能非常耗时。
由已知能够写下:
$var0 * 1103515245 ≡ 1 (mod 2^32)
这意味着1103515245
在模2^32
意义下的逆元是我们要寻找的$var0
。因此可以使用扩展欧几里得算法来寻找这个逆元。
下面是一个Python函数,我们将使用它来寻找1103515245
模2^32
的乘法逆元:
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
def modinv(a, m):
g, x, y = egcd(a, m)
if g != 1:
raise Exception('无解')
else:
return x % m
# 计算1103515245模2^32的逆元
modulus = 2**32
a = 1103515245
inverse_a = modinv(a, modulus)
print("结果:", inverse_a)
上面的脚本首先定义了egcd
函数来实现扩展欧几里得算法,并定义了modinv
函数来寻找模逆。接着,我们使用modinv
函数寻找1103515245
在模2^32
意义下的逆元。这个代码应该会返回我们所需的那个符合条件的32位整数。
运行可得,4005161829
符合题意。
flagA
回到https://2024challenge.52pojie.cn/
退出登录后按下f12,重新登录,查看登录过程的网络请求
可以发现,login请求的响应头放了两个cookie,uid
和flagA
很显然,两个都是密文,需要寻找解密方法
留意到后续请求了一个uid的接口,其返回值是uid的明文
既然Cookie里有uid的密文,这个接口的返回值是uid的明文,那不妨直接改Cookie,把flagA的密文放到uid的位置,看看会发生什么
开发者面板的应用程序-Cookie这里提供了对Cookie的修改功能,直接替换
随后回到网络面板,找到uid请求,右键-在新标签页打开
奇迹出现
flagB
先登录,回到2048的页面
一看flagB,好家伙,需要9.9亿金币
简单计算下,按照之前每7分钟拿到1万金币,暴力法大概需要玩480天,明显不可取。
v我50道具给的关键词也提示了我们正确思路:溢出
接下来,就需要判断金币的数据范围是什么,构造多大的溢出
先尝试最常见的int
int32的最大值是2^31 - 1 = 2147483647
int64的最大值是2^63 - 1 = 9223372036854775807
先计算出临界值对应的数量
>>> (pow(2,31)-1)/999063388
2.149496891582619
>>> (pow(2,63)-1)/999063388
9232018856.5
先看int32,可以尝试请求买2个和3个,结果都是“钱不够”,看来不是32位溢出
尝试int64,买9232018856个和9232018857个,看看有没有区别
9232018856个仍然提示钱不够,但9232018857个出现了另一个结果
由此可知为int64溢出
这里找到的溢出点相当于第一个向上溢出的点,所以钱会变多导致被拦截。
那么找到第一个向下溢出的点,钱就会正常减少,从而实现正常购买
那么从int64最大值开始逐渐尝试(我自己做题时是向服务器发送请求找到第一个提示钱不够而不是钱多了的点)即可获得一个符合要求值18464037713,刚好在64位内int64和28等效
>>> bin(18464037713*999063388)
'0b10000000000000000000000000000000000000000000000000000000000011100'
>>> bin(28)
'0b11100'
直接买18464037713个,扣除28金币,得到答案
flagC
看页面可以看出是物品识别,要求了数量和位置必须和它的预设一致。
个人想到的方案:可以根据test.jpg提示的物品内容,把图里的各个元素单独提取出来(比如用QQ截图单独截取然后钉在桌面),然后在桌面上/某个窗口内调整位置/数量,然后利用obs的虚拟摄像头共享桌面,使用摄像头,根据返回的文字提示慢慢调整位置/数量即可解出。
以下是一个1920*1080的图片位置参考,一共四件
后记
flag8 & flagB 中,游戏数据被记录在名为game2048_user_data
的Cookie里,也是加密文字,按照类似flagA的做法也能还原为json,看来这里和uid以及flagA用的是同一种加密方式
不过如果想利用login接口尝试将其变成密文就不行了
如果这里不做验证,或许可以通过直接改JSON的方式拿到flagB
这里贴一个能过flag8和flagB的Cookie供参考
kIc8IGsHes1HrUU8gHkub17LMRmCoc1ECG583Z5eI6mczcYSrozQiobbon+ZAyDyLXKbSkQjTdSEAqCIN2ot7UFlX6PzRZNp6En5A6HeltB2Zz6Wbf8fZnY5PdfopQuR3QeQyj70Q2EbMv42pqwhy40ap3epsmxlJshH6YA0Jl16vfzL80dMsJmlnF2ju7hGXYuSjrcuDGXzyQeICcGqIdcZDe88o7JiEfsf/iQu
解密后:
{"game_data":{"tiles":[4,8,2,4,16,64,32,8,8,32,16,4,2,0,2,8],"score":692},"money_count":10288,"remove_piece_count":26,"flag_b_count":166176339416}