Linux之shellcode && 内联汇编 &&execve函数使用方法

  首先上来就是在linux系统中编写一个1.c程序:(这段代码在cpp中跑不通,可以试一下)

#include <stdio.h>
#include <stdlib.h>
void main()
{
exit(0x12);
}

  编译执行:

    

  然后发现输出了一个18。刚好就是0x12。

  这段代码是什么意思呢?之前刚知道$PATH表示环境变量,那么$?是什么意思呢?

                    

  等等,我发现这样在64位ubuntu当中编译出来的可执行程序是64bit的,然而在本次实验中我们需要的是32bit的。32位的汇编和64位的汇编的差别很大,具体的因为我还是汇编的初学者,所以知之甚少。

  所以编译:

  这样编译出来的可执行程序e才是32bit的。

  我们对于可执行程序的main函数进行反汇编

  

  有个问题,这里的地址不是从0x0804800开始的。。。怎么回事呢?

  可以看到最后调用了函数exit。

  但是从《程序员的自我修养》当中可以知道,exit还不是最基础的调用。exit最终调用了_exit函数。我们对其进行反汇编:

  

  这是因为现代操作系统大量使用动态链接库,有些函 数只有在进程启动后才映射到进程的内存空间。为此,在主函数main中的exit位置设置一个断点,并启动进程。(而且这里出现了符号表这个概念。)

  

  display命令是一个重要的gdb调试命令,此处的display命令可以在每一步单步调试的时候都显示eip寄存器的值。

  

  这里main函数已经启动到了该处,现代操作系统已经完成了动态链接的过程,所以现在可以开始反汇编_exit()函数了。

  

  可以看到,_exit函数还不是最小的调用,这里还有 call   *%gs:0x10。在这里设置断点,继续进行反汇编:  

  

  si是汇编语言单步调试,可以看到call *%gs:0x10这个调用将会使得系统进入到内核状态

  继续对进入内核状态指令反汇编:

  

  感觉这个sysenter就是进入内核状态的指令。继续反汇编:

  

  可见,在系统调用之前,进程设置eax的值为0xfc,这是实现_exit的系统调用号;设置ebx的值为_exit的参数0x12,即退出系统的退出码

  这个是我们最终想要获得的信息。因为归根到底cpu当中指令的执行就是依赖寄存器值。前面的过程是编写代码,编译,链接动态库,进入内核,各种调用,最终才完成了exit(0x12)这条指令。

  我们编写一个名为exit_asm的c程序,使用内联汇编的方式修改寄存器的值:

  

  sysenter的功能完全等同于int $0x80,但是有些系统不支持sysenter,int 0x80的通用性更好一些。

  是的,没有错,完全没有函数依赖,也不需要#include,因为没有链接任何东西,这些就是一个完整的c语言程序!

  接着生成可执行文件,然后执行。

  

  

  结果显示的是18!

  然后继续对这个新的main函数进行反汇编:

  

  可以看到,完美执行了:

  mov   $0xfc,%eax

  mov   $0x12,%ebx

  sysenter

  的这样一个过程。可执行代码不依赖于库函数,执行效率非常之高。

  总结如下:

  

 

  有了前面的使用内联汇编来完成C语言程序功能的基础,我们再回到shellcode上面来

  

  下面的几个步骤所介绍的就是如何通过shellcode获取shell。  

  首先编写一个如下的名为shell.c的程序:

  

  这个程序执行之后的功能是能生成一个shell(也就是$),注意,是真正的shell。也就是在外面的shell里面又开了一个新的shell。

  

  

  输入ctrl + z完全无法退出,只有exit才能退出。而且在这个生成的shell里可以执行各种各样的命令。

  所以,上面的foo函数的作用原理是怎么样的?execve函数的作用机理又是怎么样的?foo函数的作用就是为了调用execve函数,而execve函数的功能就是执行一个新的程序。这个新程序就得到了shell。

  后来终于搞懂了execve函数是怎么执行的了。比如我想使用cat命令打开/etc/passwd,结合之前师兄的忠告:所有的可执行程序都是某一个目录下的可执行文件

  

  

  所以编写的c语言代码为:

  

  这就是execve函数的使用方法!!!

  回到这里来。

  然后,我们尝试按照最开始介绍的方法,反汇编得到寄存器值,尝试如何使用内联汇编的方式完成上述c语言的功能

  首先:

  

  

  注意到

  同样的这里的execve是运行时动态链接,所以先在这里设置断点再反汇编:

  

  

  

  

  

  同样注意到call *%gs:0x10,在此处设置断点调试:

  

  同最开始的教程,反汇编直到得到sysenter

  

  

  

  可以看到最后成功地调用出了shell

  同时,通过观察寄存器值我们可以看到:

  eax如最开始所说,保存着execve函数的系统调用号11。

  ebx保存着name[0]这个字符指针的入口地址。

  ecx保存着字符串数组name的指针。name是一个二级指针。

  edx为0。

  所以通过设置相同的寄存器值,也能够实现同样的功能。编写名为shell_asm.c的程序如下所示

  

  我们来解读一下这个内联汇编函数

  首先,通过查阅asc2码表可以得知:‘NULL’对应0x00,'/'对应0x2f,'s'对应0x73,'h'对应0x68,‘b’对应0x62,‘i’对应0x69,‘n’对应0x6e。所以很显然了。

  首先edx寄存器内保存一个NULL值。压入栈内。此时esp指向NULL。

  然后压入的就是ebx。ebx当中需要保存/bin/sh,也就是字符指针name[0]所指向的字符串。显然栈中从上到下地址下降,字符串首地址保存在低字节。而且在函数中,字符串被保存在堆栈地址空间当中。而且ebx保存的是name[0]的首地址,因此将esp的值传递给ebx,完成。

  之后压入的是ecx。ecx保存的是name这个二级指针,ecx指向name[0],也就是%ebx的值,ecx+1指向name[1],也就是%edx的值。因此ecx保存的是压栈之后的esp的值。

  最后eax是直接设置的。然后加上一个 int 0x80就可以了。

   

  之后是三部曲的第三步。

  首先

  生成可执行程序的反汇编代码。在其中找到foo段的代码:

  

  foo段的代码如图所示,可见包含了全部的内联汇编代码。从mov $0x0,%edx到int $0x80都是。

  因此shellcode可为"\xba\x00\x00\x00\x00\x52\x68\x2f\x73\x68\x00\\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80";

  得到了shell之后,一个很重要的问题就是shellcode如何运行呢?之前的内联汇编很容易看懂,但是这一段东西怎么才能让它跑起来?

  使用的是函数指针的方式!

  参考博客函数指针和shellcode_hambaga的博客-CSDN博客

  

  这么简单的方法就成功地调用了func函数。原理是什么呢?func,是函数名,但是它能够强制转化为int类型,转化后的值,就是函数func的入口地址!!!然后强制类型转化为void(*)()类型,这样就是把address改变为了一个函数。

  然后就类比,函数名是函数的入口地址,字符串的名字也是字符串的入口地址。

  所以,同样将字符串转化为函数类型就好啦!

  编写名为shell_asm_badcode.c的C语言文件如下:

  

  

  QQQ然而居然运行不了,不知道为什么原因。

  

  不管这个,这个字符串有一个很大的问题,那就是\x00是字符串结束的标志,不能够用在这里,不然字符串拷贝的时候,拷贝到\x00的时候就停下来。

  因此我们要想办法替代\x00。

  有两种方法

  

 

 

   这里使用第一种方法。

   

  可以看的出来这个方法很妙。//bin/sh,使用两个正斜杠就把/x00顶替掉了。两个正斜杠和一个正斜杠的作用是一样的。不管多少个正斜杠的作用都是一样滴。

  

  然后,我们可以写出修改之后的shell_asm函数,命名为shell_asm_fix.c,如下所示:

  

  同样objdump提取出可执行代码:

  

  

  

 

  shellcode就显然了。按照同样的方法编写c语言程序运行shellcode:

  

  上面那个不行,这个可以!

  

  运行opcode得到了shell!!!

 

  可以开始写作业了。

   

 

 

 

   

posted @ 2021-11-20 14:41  TheDa  阅读(840)  评论(0编辑  收藏  举报