实在是非常想再开一次入门课,因为有一个自己觉得还挺巧妙的小想法:我能不能写一个C程序,它不调用后门函数,而是我自己用栈溢出去调用完成getshell。我想从开发的角度,而非从计算机的底层来理解我自己学习到的第一个漏洞,栈溢出。出于初次教学,我们跳过非常多的细节。我认为在第一课中不需要讲的事无巨细。比如我会跳过32位和64位的区别,直接让你用p64()来完成你的第一次的脚本编写,并且我也不会在本文中详细解释为什么要这么做。就像如果让我讲C语言第一课我可能会直接让你用printf输出个"hello world",并不会详细讲解printf的各种用法,也不会讲解C语言的函数是如何编写的,更不会讲解调用函数时候的压栈出栈。这些对于初学者来说太早了,因此本文仅会从现象上入手讲解这个漏洞。本文是我在2025年俱乐部为新生讲解pwn的第一课的内容,用作(直播的时候还没写完这篇,临场发挥的时候没有打磨细节)如果本文有幸能成为你的入门读物,希望本文能让你窥见pwn世界的一角首先,必须要先简单讲一讲C语言的语法。
已经掌握C语言基础语法的直接跳大概自行看看入门的书或者视频,看到函数和数组也就差不多了。能学明白指针就最好了。相关内容就不写在这里啦!也许你刚刚才恶补完C的语法知识,也或许你之前也写过C语言的程序,也听说过栈溢出,但是你相关栈溢出如何利用吗?先看这样的一个程序它写的很简单,先看main函数,它简单声明了一个变量str,然后将其输出,然后调用func()函数。func函数也做了一样的事情。唯一显得有点奇怪的是,有个多余的函数wdf()没有起到任何作用。我们直接编译运行,程序正常输出,没有任何问题。然后我们考虑以下程序:编译运行,oh no 栈溢出了!pwn手看到Segmentation fault (core dumped)就像 xx闻到了xx一样敏感,这就是栈溢出,它让程序崩溃了。(编译指令:gcc -m32 -fno-stack-protector -no-pie stackoverflow2.c -o stackoverflow2)诶,我们不是在打ctf吗,我flag呢?看来pwn并不是这么简单啊,我们还没有拿到flag!分析一下发生了什么吧!看起来,main函数中的printf函数并没有执行啊!像是执行完func之后,程序马上就崩溃了!我们用gdb这个工具进行一下分析,程序到底是怎么崩溃的呢?这里就要讲解一下gdb这个工具了,gdb是一个用于调试程序的工具,它可以让你看到程序每一条的指令,以及程序运行时候内存上面的数据,是用于找出漏洞和利用漏洞的神器!它显示的程序指令是用汇编编写的。后续需要认真的学习一下汇编,至少要能做到能看懂。作为第一课,我们还不用太关注那些汇编,我们能看懂内存上发生了什么就行在func处打断点(b func)然后运行(r),在程序运行的时候逐步调试(ni)汇编太难啦,但是对照一下我们前面的代码,大致对照一下,应该也能看懂个7788吧?我们发现,程序是在这里崩溃的!那么0x76757473是什么呢?可以自行对照一下前面正常运行的程序的调试结果,这里正常的结果应该是“运行完func函数之后,返回到main函数”才对,可是现在变成了一个奇怪的东西。这个奇怪的东西是什么呢?我们可以盲猜一手,这个地址就是我们刚才塞的那一大坨字母表中的几个字母。诶????昨天misc不是讲了ascii码吗!非常凑巧的是,我们进行一个查,发现这些字母刚好是"stuv"。可以自行更换一下这几个变量的值,比如把换成对应的ret的地址也会变成rock的ascii码,这里自行尝试,就不演示了。那么接下来,我们怎么利用这个奇妙的现象呢?注意到,我们有一个wdf()函数还未使用。而我们通过gdb发现,func函数结尾的ret原本想完成的操作是“返回到main函数执行func函数的下一个地址”
那么如果我们把这个地址篡改为wdf函数的地址,是不是就可以执行wdf了呢?至于wdf的地址我们用一个简单的方法,在gdb里面b wdf就可以看到wdf的地址。注意,由于每个人环境都极有可能不一样,所以后面的程序大家需要自己在自己的环境里面去找地址,以及对应的字符串中的位置也很有可能不一样(我最开始的位置偏移也和前面示范的不一样)直接抄我的代码是不一定能够完成这个奇妙小魔法的。找到wdf的地址,然后给它写到那几个变量里面。我这里换成最开始我产生这个想法的时候写的程序进行示范(因为它里面有几个字节刚好是可见的ascii,这样写更装逼)编译运行这个程序:
我们真的做到了!在不调用某个函数的时候调用某个函数,而这就是从C语言到pwn的很重要的一步,我们了解了一个漏洞,叫做 “栈溢出” 而我们利用了这个漏洞,这个漏洞利用方式叫ROP,Return-Oriented Programming 的简写,翻译一下就是面向返回编程,而这个漏洞属于二进制漏洞,也就是我们的pwn。欢迎来到pwn的世界!但是这里我还是要很抱歉的告诉大家,虽然我们已经在初步使用一个漏洞了,到这里我们可能还称不上入门,pwn题并不是自己写一个有漏洞的程序,而是攻击有漏洞的程序。我们要在没有源代码的情况下完成对别人程序的攻击利用,具体我们应该怎么操作呢?来看一道题目就明白!首先你需要准备的工具有:以buuctf上面的jarvisoj_level0这题为例,先尝试运行,然后ida打开看一下代码。我们可以看到,程序的逻辑很简单,然后有一个明显的栈溢出:明明只有buf[128]但是却读入了512大小的输入。并且还有一个callsystem函数那么我们考虑刚才的手法,我们要怎么用刚才的手法完成对这个程序的攻击呢?回忆一下,刚才我们是往"变量合法长度"后的空间进行赋值,那么我们在进行输入的时候向128后的空间进行写入,是否也能做到呢?总之,我们先尝试拿一个超长的输入,然后使用gdb看一下!像前面那样打断点,然后在程序输入时,使用cyclic 200生成一个有规律的超长的字符串,复制然后输入进程序。果然,程序又返回到奇怪的东西上了!使用cylic -l 来查看这个值是在字符串的哪个地方:那么再简单看一下callsystem函数的地址,我们就可以开始着手编写漏洞利用(exp)了!当然,我们利用这个漏洞不能只是简单的输入。要向程序内覆写地址,很大概率不能直接写入。因为我们使用的输入函数会把我们的输入当做字符。而字符又只能被转为一个数字才能被存储在内存上。比如,当我们往内存写字母'a'时,在内存上的实际表示是97,16进制就是0x61。而反过来,如果我想往内存覆盖一个0x61616161的地址,那我们直接输入aaaa就可以完成覆盖了。但是如果我们要输入0x00000000这种地址,我们就不能通过键盘输入了,因为字符'0'会被转为数字48。这个转换规则是前面讲过的ascii码的内容,可以再度温习一下。总之,如果我们想要覆写一个16进制的地址在内存上,手工输入是几乎做不到的。所以我们可以写一个脚本来与程序交互,我们打不出来的字符可以用脚本来表示。python其实比C简单很多,不会也没有关系,边学边用吧!稍微教学一下基本的py一开始编写出来的exp差不多长这样:然后我们尝试运行一下我们的exp
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288716.htm
[原创]从C到pwn入门
287 浏览
0 回复
暂无回复,快来抢沙发吧!