破防实践6 补丁

2022-05-24

补丁是一个很常见的词语了

相信大家在玩游戏的时候经常会出现遇到bug,需要打补丁的情况。

打补丁的最大好处就是直接对二进制文件进行修改,不用我们再重新编译一次源文件。 像一些漏洞修复的事情,通常都是加1-2行小代码就能解决,但是你重新编译一次大程序要花几个小时,太亏了(如果期间出了什么问题,又要重来)

手动打补丁

源程序

修改后的程序

修改后的程序的ida分析

手动打补丁,顾名思义,我们需要对二进制文件进行手动修改。

这时候需要用到的工具就是 ida了

先看源程序。

很简单的程序。逻辑也十分清晰。

任务目标

  1. 修改输出函数,只打印跟我们性别有关的话

  2. 这个程序有一个栈溢出漏洞,需要我们打补丁,打上后不再有栈溢出的影响。

实现

这个就很简单

只打印跟我们性别有关的话,我们只需要用ida nop掉其中一个puts函数就可以了。

如下图

至于栈溢出漏洞,我们可以把 gets函数换成 syscall中的read来解决。

首先我们要再eh.frame段中写入要patch的汇编代码。

至于为什么选 eh.frame段,其实没有特别的利用,你选其他的段都可以,只要满足以下条件

  1. 该段是可执行的

  2. 该段没有被程序用到(如果被用到了,你修改了可能就crash了)

写入eh.frame段的内容如下

当然只改这个是不够的,这个程序还是不能执行到我们写入的内容。我们还需要改 call gets的那部分内容为 jmp 0x870

让程序跳到我们写的逻辑上来。

lief框架运用打补丁

源程序

修改后的源程序

程序分析

这个逻辑更是简单的不行,也不用我多说了

目标

hook printf函数为我们自己编写的 printf函数,利用”/bin/sh”拿到shell

要用lief库

lief库在python3中只需要

pip install lief

思路

这道题和前面一道题不同的是,这道题目的 eh_frame段是不可执行的

用如下命令查看

readelf --segments getshell

可以看到 .eh_frame section在程序的04段中

而04段是只读段。

所以首先我们需要改变 04段的权限。

第二步和上面一下,在.eh_frame段中写入我们的代码。

这回跟上面那个不同,因为要用lief实现全自动patch,所以我们选择修改 这个程序的symbol表,使得它会jmp到我们的.eh_frame中执行我们的代码。

exp

import lief
from pwn import *
context.arch = 'amd64'
binary_1 = lief.parse('./getshell')
shellcode = asm("mov rsi,0 \nmov rdx,0\nmov rax,59\nsyscall")
print(shellcode)
# rdi,rsi,rdx

eh_frame=binary_1.get_section(".eh_frame") #拿到section后写入
content = list(shellcode + b'0'*(eh_frame.size-len(shellcode)))
eh_frame.content = content
eh_frame_seg = binary_1.segments[4] #04段
eh_frame_seg.flags = lief.ELF.SEGMENT_FLAGS(7) #修改权限
printf_sym = binary_1.get_symbol("printf")
binary_1.patch_pltgot('printf',eh_frame.virtual_address) #把printf符号的值patch掉

binary_1.write("./getshell_hooked")

运行完效果如下

热补丁技术

热补丁技术是课外有时间的同学来做的(可惜叔叔我没有时间啦

但是热补丁的原理还是看了看的

原理

如果要修改一个已经编译完成的可执行文件中某个函数的执行流程,有2种方式:

  1. 通过设置LD_PRELOAD,但这种方式需要重启已经运行的可执行文件。

  2. 通过修改可执行文件某个函数指向的地址,指向新的函数,这种方式可不重启已经运行的可执行文件。

当然 第一种其实不能完全算是热补丁

第二种是货真价实的热补丁技术

第二种的具体思路如下

1. attach到目标进程(使用ptrace)

2. 在进程中找到dlopen等函数的地址

3. 在可执行文件中找到存储函数的的地址pRelocate

4. 保存原始函数的地址pOriginal(即*pRelocation),此步可用于打补丁不成功后的恢复

5. 通过dlopen将补丁文件加载到进程空间

6. 加载补丁文件完成后,找到补丁文件中的函数地址pPatchFun

7. 将存储的函数地址指向新的补丁函数,*pRelocation = pPatchFun

https://github.com/vikasnkumar/hotpatch

这里有一个现成的热补丁工具,想玩的可以试试。

优势

热补丁技术最大的好处就是你可以在程序仍然运行的时候对它进行修补,你不用重新启动它。

经典样例

想必大家玩二进制的,一般都听说过 linux的livepatch吧?

livepatch就是一个热补丁技术的典型用例。

你可以仍然用你的ubuntu虚拟机干活,它还是自动帮你打补丁。

注意!

但是livepatch也不是所有文件都可以打补丁的

要允许实时修补工作,需要满足几个要求。首先,内核本身需要支持livepatch。在4.x中添加了初始支持,因此您需要一个最新的内核。其次,您的系统需要一个客户端工具来检索内核补丁并加载它们。要允许加载内核补丁,需要将系统配置为允许加载内核模块。内核补丁通常由Linux发行版创建。它需要一些专业知识才能知道如何重定向指令集。

livepatch还必须解决 保证一致性 的问题(已经有解决方案了)

试想,在一个循环中,如果实时补丁了一个在循环中调用的函数, 如果无法保证该函数版本的一致性,那不可避免地很可能遇到意外的情况,甚至是系统 crash。

热补丁在游戏领域也挺经常出现的。

reference

https://www.zhihu.com/question/31491962