ctypes:如何将字符串转换为函数?

3
我正在阅读文章 《渗透测试期间逃避反病毒软件的技巧》,其中有一份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源代码(很可能不是)还是来源于某种编译(哪种?)。

  • 根据第一个问题,强制类型转换期间发生了什么神奇的事情?


1
你曾经用十六进制编辑器打开过可执行文件吗? :) - pmg
3个回答

4

看看这段shellcode,我从这里拿到的(它会弹出一个MessageBoxA):

#include <stdio.h>

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 <real_function> ; <- 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,我们在<main+31>处设置一个断点,以查看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 <real_function>
   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 <shellcode>:   0xfc    0x33    0xd2
(gdb)                    ^-------^--------^---- the first 3 bytes of the shellcode

因此,CPU将会调用0x402000,即我们shell代码的开头处,让我们反汇编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+33>
   ....

正如您所看到的,一个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()

1
谢谢!这正是我在寻找的见解。很可能我自己不会开发shellcode,而且我认为在Python中嵌入汇编有点奇怪,但现在我知道发生了什么 :) - f4lco

3
  • Shellcode 包含特定架构的编译后的代码,大致相当于一个函数调用。(我不是一个架构专家,而且代码被截断了...)

  • 因此,一旦你使用 create_string_buffer 创建了 C 风格的字符串,你可以通过 cast 调用来欺骗 Python 认为它是一个函数。Python 接着执行最初包含在 shellcode 中的代码。

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


2
不要忘记,为了有可执行的代码,必须将其转换为您的机器可以理解的格式。您正在提供一系列可以由机器解释的字节码序列,以便告诉您的机器执行它。通过提供最终的字节码,您实际上跳过了编译器的工作;这种技术在即时编译器中很常见,因为它们必须在程序运行时创建可执行代码。因此,这与C(或Python或任何其他语言)实际上没有多少关系,但与期望运行该代码的架构的详细信息有很大关系。
第一个字节码是CLD(0xfc),后面是CALL指令(0xe8),使代码根据此字节码序列中下4个字节中指定的偏移量跳转到地址,依此类推。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接