Read the article "WindowsNT Buffer Overflow's From Start to Finish"

简介: WindowsNT Buffer Overflow's From Start to Finish I've read most of the articles on BO's(Buffer Overflows) on the net.
WindowsNT Buffer Overflow's From Start to Finish

I've read most of the articles on BO's(Buffer Overflows) on the net. I have found that 
they either for *NIX systems, or they are not detailed enough. The author's usually take
some known vulnerable software and show you step by step how to exploit it. I am going
to take a different approach. I am going to write an app that has a buffer overflow when
reading data from a file. Then I will write an app that will create the file, that when
read, will cause the exploit. I will also include an opcode finding tool. Tools Needed: Visual C++ 6.0 Windows NT *The code and addresses I use are for Windows NT Workstation
4.0 SP6 .First lets write the app that will contain the buffer
overflow. We also want the app to be able to read in some
type of file so we can actually exploit this from some type
of script. So in Visual C++ create a new console application,
select "An Application that supports MFC" and click Finish.
This does not necessarily have to be a MFC app, but I
prefer to use some of the MFC classes. Obviously, I am a
windows programmer. So let's add some exploitable code
here. This is what it will look like: CWinApp theApp; using namespace std; void overflow(char* buff); 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; } 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); } Let's analyze the code a bit now and find where the problem actually is. Since this
is an MFC console app, the "main" routine may look a little
different, but it works the same. Let's skip to the else
section inside main. You see the first line, char buff[10].
We have allocated a local variable, buff which is an array
of 10 chars. We all know local variables are allocated on
the stack right? So now we call the function overflow and
pass it our buff. Now lets look inside the overflow function.
First we instantiate a CFile object, then a CFileException
object. Now we will attempt to open a file named "overflow.
txt" from the current directory, with read access. If we
open the file successfully we will get the files length,
then we will read the entire contents of the file into our
buff. Do you see the problem here? buff is only 10 chars.
What happens if the file we read is 100? BUFFER OVERFLOW.
But, the big problem is that we are overflowing a buffer
which exists on the stack. When we can write to the stack
we can do some strange things. As you will soon see. So now
lets create a text file called overflow.txt and place it into
the project directory of the first application. Let's step to the side for a second, a little explanation
of WindowsNT memory architecture is in order here. In NT every
process (executable) is given 4GB (0xFFFFFFFF) of virtual memory
when it is started. Some of this memory is actually shared among
all processes, like kernel and device driver areas. But those
areas are mapped to each processes virtual address space.
No process actually gets 4GB of phyiscal memory, only the
memory necessary is actually allocated from physical. So
every process has full 4GB of virtual memory, which ranges
from 0x00000000 to 0xFFFFFFFF. These areas are divided.
0x00000000 to 0x0000FFFF is reserved for NULL pointer assignments.
Attempting to access memory in this area will cause an access
violation. 0x00010000 to 0x7FFEFFFF is the processes user space.
This is where the exe image is loaded (starting at 0x00400000)
and DLL's are loaded. If code (a DLL or EXE) is loaded at a certain
address in this range it can be executed. Accessing an address which
does not have code loaded in it will cause an access violation.
0x7FFF0000 to 0x7FFFFFFF is reserved bad pointer assignments and you
will get an access violation with any attempt to access it. 0x80000000
to 0xFFFFFFFF is for operating system use only. Things like Device
Drivers and other Kernel level code is stored here. Attempting to
access this area from a user level application (ring 3) will cause
an access violation. Now back to the overflow.txt file. We are going to keep
putting characters into our text file until we see the
dialog popup informing us of an application error and what
memory we attempted to access. Which character you chose to
fill this text file with is important, as you will see in
minute. Let's start by filling the text file with a's.
Lower case a's. We know the buffer will hold ten so lets
start with 11(make sure your application being built in
debug mode or your results will be different). 11 doesn't
work so we keep increasing it. 18 finally causes a crash.
This crash isn't anything special yet. We've just totally
screwed up the stack and it shows. Lets add six more a's,
for a total of 24. Run the program and watch the dialog
popup explaining to us that instruction at 0x61616161 had
referenced memory at 0x61616161. You do know that the hex
value for the ascii character a is 0x61 right? If you have
Visual C++ installed you will be able to hit cancel now,
and it will debug the application. Once visual studio is
open, open you registers window. To do that go to the view
menu, then debug window, and select registers. If you don't
know anything about assembly, you should, get a book and
READ IT. We see that EAX has been taken, and so has EBP and
EIP. The most important thing is EIP. By being able to fill
in the EIP with whatever we want we are able to jump to any
code in memory. And what makes this even easier is that our
ESP is not destroyed. It seems to point near the area on
the stack that we control. We need to test this to find out. Now let's get into this. Set a breakpoint on the last bracket
of the main routine, we only care about what happens here.
Now start the debugger and it will make it to this breakpoint
with no errors. Now we need to switch into disassembly view.
If you have the standard keyboard setup for Visual C++ press
alt+8, if not go to the view menu, debug windows, and select
disassembly Also open your memory and registers windows if
you haven't already. You should see something similiar to
this: 004011DB 5F pop edi 004011DC 5E pop esi 004011DD 5B pop ebx 004011DE 83 C4 50 add esp,50h 004011E1 3B EC cmp ebp,esp 004011E3 E8 28 04 00 00 call _chkesp (00401610) 004011E8 8B E5 mov esp,ebp 004011EA 5D pop ebp 004011EB C3 ret So what is that junk? It's assembly code. You do know
assembly right? Even if you don't, I'll try to make this
easy to understand. Starting at the top we have pop edi.
The pop instruction will remove one item from the top of
the stack and place it into whatever register. In this case
edi. Also important here is the ESP. The ESP is the 32 bit
stack pointer. A pop will mov(e) the top element from the
stack, in this case a DWORD (4 bytes), put it in whatever
register, and increment the stack pointer by 4 (because of
the 4 bytes). So before making another step, look at ESP.
In the memory window enter ESP. You will now see exactly
where esp is pointing to and what is there. Look at the four
bytes pointed to by ESP and watch edi. Now step over this
instruction and notice that edi is now filled with
whatever esp pointed to, and esp has been incremented by
four. Now the next two instructions are the same, but
different registers, step over them and see that they work
the same way. The next three lines are not very important
to us. To understand them you will need to follow the
assembly from the beginning of the routine, and we aren't
doing that. Just step over them, they do nothing special.
Now onto the line, mov esp,ebp. You read this line, right
to left. This will mov(e) whatever is in EBP into ESP.
This also does nothing special for us. Now onto pop ebp.
Here is where this gets interesting. Remember what a pop 

