1, Simple analysis
1. Browse roughly to find the OEP address, fill in the IAT address, obtain the API address, and preliminarily write the general script
①OEP
② Populate IAT address
③ Get API address
Fill the address into the script for testing
// 1. Define variables MOV dwOEP,0047148B MOV dwGetAPI,001E1914 MOV dwWriteIAT,001E0897 // 2. Clean the environment BC // Clear all software breakpoints BPHWC // Clear all hardware breakpoints BPMC // Clear memory breakpoints // 3. Set breakpoints BPHWS dwOEP, "x" //An interrupt is generated when executing to this address BPHWS dwGetAPI, "x" //An interrupt is generated when executing to this address BPHWS dwWriteIAT, "x" //An interrupt is generated when executing to this address // 4. Circulation LOOP0: RUN // F9 CMP dwGetAPI,eip JNZ CASE1 MOV dwTMP,eax JMP LOOP0 CASE1: CMP dwWriteIAT,eip JNZ CASE2 MOV [edx],dwTMP //MOV DWORD PTR DS:[EDX],EAX JMP LOOP0 CASE2: CMP dwOEP,eip JNZ LOOP0 MSG "OEP Arrived"
2. Run the script again. It is found that the addresses of API acquisition and IAT filling are invalid, indicating that the code address has changed.
Generally speaking, there are two cases where the address is random. One is the random base address, and the other is that the code is located in the applied memory space. In this case, the solution is to find the code base address, calculate the offset, and break points at the code according to the offset.
Obviously, the random address of this place is caused by applying for memory.
Therefore, you can lower the breakpoint at virtuallalloc. After dynamic debugging, you can find that there are many broken positions at virtuallalloc. After the first stack backtracking, the code address is in the module of the program. It is speculated that the memory space applied in this place is to repair the base address of IAT code. Offset the previous code, subtract the base address and add the offset. The code is the same as before, So this place is the place to get the code base address.
After analysis, the script is modified as follows:
// 1. Define variables MOV dwOEP,0047148B MOV dwGetAPI,1914 MOV dwWriteIAT,0897 MOV dwBase,0047A37F // 2. Clean the environment BC // Clear all software breakpoints BPHWC // Clear all hardware breakpoints BPMC // Clear memory breakpoints // 3. Set breakpoints BPHWS dwOEP, "x" //An interrupt is generated when executing to this address BPHWS dwBase, "x" //An interrupt is generated when executing to this address // 4. Circulation LOOP0: RUN // F9 CMP dwBase,eip JNZ CASE0 ADD dwGetAPI,eax // Add base address ADD dwWriteIAT,eax // Add base address BPHWS dwGetAPI, "x" // Lower breakpoint BPHWS dwWriteIAT, "x" // Lower breakpoint BPHWC dwBase // Clear hardware breakpoints JMP LOOP0 CASE0: CMP dwGetAPI,eip JNZ CASE1 MOV dwTMP,eax JMP LOOP0 CASE1: CMP dwWriteIAT,eip JNZ CASE2 MOV [edx],dwTMP //MOV DWORD PTR DS:[EDX],EAX JMP LOOP0 CASE2: CMP dwOEP,eip JNZ LOOP0 MSG "OEP Arrived"
3. Other methods
First obtain the following base address, and then execute the hardware breakpoint under the known address of filling IAT and obtaining API
After the program is disconnected, it is found that the API address is saved in EAX
Set RUN tracking to pause when EIP equals IAT filling, and Ctrl+F7 will automatically enter
View RUN trace
Focus on the data beginning with 7, which may be an AIP address
Up to this location, the API address is still saved in EDX
Decryption idea: save the API address to an unused register, and then fill the IAT directly into the memory pointed to by EDX when filling the IAT
Modification code:
001E14DC 8BDA MOV EBX,EDX user32.BeginPaint 001E14DE 90 NOP 001E14DF 90 NOP 001E0895 891A MOV DWORD PTR DS:[EDX],EBX
Run under breakpoint
DUMP file and fix it with impec
Convert manual operations to scripts
// 1. Define variables MOV dwOEP,0047148B MOV dwPatch1,14DC MOV dwPatch2,0895 MOV dwBase,0047A37F // 2. Clean the environment BC // Clear all software breakpoints BPHWC // Clear all hardware breakpoints BPMC // Clear memory breakpoints // 3. Set breakpoints BPHWS dwOEP, "x" //An interrupt is generated when executing to this address BPHWS dwBase, "x" //An interrupt is generated when executing to this address // 4. Circulation LOOP0: RUN // F9 CMP dwBase,eip JNZ CASE0 ADD dwPatch1,eax // Add base address ADD dwPatch2,eax // Add base address BPHWS dwPatch1, "x" // Lower breakpoint BPHWC dwBase // Clear hardware breakpoints JMP LOOP0 CASE0: CMP dwPatch1,eip JNZ CASE1 FILL dwPatch1,4,90 //NOP 4 bytes ASM dwPatch1,"MOV EBX,EDX" //Modify the current instruction to MOV DWORD PTR DS:[EDI],EAX ASM dwPatch2,"MOV DWORD PTR DS:[EDX],EBX" BPHWC dwPatch1 JMP LOOP0 CASE1: CMP dwOEP,eip JNZ LOOP0 MSG "OEP Arrived"
2, Single step tracking
Load the program into OD and check the program entry first. It is found that there are standard push instructions (pushad/pushfd), so ESP law is used to find OEP
Use the ESP law to step to 470A036, break the ESP, and then run the program
The program is broken under popfd, and then it can reach OEP in a few steps
After reaching OEP, it can be seen from experience that this should be VC6 0 or easy language program. Looking down, you can find that the function address in the first called function CALL[XXX] is encrypted, that is, IAT is encrypted
The general way to decrypt IAT is to write breakpoints in the hardware above and below the IAT function table.
Observe the function call of OEP attachment. According to experience, VC6 The first function called by the 0 program should be GetVersion, but now you can see a random function address, such as applying for a memory address
F7 enters, continues to single track the code in the 275039 address, it can be found that the function address of the original IAT is stored in an address, the code gets the function address of GetVersion after calculating the address, and then calls the GetVersion function.
Encryption function of the program, GetVersion
00275039 68 0A000080 PUSH 0x8000000A ;push A random number, occupied, will be stored at that time API address 0027503E 53 PUSH EBX ;Save register environment 0027503F 57 PUSH EDI ;Save register environment 00275040 E8 00000000 CALL 00275045 00275045 5B POP EBX 00275046 81EB 0C104000 SUB EBX,0x40100C ; 0027504C 81C3 24104000 ADD EBX,0x401024 ;SUB and ADD Can be resolved into an instruction 00275052 8BFB MOV EDI,EBX ; EDI=EBX=0027505D 00275054 8B3F MOV EDI,DWORD PTR DS:[EDI] ; from EDI Get in API address 00275056 897C24 08 MOV DWORD PTR SS:[ESP+0x8],EDI ; take API The address is stored in the stack and the placeholder address just now is modified 0027505A 5F POP EDI ;Recovery register 0027505B 5B POP EBX ;Recovery register 0027505C C3 RETN ;Return to api address 0027505D C744F6 76 00000>MOV DWORD PTR DS:[ESI+ESI*8+0x76],> ;Stored API address Some instructions can be resolved, such as 00275046 81EB 0C104000 SUB EBX,0x40100C 0027504C 81C3 24104000 ADD EBX,0x401024 After it is resolved ADD EBX,0x18
The approximate steps to generate the encrypted IAT function are as follows:
1. Obtain the original IAT function address and store it in a certain location
use loadlibrary A / W, GetProcAddress function
2. Apply for space and construct a new IAT function
use VirtualAlloc to apply for space and copy the code
3. Calculate the encrypted value according to the original IAT function address and hide the real address
calculate the x value in the code similar to the GetVersion function
sub edx,x;
add ebc,x;
4. Write the new IAT function address to the IAT
fill address to IAT
Encryption function
00274986 / EB 73 JMP SHORT 002749FB 002749FB 4C DEC ESP 002749FC E8 DBFFFFFF CALL 002749DC 002749DC 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 002749E0 4C DEC ESP 002749E1 EB 51 JMP SHORT 00274A34 00274A34 4C DEC ESP 00274A35 E8 D1FFFFFF CALL 00274A0B 00274A0B 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 00274A0F 4C DEC ESP 00274A10 ^ EB E4 JMP SHORT 002749F6 002749F6 83EC 04 SUB ESP, 0x4 002749F9 EB 2A JMP SHORT 00274A25 00274A25 50 PUSH EAX 00274A26 ^ EB DD JMP SHORT 00274A05 00274A05 51 PUSH ECX 00274A06 E8 12000000 CALL 00274A1D 00274A1D 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 00274A21 8BC8 MOV ECX, EAX 00274A23 ^ EB B3 JMP SHORT 002749D8 002749D8 03C4 ADD EAX, ESP 002749DA EB 36 JMP SHORT 00274A12 00274A12 2BC1 SUB EAX, ECX 00274A14 E8 0F000000 CALL 00274A28 00274A28 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 00274A2C 8958 08 MOV DWORD PTR DS : [EAX + 0x8] , EBX 00274A2F ^ E9 6EFFFFFF JMP 002749A2 002749A2 59 POP ECX 002749A3 E9 92000000 JMP 00274A3A 00274A3A 58 POP EAX 00274A3B E8 85FFFFFF CALL 002749C5 002749C5 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 002749C9 6A 00 PUSH 0x0 002749CB ^ EB D2 JMP SHORT 0027499F 0027499F 50 PUSH EAX 002749A0 EB 50 JMP SHORT 002749F2 002749F2 03C4 ADD EAX, ESP 002749F4 ^ EB 9B JMP SHORT 00274991 00274991 2B0424 SUB EAX, DWORD PTR SS : [ESP] 00274994 E8 EFFFFFFF CALL 00274988 00274988 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 0027498C 8978 04 MOV DWORD PTR DS : [EAX + 0x4] , EDI 0027498F EB 41 JMP SHORT 002749D2 002749D2 58 POP EAX 002749D3 E8 0B000000 CALL 002749E3 002749E3 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 002749E7 E8 00000000 CALL 002749EC 002749EC 5B POP EBX 002749ED E8 C7FFFFFF CALL 002749B9 002749B9 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 002749BD 81EB 66104000 SUB EBX, 0x401066 002749C3 EB 54 JMP SHORT 00274A19 00274A19 8BFB MOV EDI, EBX 00274A1B ^ EB 8B JMP SHORT 002749A8 002749A8 81C7 C0104000 ADD EDI, 0x4010C0 002749AE EB 51 JMP SHORT 00274A01 00274A01 8B3F MOV EDI, DWORD PTR DS : [EDI] 00274A03 ^ EB AB JMP SHORT 002749B0 002749B0 897C24 08 MOV DWORD PTR SS : [ESP + 0x8] , EDI 002749B4 E9 87000000 JMP 00274A40 00274A40 5F POP EDI 00274A41 ^ E9 53FFFFFF JMP 00274999 00274999 5B POP EBX 0027499A E8 2E000000 CALL 002E49CD 002749CD 8D6424 04 LEA ESP, DWORD PTR SS : [ESP + 0x4] 002749D1 C3 RETN
According to the speculation and the operation of the IAT of the shell, we can roughly deduce that whether the IAT is encrypted or not, the shell will actually fill the IAT, but the encrypted IAT will fill the encrypted function.
Therefore, as long as the IAT function address before encryption and the place where IAT is filled can be found, and the function address before encryption can be written when IAT is filled, the IAT is equivalent to decryption.
Therefore, the next two key points we analyze are the place where IAT is written and the place where IAT function address appears before encryption. These two key points can be cracked and decrypted.
To sum up, let's start with the place written in IAT.
First, at the original OEP, write the breakpoint under the IAT of the GetVersion function and re run the program.
The program is disconnected and the position of filling IAT is found
Generally speaking, the place where the IAT is written should be a loop, which should include operations such as loading modules and obtaining function addresses.
Therefore, we can set the software breakpoint on the LoadLiibraryA/W and GetProcAddress functions, and the hardware execution breakpoint on the next line of code written to the IAT.
Note: the code in the shell is usually decompressed and decrypted, and the general address is unreliable
Continue the one-step tracking analysis. It is found that the code calculates an address and obtains a number of 4 bytes from this address, which is irregular. Generally speaking, this value is the hash value.
Continue the one-step tracking analysis and find that it has obtained the base address of the Kernel32 module
Continue the single step tracking analysis and access the data catalog table
Continuing the one-step tracking analysis, it is found that the exported function string is obtained. Combined with the context analysis, it is speculated that the code is obtaining the exported function string, calculating the hash value of the string, and then comparing it with the hash value just obtained.
Continue tracking and find that the program loads each byte of the function string and calculates it
Program hash function for function string
00311CB2 33D2 XOR EDX,EDX 00311CB4 /EB 1E JMP SHORT 00311CD4 00311CD4 FC CLD 00311CD5 ^\EB AA JMP SHORT 00311C81 00311C81 AC LODS BYTE PTR DS:[ESI] 00311C82 E8 13000000 CALL 00311C9A 00311C9A 8D6424 04 LEA ESP,DWORD PTR SS:[ESP+0x4] 00311C9E 84C0 TEST AL,AL 00311CA0 E8 EDFFFFFF CALL 00311C92 00311C92 8D6424 04 LEA ESP,DWORD PTR SS:[ESP+0x4] 00311C96 74 33 JE SHORT 00311CCB 00311C98 EB 29 JMP SHORT 00311CC3 00311CC3 C1C2 03 ROL EDX,0x3 00311CC6 E8 0C000000 CALL 00311CD7 00311CD7 8D6424 04 LEA ESP,DWORD PTR SS:[ESP+0x4] 00311CDB 32D0 XOR DL,AL 00311CDD ^ EB AF JMP SHORT 00311C8E 00311C8E ^\EB F1 JMP SHORT 00311C81 Simplify to START: LODS BYTE PTR DS:[ESI] TEST AL,AL JE SHORT 00311CCB ROL EDX,0x3 XOR DL,AL JMP START After asking, hash Values are saved in EDX in
When the hash value is calculated, it will be compared
Run the breakpoint directly at the position of 1A28, that is, when the hash is equal
Continue the one-step tracking analysis and find the place to obtain the function address
Continue the one-step tracking analysis. It is found that the function address is processed. Memcpy is used to copy a piece of code, and the function address is written into the code. The new function address is the first address of memcpy copy, which is written into IAT.
So far, we have known the operation process of the program when writing an IAT function address, which can be summarized as the following steps.
1. Get the pre calculated hash value
2. Circularly obtain the name of the exported function in the module currently being obtained, calculate the hash value, and compare it with the pre stored. If it fails, continue to obtain circularly
3. If correct, get the address of the exported function
4. Copy the pre stored code to the buffer and write the address of the exported function to the buffer
5. Write the first address of the buffer to the IAT to complete the operation of filling the IAT
How to decrypt IAT? Starting from the function address here, if we get the original function address and the original function address is still saved in the register when writing the IAT, it will become easy to decrypt the IAT. If the code is executed linearly, we only need to change the jump to complete it, but now the code confusion is relatively high, and it is difficult to find the law. Although it should be possible to change the jump as long as we are patient enough. After carefully tracking the code, we can find that the function address is initially saved in EAX, then saved in EDX, and then EDX is modified to IAT address, EAX is modified to an encrypted address. In this process, as long as we can make EAX the last function address.
After analysis, modify two codes.
First code:
The modification here is to save the function address to EBX, because EBX seems to be of no practical use
Second code:
The modification here is to save the function address to EAX, because the code that fills in IAT last uses EAX
It can also be changed into a script. Similar to method 3 above, you can change the address and part of the code
FILL dwPatch1,4,90 //NOP 4 bytes ASM dwPatch1,"MOV EBX,EDX" FILL dwPatch2,2,90 //NOP 2 bytes ASM dwPatch2,,"MOV EAX,ECX"
Try modifying the shell code directly
8D642404895401FC
Search in the memory window and navigate to the address
0047BB6C 8D6424 04 LEA ESP,DWORD PTR SS:[ESP+0x4] 0047BB70 895401 FC MOV DWORD PTR DS:[ECX+EAX-0x4],EDX ; user32.BeginPaint
Calculate another address
Address = 0047BB70-(002214DC-00220895)
0047AF29 8902 MOV DWORD PTR DS:[EDX],EAX
(execute breakpoints for hardware at 0047BB70 and 0047AF29 addresses)
Change to
0047BB70 mov ebx,edx 0047AF29 MOV DWORD PTR DS:[EDX],EBX
Also scriptable...
FILL dwPatch1,4,90 //NOP 4 bytes ASM dwPatch1,"MOV EBX,EDX" ASM dwPatch2,,"MOV DWORD PTR DS:[EDX],EBX"