前面的内容中我们使用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,成功:
图34
下面我们在Immunity Debugger来看看:
图35
这一段就是完整的代码,可见,字符串数据全部被反汇编为指令了,上图中标出了三个字符串的结尾符。
标出了结尾符之后,问题也就来了,是的,空字节,又出现了空字节,而且这次更不好处理,因为我们只用了字符串的首地址。为了不出现空字节,我们定义字符串的时候不能加0x00结束符,但是使用字符串的时候又需要该结束符,而且,这样定义的字符串位于代码段,代码段是不可写的,也就是一旦定义,不可修改。所以,如果空字节会引起问题,就不要这样定义。使用直接压栈的方法反而容易处理。
使用nasm有个好处,就是可以直接编译为bin格式,即操作码,例如:
nasm -f bin -o example_8.bin example_8.asm
用HexEdit打开example_8.bin,如下:
图36
这样,可以简单的写个程序将bin文件写出为Shellcode,就不用再去Immunity Debugger一个字节一个字节的抠出来了。