does, it removes the top element from the stack. Now lets
take a look at where we our ESP is pointing to, cause
whatever four bytes are there are about to go into EBP.
So again type esp into your memory window. We have a bunch
of 0x61's there (hex value of 'a'). So 0x61616161 is about
to be popped into ebp. Step over the instruction and verify
that it does. Sure enough, that is what happens. But that doesn't
really get us anywhere. Now the next line, ret. Ret is the assembly
return instruction. But there is more to it than just returning. How
does it know where to return to? By the address that is supposed to
be sitting on the stack right now. The return would be the equivalent
of pop eip (which you can't do). It takes the four bytes that ESP points
to and moves them into EIP. And EIP is our 32 bit instruction pointer.
This mean, whatever address EIP points to, is the next instruction to get
executed. So once again, type esp into the memory window and see what we
are about to put into EIP. Well what do you know, another four bytes of
0x61. So step over the ret instruction and watch what happens. EIP will
become 0x61616161 and you will be about to execute the instruction at
0x61616161. Which in my case is nothing ???, invalid memory. So step over
again and you get an access violation. Now look at ESP. It correctly points
to the next area on the stack. For some reason, if you run the program
independant of the debugger and let it crash so you get the ok/cancel dialog,
and then press cancel. When you land on 0x61616161 your ESP will be wrong.
I'm not sure why that is, but it works as expected when you step through
it line by line like we just did. So now we got the program to execute, or
attempt to execute code at 0x61616161, which means we can take over the EIP.
So lets see if we can overflow the stack some more, so that when we get to
0x61616161 our ESP points to the rest of our overflow. So lets add another
4 a's to our text file and debug again. We now have 28 a's in our text file.
So we view the disassembly again, make sure to have your memory window and
register windows open. Step through and over the ret instruction. You are
now at 0x61616161 again. Now type esp into the memory window and look what
is there. Just as we suspected, there are 4 0x61's there. Now we are in business. Let me go back to a point I made earlier. We used a's (0x61) to fill our text
file to determine if there was an overflow. So since EIP became 0x61616161 we
attempted to access instructions at that address. In my case there was invalid
memory there so it was an access violation. But what if there had been code there?
Maybe a DLL loaded or something. Well, it would have executed that code and probably
done something totally different. The same thing could have happened if we would have
used, A's instead of a's. A's hex value is 0x41. So we would have jumped to 0x41414141
instead of 0x61616161. There could be code there and it would have executed it. So keep
those things in mind. So we can control the EIP, the ESP points to the rest of the stack, and we can
fill the stack with whatever we like. So now what? Would it be nice if we could
could just jump to ESP and start executing? Well we can, hopefully. Jmp ESP is
in fact a legal instruction. This instruction would mov(e) whatever is in ESP
into EIP and begin executing instructions there. So we need to somehow call jmp
esp. Hmm, how can we do that? Well, lets think. We do have control of EIP, so we
can jump to where ever we want in our process space. If we can fill EIP with the
address of a jmp esp instruction somewhere in memory we are in business. So how
do we find out if there is a jmp esp instruction somewhere in our process space?
It's easier than you think. The first thing we need to do is figure out what the
opcodes for jmp esp are. The opcodes are the machine instructions that programs
are compiled into so they can be executed. So let's create a new app in Visual
C++. Again a console app, and again with MFC. Enter the following code: 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; } Now set a breakpoint on the return 0; statement, because the inline assembly line
will not get executed. Start the debugger and let it run to the breakpoint. Now
open up the disassembly debug window. Right click on the window to turn on source
annotation and code bytes. Now look at the line which contains jmp esp. To the
left of jmp esp and to the right of its address, you will see its code bytes
or opcodes. The opcodes for jmp esp are FF E4. So now that we know that, how
do we find that in oour process space? Let's add a bit more code to this app.
Change it to the following: 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("Kernel32"); h = GetModuleHandle(dllname); if(h == NULL) { h = LoadLibrary(dllname); if(h == NULL) { cout<<"ERROR LOADING DLL: "<
目录
相关文章
|
C++
EDA设计与开发:原理、实例与代码详解
EDA设计与开发:原理、实例与代码详解
2042 0
|
传感器 监控 安全
闭环反馈系统原理概述
有时,为了获得系统的一致性和稳定性并产生控制系统的期望输出,我们使用反馈回路。反馈只不过是输出信号的一部分。这个概念在控制系统中最常见和最重要,以实现输出的稳定性。根据反馈连接,控制系统分为两种类型。它们是开环控制系统和闭环控制系统。下面简单介绍下闭环反馈系统。
4652 0
闭环反馈系统原理概述
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
210 2
|
Java 开发者
在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。
【10月更文挑战第13天】在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。本文将带你深入了解Java命名规则,包括标识符的基本规则、变量和方法的命名方式、常量的命名习惯以及如何避免关键字冲突,通过实例解析,助你写出更规范、优雅的代码。
385 3
|
人工智能 供应链 监控
流程优化:提升电商运营效率的新路径
在数字经济的推动下,电商运营成为企业连接消费者、推动业务增长的核心环节。本文从数据洞察、策略制定、执行优化及协同办公四个方面,深入探讨电商运营的各个环节,旨在通过高效协同工具提升运营效率,优化用户体验,实现业务持续增长。
阿里云国际版CDN网页打不开、页面报错该如何解决?
阿里云国际版CDN网页打不开、页面报错该如何解决?
|
存储 自然语言处理 机器人
ROS2教程06 ROS2行动
这篇文章是关于ROS2(Robot Operating System 2)行动(Action)通信机制的教程,包括行动的概念、特点、命令行工具的使用,以及如何编写行动的客户端和服务器代码,并介绍了如何测试行动通信。
596 4
ROS2教程06 ROS2行动
|
资源调度 分布式计算 Hadoop
搭建YARN集群
文章介绍了如何搭建YARN集群,包括启动HDFS集群、修改YARN配置文件、启动ResourceManager和NodeManager节点,并提供了用于管理Hadoop集群的自定义脚本。
397 3
|
消息中间件 存储 Kafka
kafka是如何实现高性能高吞吐的?
以下是某网站上对该问题的总结,一共分为了以下六点,但这上面说的很浅显,我在后面加了一些自己的理解,做为解释,如有遗漏或者不对的地方欢迎大家指点,我会即时的修改,辛苦诸位老铁!
285 0
|
存储 关系型数据库 MySQL
PolarDB的特点在不同版本间各有所长
【5月更文挑战第14天】PolarDB的特点在不同版本间各有所长
354 2