天天看点

栈溢出笔记1.5 换一个汇编工具

前面的内容中我们使用VC++内联汇编,虽然很方便,但是无法定义字符串常量,导致我们需要把字符串的ASCII码找到,再一个一个压入栈中,比较繁琐,本节,我们换一个汇编工具——nasm,选择它是因为它可以跨平台使用。

有了这个汇编工具,我们就可以定义字符串常量了,例如:

/*****************************************************************************/
DB  “example_1”, 0x00
DB  “HelloWorld”, 0x00
/*****************************************************************************/
           

这样方便是方便了,但是出现了新的问题,我们不可能写成这个样子:

/*****************************************************************************/
push    ebp
        mov ebp, esp

        db      “example_1”, 0x00
db      “HelloWorld”, 0x00
db      “user32.dll”, 0x00

        lea     ebx, [ebp-24h]
        push    ebx
        mov ebx, 0x7c801d7b 
        call        ebx // LoadLibraryA
/*****************************************************************************/
           

因为三个字符串为数据,而不是指令,不可能夹在指令中间,这样会让CPU将数据当做指令运行,程序跑飞。因此,只能放在指令区域之外。但是,又必须放在代码段中,因为Shellcode只有指令,没有数据段。因此,有了下面这种经典的做法:将字符串定义放置到一条CALL指令之后,由于CALL指令会将返回地址压栈,此时压入栈中的地址就为第一个字符串的地址,从而,我们可以在此后的代码中在栈上获取该字符串的地址。如下结构:

/*****************************************************************************/ JMP short GetString
RunMsgBox:
...
GetString:
CALL RunMsgBox
DB  “example_1”
DB  “HelloWorld”
/*****************************************************************************/
           

这种结构就充分说明了指令和数据并不区分,本来CALL指令用来在栈上保存指令,结果现在被用来保存数据了。

下面就是利用上述结构编写的汇编程序:

/*****************************************************************************/
// example_8 nasm下的汇编Shellcode
SECTION .text

BITS 32

global _main

_main:
    jmp short GetString

RunMsgBox:
    pop     ebx

    push    ebx             ; "user32.dll"
    mov     eax, 0x7c801d7b  ; LoadLibraryA
    call    eax

    xor     eax,eax
    push    eax
    lea     ebx, [ebx+11]    ; "example_1"
    push    ebx
    lea     ebx, [ebx+10]    ; "HelloWorld"
    push    ebx
    push    eax

    mov     ebx, 0x77d507ea ; MessageBoxA
    call    ebx

    push    eax
    mov     ebx, 0x7c81cafa  ; ExitProcess
    call    ebx    

GetString:
    call    RunMsgBox
    db      "user32.dll", 0x00
    db      "example_1", 0x00
    db      "HelloWorld", 0x00
/*****************************************************************************/
           

RunMsgBox中第一句pop ebx就将“user32.dll”的首地址保存在了ebx,这里注意与栈不同,后面的ebx地址是加而不是减。使用命令:

nasm -f win32 example_8.asm

编译为obj文件。然后使用VS的cl.exe,链接为exe:

cl example_8.obj libcmt.lib

运行exe,成功:

栈溢出笔记1.5 换一个汇编工具

图34

下面我们在Immunity Debugger来看看:

栈溢出笔记1.5 换一个汇编工具

图35

这一段就是完整的代码,可见,字符串数据全部被反汇编为指令了,上图中标出了三个字符串的结尾符。

标出了结尾符之后,问题也就来了,是的,空字节,又出现了空字节,而且这次更不好处理,因为我们只用了字符串的首地址。为了不出现空字节,我们定义字符串的时候不能加0x00结束符,但是使用字符串的时候又需要该结束符,而且,这样定义的字符串位于代码段,代码段是不可写的,也就是一旦定义,不可修改。所以,如果空字节会引起问题,就不要这样定义。使用直接压栈的方法反而容易处理。

使用nasm有个好处,就是可以直接编译为bin格式,即操作码,例如:

nasm -f bin -o example_8.bin example_8.asm

用HexEdit打开example_8.bin,如下:

栈溢出笔记1.5 换一个汇编工具

图36

这样,可以简单的写个程序将bin文件写出为Shellcode,就不用再去Immunity Debugger一个字节一个字节的抠出来了。

继续阅读