ctypes:投射字符串function?

我正在阅读文章在笔测试期间躲避反病毒的提示,并对给定的Python程序感到惊讶:

from ctypes import * shellcode = '\xfc\xe8\x89\x00\x00....' memorywithshell = create_string_buffer(shellcode, len(shellcode)) shell = cast(memorywithshell, CFUNCTYPE(c_void_p)) shell() 

shellcode缩短了。 有人可以解释发生了什么吗? 我熟悉Python和C,我尝试过阅读ctypes模块,但还有两个主要问题:

  • 什么存储在shellcode
    我知道这与C有关(在文章中它是来自Metasploit的shellcode,并且选择了不同的ASCII符号),但是我无法确定它是否是C源(可能不是)或源自某种编译(哪一个?)。

  • 根据第一个问题,演员阵容中发生了什么?

看看这个shellcode,我从这里开始 (它弹出一个MessageBoxA):

 #include  typedef void (* function_t)(void); unsigned char shellcode[] = "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B" "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9" "\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C" "\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0" "\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B" "\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72" "\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03" "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47" "\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F" "\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72" "\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66" "\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14" "\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72" "\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F" "\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01" "\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65" "\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B" "\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42" "\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24" "\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57" "\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01" "\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F" "\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24" "\x40\xFF\x54\x24\x40\x57\xFF\xD0"; void real_function(void) { puts("I'm here"); } int main(int argc, char **argv) { function_t function = (function_t) &shellcode[0]; real_function(); function(); return 0; } 

在任何调试器下编译它是一个钩子,我将使用gdb:

 > gcc shellcode.c -o shellcode > gdb -q shellcode.exe Reading symbols from shellcode.exe...done. (gdb) > 

反汇编主函数,看看调用real_functionfunction之间有什么不同:

 (gdb) disassemble main Dump of assembler code for function main: 0x004013a0 <+0>: push %ebp 0x004013a1 <+1>: mov %esp,%ebp 0x004013a3 <+3>: and $0xfffffff0,%esp 0x004013a6 <+6>: sub $0x10,%esp 0x004013a9 <+9>: call 0x4018e4 <__main> 0x004013ae <+14>: movl $0x402000,0xc(%esp) 0x004013b6 <+22>: call 0x40138c  ; <- here we call our `real_function` 0x004013bb <+27>: mov 0xc(%esp),%eax 0x004013bf <+31>: call *%eax ; <- here we call the address that is loaded in eax (the address of the beginning of our shellcode) 0x004013c1 <+33>: mov $0x0,%eax 0x004013c6 <+38>: leave 0x004013c7 <+39>: ret End of assembler dump. (gdb) 

有两个call ,让我们在处设置一个断点来查看eax中加载的内容:

 (gdb) break *(main+31) Breakpoint 1 at 0x4013bf (gdb) run Starting program: shellcode.exe [New Thread 2856.0xb24] I'm here Breakpoint 1, 0x004013bf in main () (gdb) disassemble Dump of assembler code for function main: 0x004013a0 <+0>: push %ebp 0x004013a1 <+1>: mov %esp,%ebp 0x004013a3 <+3>: and $0xfffffff0,%esp 0x004013a6 <+6>: sub $0x10,%esp 0x004013a9 <+9>: call 0x4018e4 <__main> 0x004013ae <+14>: movl $0x402000,0xc(%esp) 0x004013b6 <+22>: call 0x40138c  0x004013bb <+27>: mov 0xc(%esp),%eax => 0x004013bf <+31>: call *%eax ; now we are here 0x004013c1 <+33>: mov $0x0,%eax 0x004013c6 <+38>: leave 0x004013c7 <+39>: ret End of assembler dump. (gdb) 

查看eax中地址继续的数据的前3个字节:

 (gdb) x/3x $eax 0x402000 : 0xfc 0x33 0xd2 (gdb) ^-------^--------^---- the first 3 bytes of the shellcode 

因此,CPU将call 0x402000 ,我们的shell代码开始于0x402000 ,让我们在0x402000反汇编:

 (gdb) disassemble 0x402000 Dump of assembler code for function shellcode: 0x00402000 <+0>: cld 0x00402001 <+1>: xor %edx,%edx 0x00402003 <+3>: mov $0x30,%dl 0x00402005 <+5>: pushl %fs:(%edx) 0x00402008 <+8>: pop %edx 0x00402009 <+9>: mov 0xc(%edx),%edx 0x0040200c <+12>: mov 0x14(%edx),%edx 0x0040200f <+15>: mov 0x28(%edx),%esi 0x00402012 <+18>: xor %ecx,%ecx 0x00402014 <+20>: mov $0x18,%cl 0x00402016 <+22>: xor %edi,%edi 0x00402018 <+24>: xor %eax,%eax 0x0040201a <+26>: lods %ds:(%esi),%al 0x0040201b <+27>: cmp $0x61,%al 0x0040201d <+29>: jl 0x402021  .... 

如您所见,shellcode只不过是汇编指令,唯一不同的是您编写这些指令的方式,它使用特殊技术使其更具可移植性,例如从不使用固定地址。

python等同于上面的程序:

 #!python from ctypes import * shellcode_data = "\ \xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B\ \x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9\ \xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C\ \x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0\ \x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B\ \x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72\ \x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\ \xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\ \x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\ \x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\ \x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66\ \x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14\ \x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72\ \x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\ \x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01\ \x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65\ \x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B\ \xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42\ \x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24\ \x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57\ \x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01\ \x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F\ \x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24\ \x40\xFF\x54\x24\x40\x57\xFF\xD0" shellcode = c_char_p(shellcode_data) function = cast(shellcode, CFUNCTYPE(None)) function() 
  • shellcode ,如果我没弄错的话,包含特定于体系结构的编译代码,大致翻译为函数调用。 (不是架构专家,代码被截断……)

  • 因此,一旦你用create_string_buffer创建了一个C风格的字符串,你就可以让python认为它是一个带有cast调用的函数。 Python然后执行最初包含在shellcode的代码。

这里有一个有用的链接: http : //www.blackhatlibrary.net/Python#Ctypes

我们不要忘记,为了获得可执行代码,必须将其转换为您的机器可以理解的格式。 你在做什么是提供一系列可以由你的机器解释的字节代码,所以你可以告诉你的机器执行它。 通过提供最终的字节代码,您实际上正在跳过编译器的工作; 这种技术在Just-In-Time编译器中很常见,它必须在程序运行时创建可执行代码。 因此,这实际上与C(或Python或任何其他语言)几乎没有关系,但与此代码预期运行的体系结构的细节有很大关系。

第一个字节代码是CLD (0xfc),后跟一个CALL指令(0xe8),它使代码根据该字节码序列中接下来的4个字节中指定的偏移量跳转到地址,依此类推。