Scan of the Month 33: Anti Reverse Engineering Uncovered

Win64 驱动内核编程-23.Ring0 InLineHook 和UnHook Ring0InLineHook和UnHook 如果是要在R0里hook,作者的建议是InLineHOOK,毕竟SSDTHOOK和SHADOWSSDTHOOK比较麻烦,不好修改。目前R3的InLineHOOK我比较喜欢的是MINIHOOKENGINE,但是今天要解决的是R0的InLineHOOK问题。 下面说下InLineHOOK的思路: 一、实现... 阅读详情

Scan of the Month 33: Anti Reverse Engineering Uncovered

By Nicolas Brulez - 0x90(at)Rstack(dot)org



Rather than doing another complete analysis of the binary, i will rather present the techniques i have used in the challenge, and how i have implemented them. The Scan of the Month 33 was released by the Honeynet Project in November 2004. I invite everyone to read the excellent submissions we received this month once they have read my paper. I am presenting the binary from the protection author point of view, while they presented it from the analyst point of view. You will learn the methods and techniques used to Protect / Unprotect a binary with this month's challenge. A lot of weaknesses were left on purpose in this binary and they will be presented here.

Contents




The Challenge:
This month's challenge is to analyze an unknown binary, in an effort to reinforce the value of reverse engineering, and improve
(by learning from the security community) the methods, tools and procedures used to do it. This challenge is similar to SotM 32.
However, this binary has mechanisms implemented to make the binary much harder to analyze, to protect against reverse engineering. 
Skill Level: Advanced/Expert

All we are going to tell you about the binary is that it was 'found' on a WinXP system and has now be sent to you for analysis. 
You will have to analyse it in-depth and get as much information as possible about its inner working, and what is the goal of the binary.
The main goal of this challenge is to teach people how to analyse heavily armored binaries. Such techniques could be used in the future, 
and its time to get used to them.

1 - Identify and explain any techniques in the binary that protect it from being analyzed or reverse engineered

Many techniques have been used in order to slow down analysis and break reverse engineers tools:

  • PE Header Modifications

    Many fields of the PE header were modified in order to disturb analysing tools, and thus, the Reverse Engineer. I will quickly cover the most important changes:


    ->Optional Header
       Magic:                        0x010B  (HDR32_MAGIC)
       MajorLinkerVersion:           0x02
       MinorLinkerVersion:           0x19  -> 2.25
       SizeOfCode:                   0x00000200
       SizeOfInitializedData:        0x00045400
       SizeOfUninitializedData:      0x00000000
       AddressOfEntryPoint:          0x00002000
       BaseOfCode:                   0x00001000
       BaseOfData:                   0x00002000
       ImageBase:                    0x00DE0000  <--- "Non Standard" ImageBase
       SectionAlignment:             0x00001000
       FileAlignment:                0x00001000
       MajorOperatingSystemVersion:  0x0001
       MinorOperatingSystemVersion:  0x0000  -> 1.00
       MajorImageVersion:            0x0000
       MinorImageVersion:            0x0000  -> 0.00
       MajorSubsystemVersion:        0x0004
       MinorSubsystemVersion:        0x0000  -> 4.00
       Win32VersionValue:            0x00000000
       SizeOfImage:                  0x00049000
       SizeOfHeaders:                0x00001000
       CheckSum:                     0x00000000
       Subsystem:                    0x0003  (WINDOWS_CUI)
       DllCharacteristics:           0x0000
       SizeOfStackReserve:           0x00100000
       SizeOfStackCommit:            0x00002000
       SizeOfHeapReserve:            0x00100000
       SizeOfHeapCommit:             0x00001000
       LoaderFlags:                  0xABDBFFDE <--- Bogus Value
       NumberOfRvaAndSizes:          0xDFFFDDDE <--- Bogus Value
    

    The "standard" ImageBase usually is 400000 for Win32 applications and Reverse Engineers are used to analyse programs with such an ImageBase. While it isn't a protection by itself, this simple modification will confuse some Reverse Engineers, because they aren't used to such memory addresses.

    "Anti" OllyDbg:

    LoaderFlags and NumberOfRvaAndSizes were modified.. I have Reverse Engineered OllyDBG and Soft ICE to find a few tricks that could slow down the analysis of a binary. With those two modifications, Olly will pretend that the binary isn't a good image and will eventually run the application without breaking at its entry point. This could be a bad thing if you wanted to debug a malware on your computer, because you would get infected.

    Anti Soft ICE : Blue Screen of Death and no Chocolate:

    The NumberOfRvaAndSizes field has been modified in order to reboot any computer running a recent version of Soft ICE. While Disassembling the PE Loader of Soft ICE, i found a very critical vulnerability in Soft ICE that allows one binary to crash any computer running Soft ICE without any code execution. This vulnerability (bug) has been reported to Compuware and should be fixed in the next version. Apparently it didn't happen on some of the authors of the submissions for some reasons. Oh well.


    Here is the disassembly of Soft ICE PE loader to find out why it reboots your computer:


    .text:000A79FE
    .text:000A79FE loc_A79FE:                              ; CODE XREF: sub_A79B9+31j
    .text:000A79FE                                         ; sub_A79B9+3Cj
    .text:000A79FE                                         ; DATA XREF: .text:00012F9Bo
    .text:000A79FE                 sti
    .text:000A79FF                 mov     esi, ecx
    .text:000A7A01                 mov     ax, [esi]
    .text:000A7A04                 cmp     ax, 'ZM'
    .text:000A7A08                 jnz     not_PE_file
    .text:000A7A08
    .text:000A7A0E                 mov     edi, [esi+_IMAGE_DOS_HEADER.e_lfanew]
    .text:000A7A11                 add     edi, esi
    .text:000A7A13                 mov     ax, [edi]
    .text:000A7A16                 cmp     ax, 'EP'
    .text:000A7A1A                 jnz     not_PE_file
    .text:000A7A1A
    .text:000A7A20                 movzx   ecx, [edi+IMAGE_NT_HEADERS.FileHeader.NumberOfSections]
    .text:000A7A24                 or      ecx, ecx
    .text:000A7A26                 jz      not_PE_file
    .text:000A7A26
    .text:000A7A2C                 mov     eax, [edi+IMAGE_NT_HEADERS.OptionalHeader.NumberOfRvaAndSizes]
    .text:000A7A2F                 lea     edi, [edi+eax*8+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory]
    .text:000A7A33                 mov     eax, ecx
    .text:000A7A35                 imul    eax, 28h
    .text:000A7A38                 mov     al, [eax+edi]   ; CRITICAL BUG! One can force EAX+EDI to be equal to zero. Reading at [0] in ring 0 isn't nice eh ;-)
    .text:000A7A3B
    .text:000A7A3B loc_A7A3B:                              ; DATA XREF: .text:00012FA5o
    .text:000A7A3B                 cli
    .text:000A7A3C                 call    sub_15C08
    .text:000A7A3C
    .text:000A7A41                 mov     byte_FA259, 0
    .text:000A7A48                 push    eax             ; Save EAX
    .text:000A7A49                 mov     eax, dword_16B56F ; EAX is modified by a saved dword
    .text:000A7A4E                 mov     dr7, eax        ; Debug Register 7 take the value in EAX
    .text:000A7A51                 pop     eax             ; EAX is restored
    .text:000A7A52                 mov     dword_FC6CC, esp
    .text:000A7A58                 mov     esp, offset unk_FBABC
    .text:000A7A5D                 and     esp, 0FFFFFFFCh
    .text:000A7A60                 xor     al, al          ; AL is zeroed? Why this mov al, [eax+edi] then ?
    .text:000A7A60                                         ; I don't see the point. old code?
    .text:000A7A62                 call    sub_4D2EB
    .text:000A7A62
    .text:000A7A67                 call    sub_36AC1
    .text:000A7A67
    .text:000A7A6C                 xor     edx, edx
    .text:000A7A6E
    .text:000A7A6E loc_A7A6E:                              ; CODE XREF: sub_A79B9+124j
    .text:000A7A6E                 call    sub_74916
    .text:000A7A6E
    
    

    As you can see from the code above, we can force Soft ICE to read at memory location [0] or something similar using a special value inside the PE header. For this binary i didn't bother calculating the exact value to read at address [0], that's may explain why it didn't crash for some people.I won't explain how to calculate this special value because it is trivial and i don't want Darklords to use that trick without a little brainstorming.

    To fix this problems, one needs to patch the value in the PE Header. The standard value for NumberOfRvaAndSizes is 0x10.Just patch this value in the PE Header and the Soft ICE wrecking will be gone. The OllyDBG problem as well, because it is based on BOTH fields modifications. You can also nullify the other field if you want.

  • Section Modification: Or how to kill many tools.
    ->Section Header Table
       1. item:
        Name:                  CODE
        VirtualSize:           0x00001000
        VirtualAddress:        0x00001000
        SizeOfRawData:         0x00001000
        PointerToRawData:      0x00001000
        PointerToRelocations:  0x00000000
        PointerToLinenumbers:  0x00000000
        NumberOfRelocations:   0x0000
        NumberOfLinenumbers:   0x0000
        Characteristics:       0xE0000020
        (CODE, EXECUTE, READ, WRITE)
    
       2. item:
        Name:                  DATA
        VirtualSize:           0x00045000
        VirtualAddress:        0x00002000
        SizeOfRawData:         0x00045000
        PointerToRawData:      0x00002000
        PointerToRelocations:  0x00000000
        PointerToLinenumbers:  0x00000000
        NumberOfRelocations:   0x0000
        NumberOfLinenumbers:   0x0000
        Characteristics:       0xC0000040
        (INITIALIZED_DATA, READ, WRITE)
    
       3. item:
        Name:                  NicolasB
        VirtualSize:           0x00001000
        VirtualAddress:        0x00047000
        SizeOfRawData:         0xEFEFADFF   <--- BIG Size of section on the disk.
        PointerToRawData:      0x00047000
        PointerToRelocations:  0x00000000
        PointerToLinenumbers:  0x00000000
        NumberOfRelocations:   0x0000
        NumberOfLinenumbers:   0x0000
        Characteristics:       0xC0000040
        (INITIALIZED_DATA, READ, WRITE)
    
       4. item:
        Name:                  .idata
        VirtualSize:           0x00001000
        VirtualAddress:        0x00048000
        SizeOfRawData:         0x00001000
        PointerToRawData:      0x00047000
        PointerToRelocations:  0x00000000
        PointerToLinenumbers:  0x00000000
        NumberOfRelocations:   0x0000
        NumberOfLinenumbers:   0x0000
        Characteristics:       0xC0000040
        (INITIALIZED_DATA, READ, WRITE)
    

    From those informations, we can conclude a few things. First, the binary doesn't seem to be compressed, because the Virtual Address and Size matche the Raw Offset and Size at one exception, the NicolasB section. This section has an extremly big size of raw data, which will crash a few tools and make a few others very very slow.

    IDA will try to allocate a LOT of memory because it thinks that the section is THAT big, turning your computer into a very slow turtle ;-). Eventually, it will load the file, or run out of memory, depending of the computer you are using to do the analysis.

    This modification will also create havoc with many tools such as Objdump, PE editor, some memory dumpers etc. It is very easy to fix this problem, you need to correct the Raw Size. If you look at the section following this special one, you will find that it starts at the very same Raw Offset. This means that the other section is actually null on the disk. You can therefore, safely replace the big value by zero.

    Protection Weakness:

    While writing this binary, i knew people were going to patch the PE header but i didn't do any integrity checks on purpose. Originally i wanted to use the value in the PE Header as keys to decrypt a few layers of the protection, and the result would have been an unworking binary if this one had been changed.

    I have also changed a few other things in the PE header, but nothing of real interest here. (who said Cosmetic?)


  • Junk Code

    All along the binary, i have added junk code between real instructions, in order to make the analysis a little harder. The junk code are long blocks of code that does nothing but fancy operations to disturb the analyst , especially when he choose to do a static analysis of the binary. Each block of Junk Code is different and have been generated by a personal tool. A Thrash generator which creates macros to be inserted in the code source around real instructions.

    Here is how it looks inside a disassembler:



    The junk code starts with a pushad (save all registers states onto the stack) and finish with a popad (restore register states).Here is the end of a block of junk:


    Protection Weakness:

    The thrash generator isn't perfect (at least with the options i have used here ;) and it is easy to find the start and the end of a block of junk code. The junk code is bounded by pushad/popad. When i wrote this binary i was aware of this problem, but this is a perfect real life example of protection weakness. It allows Reverse Engineers to practice IDA/Ollydbg scripting. Very interesting scripts were found in the submissions. I invite you to have a look at them if you didn't know how to write one. When i wrote the binary, i already had a better version of my Thrash generator that doesn't use any pushad/popad around the blocks of useless code, but we will keep it for another challenge, if any.

  • SEH - Structured Exception Handling

    Windows SEH were used extensively in this binary. It allows one to access the context structure of the current application, and therefore, access privileged registers such as Debug Registers. Those registers are used by Hardware Breakpoints (BPM). If you can access them, you can also erase the hardware breakpoints.

  • Timing Detection Through SEH

    Here is a little detection i invented to detect debuggers. If we merge SEH (And access to context structure) with the known Timing Detection Technique, we can detect a lot of Ring 3 debuggers and Tracers. The idea is to read the Time Stamp Counter using RDTSC (number of cycles executed by the CPU basically) and then generating an Exception.

    In the exception handler, we can access the EAX register (previously modified by RDTSC) in the Context Structure, which contains the TSC. In the Exception Handler, we use RDTSC one more time, to get the current TSC value. Now, we can compare both TSC to see whether the program has been debugged/traced or not. If such an action has occured, the difference of cycles will be huges, thus triggering the Payload. In this binary, i just modified EIP through the context structure. The application resumes at a different location skipping mandatory instructions.The application crashes eventually. It seems that on some version on Windows, it doesn't work as expected because of the utilisation of the CPUID instruction, that will modify the ECX register.

    The detection became less stealth because of this "bug", but it would still have been a matter of time until someone discovered it anyway. Many people wondered why i used CPUID in the program before RDTSC. The reason is that on recent CPU such as P4, there is a feature called: Out of Order Execution. The CPUID is a synchronization instruction which tells to the CPU not to use Out Of Order execution, avoiding False Positives in the debugger detection. If you don't tell to the CPU not to use OOO execution, you don't know in which order the CPU is going to execute your code. It can be different from your source code. Sometimes, it will create a false positive and your program will crash for no reason.

    Here is the code of this detection:


    E0000h is the maximum cycles difference accepted by this detection. If the number is bigger, then a debugger is most likely running and debugging our application.

    Protection Weakness:

    I have used a fixed value for the number of cycles: E0000h. I could have (Actually i can do it with my layer generator) used a random value rather than a constant and therefore, making the scan for this constant useless. I could also have used different instructions for each SEH to make the creation of a generic pattern difficult. The biggest weakness of this detection is the constant and the usage of the same instructions for every checks. It is also possible to write a Kernel Module Driver to catch every execution of RDTSC (See Intel documentation for further informations) and return very similar values, thus bypassing the detection completely.

  • BPX Detection:

    As we are going to use API functions, We have to protect them from beeing BPX'ed by an attacker. Rather than Using GetProcAddress to get the API address and then to check for an int 3 opcode (0xCC) in the API function code, i have used a different method. I directly access the Import Table , more precisely, the Import Address Table to read the API function address and then start to search for breakpoints.

    The int 3 opcode is 0xCC and is known by Reverse Engineers. In order to make a little less obvious, i have obfuscated the breakpoint check using a "SHR" (Shift Right) instruction: 0x660 shr 3 = 0xCC ;-). The program will then check four bytes at API function entry point, looking for a breakpoint. If a breakpoint is found, i have used a funny way to crash the application. Im using RDTSC to generate a pseudo random number and i put this number onto the stack. To modify EIP, i simply use the RET instruction, which will transfer us to random memory address, crashing our application. Each time a detection occurs, the address is different, thus hard to monitor. The crash occurs far from the detection code and Soft ICE's FAULT ON won't catch it either.

    Protection Weakness:

    First, the Imports aren't protected, therefore anyone can read the Imported functions from the binary. From The import table we can see that printf, GetCommandLineA and ExitProcess are used. This is a weakness. A Reverse Engineer can put breakpoints on those functions, or at least, guess they are going to be used at some point. In the case of our binary, one can guess that the application is waiting for a special command line. A solution would be to load the Import Table manually.

    For this we could use a home made GetProcAddress function to browse the Export Table of the dlls we want to import functions from, and then, get the address of the API function from there. A Kernel32 address is always on the stack when a binary is started, so we could have used this value to get the dll's ImageBase (Or use the PEB, SEH chaining etc..). We would have everything needed to get the address of Loadlibrary which allows us to Load ANY dll, and thus, to get the address of ANY API function. With this method, we don't need any Import Table at all.

    Well actually, this isn't true. There is a mandatory thing to do to keep compatibility with all versions of Windows. We have to create a very small Import Table, with at least ONE import from Kernel32, else the binary won't run on Windows 2000. The Windows 2000 PE Loader is different from the one in Windows XP. XP doesn't care whether there is any import table or not.

    The small Import Table is just for compatibility issue, the real import table is encrypted and will be decrypted at runtime by the protection.

    Then, it is just a matter of loading the Imports mimicing the Operating System. We need to put the API address in the Import Address Table (of the decrypted Import Table) manually. The Reverse Engineer has no clue about the API functions used by the binary until he gets to the part of the code that will decrypt and load the Imports.

    The BPX protection has a few weaknesses. I only check for four bytes at API entry point, which can be easily bypassed, if the API has many instructions. One could put a breakpoint to the first instruction after the 4 bytes boundary.A Better check would use a Length Disassembler Engine (LDE) which tells us the size of the instructions. With this, we can safely scan a lot of instructions without triggering any false positive.

    A genuine instruction can contain the byte 0xCC and yet not beeing a breakpoint. Eg: Mov eax, 0x4010CC. The detection would trigger a false positive on this instruction, because of the 0xCC inside of it. On the other hand, a LDE would tell us the size of this instruction (5 Bytes). An int 3 (breakpoint) is either one or two bytes (0xCC or 0xCD 0x03). We would therefore skip the current instruction and check the following one.

    Also, the BPX check is only done once per API at a given location in the binary.Once we have stepped over those checks , we can put a breakpoint on any API function without triggering any error. This weakness wasn't fixed on purpose because this is a common error in Protection Systems.

    There is another kind of BPX detection that will be described in the next section

  • The Crazy Layers

    Here is a little more challenging protection. In order to protect the binary from beeing disassembled, i have written an Encryption Layer generator, that will generate the number of layers i want. For this binary, i used 175 layers. The Layer Generator has many options. Here are the options from the config file: (0 means disabled)

    SEH=1
    RANDOM_LAYER_SIZE=0
    RANDOM_REGISTERS=1
    RANDOM_ENCRYPTION=0
    ENCRYPTED_RETURN_ADDRESS=1
    TIMING_DETECTION=1
    RANDOM_CONSTANT=0
    JUNKS=0
    PUSHAD_POPAD=1
    RANDOM_ORDER=0
    USE_DIFFERENT_LOOP_CODE=0
    RANDOM_FIRST_BLOCK=0
    NUMBER=175
    

    I will comment each options below:

    SEH:

    This tells to my layer generator to use (or not) SEH inside the layers.

    RANDOM LAYER SIZE:

    This tells to my layer generator to use a different size for each layer. This option wasn't enabled to simplify the analysis.

    RANDOM REGISTERS:

    If this option is enabled, all the layers are using different registers. Some kind of "polymorphism". This option was enabled.

    RANDOM ENCRYPTION:

    When this option is enabled, Each layer will have a different encryption algorithm. I didn't enable this option. Therefore all the layers have a static encryption code. (Default layer)

    ENCRYPTED RETURN ADDRESS

    This option will encrypt the return address inside the layer. It avoids a simple patch to skip the SEH.This option was enabled

    TIMING_DETECTION:

    Tells whether the layers must use Timing Detection or not. I enabled this option.

    RANDOM_CONSTANT:

    The Random constant is to tell whether we want to use a static value for the timing detection or not. This option wasn't enable. All layers were using the defaut value: E0000h. Enabling this option will also modify the code that checks for the Difference between both TSC.

    JUNKS:

    Enable of Disable Junks in the Layers. I disabled this option because the layers are WAY biggers when it is enabled. The resulting binary is too huge and slow if you use a big number of layers.

    PUSHAD_POPAD:

    This option tells the Layer Generator to use (or not) Pushad/Popad around the Junk Code. The layer generator directly use the Thrash Generator (external tool) i have programmed. I was using pushad popad in the junk code, that's why it is enabled. This option does nothing if the Junks option is disabled.

    RANDOM_ORDER:

    Each layer use a table to access part of its code. If this option is enabled, Each layer has a random order of execution. I didn't enable this one on purpose.

    USE_DIFFERENT_LOOP_CODE:

    Each layer loops a given number of time. With this option, one can use different code to test the end of the loop. It makes it harder for the reverse engineer to find removal pattern. This option wasn't enabled. A defaut checking code was used.

    RANDOM_FIRST_BLOCK:

    This option allows one to use random value inside the first elements of the layers tables. You will see in some submissions that the static value were used to bypass the layers. I didn't enable this option to see whether someone was going to use it or not.

    NUMBER:

    This is the number of the layer, the generator must use. I used 175 layers in this challenge. I can generate 65000 layers in a few seconds because the generator engine is programmed in Assembly Language.



    Presentation of the encryption layers:

    Layer Selector

    xor esi,esi                                           		; ESI = 0
    mad_loop175_1:                                        		; Loop label
    inc esi                                               		; ESI++
    mov edi,dword ptr [ebp+(esi*4)+EIPtable175_1]         		; Grab block address
    mov ebx,dword ptr [ebp+(esi*4)+RETable175_1]          		; Grab "Encrypted" Return address
    
    Add ebx, [ebp+_startloader]                           		; Add Base.
    push ebx                                              		; Save Return Address onto the stack
    Call tricky_call175_1						; Fake call
    db 0EBh,01,0E8h							; Some junk crap
    
    fake_ret175_1:							; fake return address label.
    Add edi, [ebp+_startloader]					; Add EDI Base. EDI now contains address of a block inside the layer.
    jmp edi								; Execute that block.
    
    return_addy175_1:
    cmp esi, 4							; When we get back from the block, we check whether we have done every blocks.
    jnz mad_loop175_1						; if we didn't, loop!
    
    bpxcheck175_1:							; Label used for BPX check.
    jmp @layer175_1
    
    tricky_call175_1:							
    pop ebx								; Ret address is in EBX
    
    jmp fake_ret175_1						; Jmp to fake return address.
    @layer175_1:							; end of the layer.
    

    This is the main part of a layer. This part loops through the layer blocks using some obfuscated ways. It prepares the stack with return addresses, and fake a call. If you step over with your debugger on this call, the binary won't break and it will run. If you were debugging a malware, you would get infected. And if you were analysing the binary, you would need to restart from scatch. (Except if you have dumped your position regulary).


    Layers Blocks

    include	obfuscation/junk198.inc                   		; I have added a few junks macro manually in order to add a little fun :)
    	
    dec_loader175_1:						; Decrypt label
    xor byte ptr [edx],cl						; Defaut options were used. Very simple encryption.
    inc edx                                             		; Code to decrypt++
    dec ecx                                             		; Loop index--
    test ecx, ecx                                     	  	; is ECX = 0 ?
    jnz dec_loader175_1                              	   	; no :( therefore we continue to decrypt.
    
    ; This encryption can be different for each layer if you enable the option in the layer Generator.
    
    lea edx , [ebp+bpxcheck175_1]					; Grab address of BPX check.
    cmp byte ptr [edx],0CCh                         		; Any break point ?
    jnz return175_1							; no. Good boy.
    
    rdtsc								; Ah.. he did put a bpx.. EAX = random value
    push eax							; push eax on stack
    ret								; Return to it :) Crash the poor guy.
    
    
    return175_1:							; on return block
    include	obfuscation/junk199.inc					; a few junk
    include	obfuscation/junk19A.inc					; ditto.
    SEHBLOCK 66137317 28513829					; SEH block macro with keys in parameters.
    ret								; return
    
    
    inst175_2_1:							; another block of code
    add dword ptr [esp], 41952561					; fix return address and return.
    ret
    
    
    inst175_3_1:							; Another block.
    mov ecx, (offset _end174_1- @layer175_1)			; Get Size of layer
    add dword ptr [esp], 13007360					; Fix return address and return
    ret
    
    
    
    inst175_1_1:							; Another block
    lea edx, [ebp+@layer175_1]					; Get Layer address
    add dword ptr [esp], 30560857					; Fix the return address and return.
    ret
    

    EIPtable175_1 dd 000DEADh, (offset inst175_1_1 - offset startloader), (offset inst175_2_1 - offset startloader), (offset inst175_3_1 - offset startloader), (offset _end174_1 - offset startloader)

    ; This is a table of offset used to redirect the code.

    RETable175_1 dd 0031000h, (offset return_addy175_1 - offset startloader - 30560857) , (offset return_addy175_1 - offset startloader - 41952561),(offset return_addy175_1 - offset startloader - 13007360),(offset return_addy175_1 - offset startloader - 37623488)

     This is a table of return address with a little "encryption".
    
    ; You can notice the first member of the tables : DEADh and 31000h. Those values are constants and can be random using the RANDOM_FIRST_BLOCK 
    ; option in the layer generator.

    The layer presented above has been generated by the little Layer generator Engine i have programmed. I have added comments for the readers.


    Protection Weakness:

    Those layers have a few weaknesses. You can use BPM (Hardware Break Point) on the next layer once you have passed the SEH that is going to clear the debug registers. Another weakness is the static size of the layer. Using this information, one can pass the layers rather quickly with a few Soft ICE macros for instance. I didn't turn the random size option on, on purpose to allow such attacks.

    Those layers always use the same encryption algo, which can allow one to write scripts to decrypt the binary. And as you can read in a few submissions, some people did it. I did put this weakness on purpose as well. In a challenge i had done in the past, i had used random encryption for each layers, this time i choose not to use it. It is possible to bypass the 175 layers in a few seconds easily as well using a live approach. As we know wich API functions are going to be used, we can set a break point after the BPX checks have occured.Another possibility is to create a little utility that will PATCH the system dll in memory (each application has a copy of the dll) and to redirect them to a place that you contol. This way you can put breakpoints without triggering any Detection code.

    Talking of patching the Windows dll files, it is possible to patch ntdll to avoid the Debug Registers access in the context structure, by hooking the Exception Handling Mechanism of Windows. This allows one to put Hardware Breakpoints anywhere without ever having problems, never seeing his debug breakpoints beeing erased etc. The cool thing is you don't even need a Kernel Mode Driver to do that. I leave this as an exercice for interested people.

  • Virtual Machine

The final protection of the binary is a complete Virtual Machine i wrote for the challenge. I have designed a Virtual CPU that will interpret my own Assembly language. The Virtual Machine is quite simple to understand and isn't very complex.

Virtual Machines seem to be a new trend in protection systems, so i thought it could be a good thing to write one for such a challenge. The instruction encoding is very trivial, and could have been a lot harder to understand. The first Version i had in mind was a lot more complex. I wanted not only to have a pseudo language, but also to program the instructions handlers emulating real x86 instructions. Each handler would be a few hundred instructions long and a lot harder to analyse.

A small program has been written with this Virtual Machine Assembly language, and it was used to authenticate the user running the binary.

Read next part for further informations

2 - Something uncommon has been used to protect the code from beeing reverse engineered, can you identificate what it is and how it works?

Even though, a few protection systems are using some kind of Virtual Machines, those aren't very common. Especially in Malwares and other exploits.

Virtual CPU description and Inner working:

Registers:

REGISTERS STRUC
	R0_		dd	?  ; 000
	R1_		dd	?  ; 001
	R2_		dd	?  ; 002
	COUNTER_	dd	?  ; 003
	EIP_ 		dd	?  ; 004 -> reserved
	STATE_		dd 	?  ; 005
REGISTERS ENDS

This is the original structure from my code source. Every registers is a DWORD. Some registers weren't used because they are reserved for futur version of the Virtual Machine. One can read "EIP_". I planned to add another information per instruction, but i didn't do it, because i didn't want it to be too complex. I will add the ability to change the Instruction pointer for any instruction. The result will be a completely mad code flow. The instruction order in the file will have nothing to do with the real execution flow.

The STATE Register is some kind of mini Eflags. This register changes depending of other instructions.

The COUNTER Register is used for loop instructions. Similar to the ECX register when we use the LOOP instruction.

regs	REGISTERS	<>

R0	equ	000b
R1	equ	001b
R2	equ	010b
COUNTER equ 	011b
EIP 	equ 	100b
STATE	equ	101b

Here are a few other definitions used in my program.I started to represent the registers in binary because i wanted to do complex opcode decoding. I will do that for another version ;)

Registers Initialisation:

		mov		dword ptr [regs.R0_],"livE" 	; Registers are initialized with a Slayer Song Title.
		..	
		mov		dword ptr [regs.R1_],"saH " 	; Evil Has No Boundaries!
		..		
		mov		dword ptr [regs.R2_]," oN "
		..		
		mov		dword ptr [regs.COUNTER_],"nuoB"
		..		
		mov		dword ptr [regs.EIP_],"irad"
		..		
		mov		dword ptr [regs.STATE_],"! se"

At the start of the VM, i first begin to initialise my own registers with the song's title of a thrash metal band. This title was selected because i planned to do real evil things with the Virtual Machine. It isn't as hard as the initial version, but still evil enough to keep that funny string ;-).

There is about 34 Instructions in the Virtual Machine. (I count instructions having different utilisation as unique)

I will present a few instruction handlers to explain the inner working of the Virtual Machine, but not every instruction will be presented here.

Pcode Fectcher:

The first thing the Virtual Machine does after the Register Init is to Fetch the Pcode entry point and jmp to the first Pcode handler.

		movzx	eax, byte ptr [esi]			; ESI is Pcode Entry Point. This code gets the first instruction Prefix.
				
		mov 	edi, dword ptr [eax*4+poffset]		; It uses it with the offset table to find the Pcode family it has to execute.
		
		movzx	eax, byte ptr [esi+1] 		    	; get second byte, use it as an index into last table.
								; The VM now knows what instruction it has to emulate and goes to it.
		
		JMPNEXT						; Emulate a jmp	dword ptr [eax*4+edi] with Exception Handling and Context Manipulation.
								; Jmp to the next Pcode instruction handler

Examples of Instructions implemented inside the Virtual Machine:

Before i start with those examples, i would like to say that a few instructions present in the Virtual Machine weren't used and were left as decoy.Three of them are using Self modifying code. People are reporting that they don't work, but they should. The off by one difference is because the opcode is beeing called from other instruction handlers. Two instructions are modifying one instruction on the fly as they need to execute a particular piece of code. They then restore the instruction state. I am too lazy to check whether those instructions are really bugged or if they didn't use the good parameters. One of the _unused_ instruction HAS a bug, and i am glad some people noticed it. The instruction isn't used therefore, it is just a decoy instruction. The instruction is supposed to be a Virtual BSWAP, but it doesn't save the result of the swaping. Another unused instruction is the INT 3. This instruction allows one to put breakpoint in his Pcode program and trace with his debugger from that instruction. I left this instruction in the final Virtual Machine and im glad some people found it and abused it!.

STOPVM

The first instruction i will present here is a very simple one. It tells to the Virtual Machine to stops and the program will get back to normal x86 assembly program.

@STOPVM:
		pop  	dword ptr fs:[0]			; Im using SEH to jmp from handlers to handlers in the VM.
		add  	esp,4					; Therefore i need to remove the handler installed before i do anything.
		popad						; i restore the registers..
		..
		push	dword ptr [Pret]			; Put the Return Address (to get out of the VM) on the stack.
		..		
		xor	[esp],'HAX0	'			; Decrypt it with a funny string: HAXO(R)
		..		
		ret 						; Get out of the VM.

This instruction is not using any bytecode fetcher because it doesn't need to jmp to another handler. I will now present a real instruction. A Virtual PUSH:

LOAD

@Load:
		pop  dword ptr fs:[0]					
		add  esp,4
		popad

; Same as every handler, remove SEH and restore registers.

		mov	eax,dword ptr [esi+2]				; Get into EAX the first operand of the instruction.
		
		xor	eax,37195411h					; Decrypt it.
		
		push 	eax						; Push it onto the stack.
		
		mov 	eax,0FFFFFF3Fh					; EAX = FFFFFF3Fh
		
		not	eax						; EAX = not(EAX) = C0h
		
		shr	eax,5						; EAX = EAX shr 5 = 6 : This is the instruction length
		
		lea	esi, [esi+eax]					; ESI = Instruction Pointer. Deplace the Instruction Pointer 6 bytes further.
		
		movzx	eax, byte ptr [esi]				; ESI now points to the new instruction to be executed.
		
		mov 	edi, dword ptr [eax*4+poffset]  		; It uses it with the offset table to find the Pcode family it has to execute.
		
		movzx	eax, byte ptr [esi+1] 		    		; get second byte, use it as an index into last table.
									; The VM now knows what instruction it has to emulate and goes to it.
		
		JMPNEXT							; Emulate a jmp	dword ptr [eax*4+edi] with Exception Handling and Context
									; Manipulation.
									; Jmp to the next Pcode instruction handler

As you can see from this little handler, the instruction is 6 bytes long. It takes only one parameter and it is placed 2 bytes after the start of the instruction. (ESI+2). The parameter is encrypted with 37195411h. The decrypted parameter is pushed on the stack and then the Virtual Machine calls the next instruction.

From this, we can say that this instruction is a push. since push is already a x86 instruction, i named my virtual push : LOAD.

One can use it like this: "LOAD number"

VMXOR

 
@VMXORDISPATCHER:
		pop  	dword ptr fs:[0]
		add  	esp,4
		popad

; Same as every handler, remove SEH and restore registers.


		movzx	eax, byte ptr [esi+2] 			; Get the Index Register to acces the Virtual CPU registers.
		
		mov	eax, dword ptr [regs+eax*4] 		; edi = Register value to know wich register is going to be concerned (RO, R1 , R2)
								; EAX = value used by the XOR.
	
		movzx	ecx, byte ptr [esi+3]			; ECX = type of XOR. Byte ptr ? Word Ptr ? or Dword Ptr..
		
		jmp 	dword ptr [xortable+ecx*4]		; Jmp to the good handler accordingly.
		

@VMXORBPTR:  
		
		movzx	ecx, byte ptr [esi+4] 			; Get Index Register for the destination.
		
		mov	ecx, dword ptr [regs+ecx*4] 		; edi = Register value to know wich register is going to be used (RO, R1 , R2)
		
		xor	byte ptr [ecx],al			; XOR BYTE PTR
	
		add	esi,5					; Instruction Length is 5

		movzx	eax, byte ptr [esi]			; ESI now points to the new instruction to be executed.
		
		mov 	edi, dword ptr [eax*4+poffset]  	; It uses it with the offset table to find the Pcode family it has to execute.
		
		movzx	eax, byte ptr [esi+1] 		    	; get second byte, use it as an index into last table.
								; The VM now knows what instruction it has to emulate and goes to it.
		
		JMPNEXT						; Emulate a jmp	dword ptr [eax*4+edi] with Exception Handling and Context Manipulation.
								; Jmp to the next Pcode instruction handler


@VMXORWPTR:
		
		movzx	ecx, byte ptr [esi+4] 			; Get Index Register for the destination
		
		mov	ecx, dword ptr [regs+ecx*4] 		; edi = Register value to know wich register is going to be used (RO, R1 , R2)
		
		xor	word ptr [ecx],ax			; XOR WORD PTR
		
		add	esi,5					; Instruction Length is 5
		
		movzx	eax, byte ptr [esi]			; ESI now points to the new instruction to be executed.
		
		mov 	edi, dword ptr [eax*4+poffset]  	; It uses it with the offset table to find the Pcode family it has to execute.
		
		movzx	eax, byte ptr [esi+1] 		    	; get second byte, use it as an index into last table.
								; The VM now knows what instruction it has to emulate and goes to it.
		
		JMPNEXT						; Emulate a jmp	dword ptr [eax*4+edi] with Exception Handling and Context Manipulation.
								; Jmp to the next Pcode instruction handler

@VMXORDPTR:

		movzx	ecx, byte ptr [esi+4] 			; Get Index Register for the destination
		
		mov	ecx, dword ptr [regs+ecx*4] 		; edi = Register value to know wich register is going to be used (RO, R1 , R2)
				
		xor	dword ptr [ecx],eax			; XOR DWORD PTR
		
		add	esi,5					; Instruction Length is 5
		
		movzx	eax, byte ptr [esi]			; ESI now points to the new instruction to be executed.
		
		mov 	edi, dword ptr [eax*4+poffset]  	; It uses it with the offset table to find the Pcode family it has to execute.
		
		movzx	eax, byte ptr [esi+1] 		    	; get second byte, use it as an index into last table.
								; The VM now knows what instruction it has to emulate and goes to it.
		
		JMPNEXT						; Emulate a jmp	dword ptr [eax*4+edi] with Exception Handling and Context Manipulation.
								; Jmp to the next Pcode instruction handler		

From this piece of code we can learn many things. The XOR instructions are coded with 5 Bytes. It has two parameters.One register has the value used to do the XOR and One Register has a pointer to the location to be xored.It also have a byte saying whether it is a XOR BYTE PTR, a XOR WORD PTR or a XOR DWORD PTR.

This instruction handler is therefore handling Virtual XOR instruction.


How were the virtual instruction used to create a program ?

I will now show how i did to create virtual instruction, because the x86 assembler doesn't know them and will never compile a LOAD or a VMXOR.To do so, i used a very simple way: MACRO. For each instruction, a corresponding macro has been created, and is used to encode the instruction for me. This way i can write a program with my Assembly mnemonics without caring of the opcodes representation.I will now show the 3 Macros used for the examples Virtual Instruction descrived above

STOPVM macro
	db 	02,00
endm

This is the macro for the STOPVM instruction. Usage: STOPVM

Load macro x
	db 	00,00
	dd	x xor 37195411h
endm

This is the macro for the LOAD instruction. Usage: LOAD x

VMXOR macro  reg0,kind,reg1  
	db 01,03,reg0,kind,reg1
endm

This is the macro for the VMXOR instruction. Usage: VMXOR Rx xPTR Rx



P-code Program used in this challenge:

The P-code is my own assembly language, thus IDA doesn't know anything about it. Here is how it looks under a disassembler:

Ok, it doesn't look so good. So now, here is the complete program (copy pasted from my source) i have written with my OWN assembly language. Cool isn't it ? :-)

CODE@:

Pcode1:

	MOVE pcrypt COUNTER
	LOADPTR startpcodecrypted
	RestoreREG R0
	MOVE 'S' R2

decryptpcode:

	VMXOR R2 BPTR R0
	INCR R0
	DECR COUNTER
	BNZ decryptpcode
	
startpcodecrypted:
	
	MOVE 05CC80E31h R1
	APICALL GetCommandLineA
	SCANB " " 255h
	BZ youwishdude
	DLOAD R0 R2 

	ADDREG R2 01D9BDC45h
	ADDREG R1 74519745h		
	SUBREG R2 0AD45DFE2h
	ADDREG R1 0DEADBEEFh	
	ADDREG R2 "hell"
	SUBREG R1 17854165h	
	SUBREG R2 "Awai"
	ADDREG R1 "show"
	ADDREG R2 "its "
	SUBREG R1 " no "	
	ADDREG R2 "driv"
	ADDREG R1 "merc"	
	SUBREG R2 "nuts"
	SUBREG R1 "y!!!"	
	SUBREG R2 "eh?!"
	ANDREG R2 0DFFFFFFFh
	
	LOADREG R2
	LOADREG R1
	CMPQ firstcheckdone
	CLEAR COUNTER
	BZ youwishdude
	
firstcheckdone:

	INCR R0
	ADDREG R0 2
	INCR R0   			
	WLOAD R0 R1 			
	
	LOADREG R1
	
	RESTOREREG R2     	
	LOADREG R0        	
	LOADPTR tricky-98547h 	
	RESTOREREG R0         	
	ADDREG R0 98548h      	
	DECR R0               	
	
	VMXOR R2 WPTR R0
	
	RESTOREREG R0  

	BLOAD R0 R2
	ADDREG R0 2
	
	BLOAD R0 R1
	RADD R2 R1 
	VMCALL sub_check_routine
		
tricky:
	
	ENCRYPTEDCLEAR COUNTER  		; This one get patched at run time!

cracked:	
	LOADREG COUNTER
	LOADPTR congrats
	APICALL printf	
	CleanStack 8 
	BR outout
	
youwishdude:

	LOAD 0
	LOADPTR notgood
	APICALL printf	
	CleanStack 8
outout:	
	STOPVM


sub_check_routine:

	MOVE 'L' R1
	INCR R1 
	INCR R1	
	ADDREG R1 5 
	DECR R1 
	SUBREG R1 4 
	SUBREG R2 'Z'
	CMPREG R1 R2
	BNZ youwishdude

	DECR R0         
	BLOAD R0 R2     
	ADDREG R0 2     
	BLOAD R0 R1     
	RADD R2 R1      
	INCR R2         
	SUBREG R2 4Eh
	
	LOADPTR retdecrypt-0DEADh 	; push ptr to patch
	RESTOREREG R0         		
	ADDREG R0 0DEACh      		
	INCR R0               		
	VMXOR R2 BPTR R0	

	MOVE msgcrypt COUNTER
	LOADPTR goodboy
	RestoreREG R0
	INCR R2

decryptmsg:

	VMXOR R2 BPTR R0
	INCR R0
	DECR COUNTER
	BNZ decryptmsg

retdecrypt:	

	VMRETCRYPTED



DATA@:

notgood		db	"Please Authenticate!",10,13,0


goodboy:
congrats	db	"Welcome...",10,13
		db  	"Exploit for it doesn't matter 1.x Courtesy of Nicolas Brulez",0 
goodboyend:

This little routine is the password protection used in the binary. For more informations about it, i will let you read the submissions. As you can see, the password protection is VERY SHORT. I could have written a very complex algo with hundreds of lines to make it harder to analyse. Also this code is clear of junk. I could also have placed P-code Junk instructions inside the program. The password check was very simple and sadly, some people concentrated on the password rather than the Virtual Machine. Next time i will make it a lot more complex so people has no choice but to analyse the Virtual Machine and Instruction set.

You can compare the original P-code program here with the one inside the submissions to realize that they have done a very good job.

A Few notes regarding the Password Protection

The password is checked using a very simple algorithm, but it is also used to decrypt yet another part of the pcode program. There is a little weakness allowing one to find the correct value without any brute forcing or analysis of the Opcodes:

Here is the encrypted String :

		0x14, 0x26, 0x2F, 0x20, 0x2C, 0x2E, 0x26, 0x6D,
		0x6D, 0x6D, 0x49, 0x4E, 0x06, 0x3B, 0x33, 0x2F,
		0x2C, 0x2A, 0x37, 0x63, 0x25, 0x2C, 0x31, 0x63,
		0x2A, 0x37, 0x63, 0x27, 0x2C, 0x26, 0x30, 0x2D,
		0x64, 0x37, 0x63, 0x2E, 0x22, 0x37, 0x37, 0x26,
		0x31, 0x63, 0x72, 0x6D, 0x3B, 0x63, 0x00, 0x2C,
		0x36, 0x31, 0x37, 0x26, 0x30, 0x3A, 0x63, 0x2C,
		0x25, 0x63, 0x0D, 0x2A, 0x20, 0x2C, 0x2F, 0x22,
		0x30, 0x63, 0x01, 0x31, 0x36, 0x2F, 0x26, 0x39,
		0x43

Everyone knows that a C String ends with a null byte. Therefore, the value used to encrypt this string is 0x43. The key is the last byte of the encrypted string. X xor 0 = X. :-)

The other possible ways to find the good value was to look at the code structure.. We were doing a Call routine, therefore we must have an instruction to do a RET. This instruction is the Virtual RET implemented in the Virtual Machine. From this, we just had to find the opcode of this instruction to compute the key.

3 - Provide a mean to "quickly" analyse this uncommon feature.

With this question, i was expecting a disassembler for my Virtual Machine. A few people sent me fully working disassemblers, so i didn't write yet another one. I invite you once again to have a look at their submissions.One of the author emailed me after the deadline with a working IDA processor module with source code included. This Processor module wasn't used in my judgement because it was sent after the deadline, but it is well worth studying it. It will be uploaded on the honeynet web site shortly after the publication of the Results.

4 - Which tools are the most suited for analysing such binaries, and why?

In my opinion the best tools to analyse such binaries are Interactive Disassemblers or CPU emulators. The disassembler can be used to analyse the code statically, to remove the obfuscations, to decrypt binaries etc. If it offers possibility to write processor module, you can even write a disassembler for the Virtual Machine and thus, do a full static analysis of the whole thing. A CPU emulator can be used to quickly decrypt the code , layers etc. If it can be scripted not to show the obfuscations you have a perfect weapon. I don't like Debuggers because they aren't reliable. I could have easily written a driver to hook debug interupts to decrypt the binary for instance. Debuggers would have been useless and would have rebooted the computer if used.

5 - Identify the purpose (fictitious or not) of the binary.

This binary is waiting for an user to authenticate with a password that is passed to the application through the command line. Once the user has been identified, the binary will print a little message. It looks like a fake exploit. In the real world, it could have been a real exploit protected from prying eyes.

6 - What is the binary waiting from the user? Please detail how you found it.

The binary is waiting for a password through the command line. The password is used to access the real program. To find this password, you have to Reverse Engineer the binary. Decrypt every layers to access the Virtual Machine. This Virtual Machine has a virtual program used to check the password entered. One has to Reverse Engineer the Virtual Machine (or trace it blindly) in order to understand its instruction set. Then it is just a very simple algo using a few easy operations to reverse. I invite you to read submissions for details about the algo itself.

Bonus Question - What techniques or methods can you think of that would make the binary harder to reverse engineer?

This binary has a lot of security flaws that were left on purpose and it has a lot of things needing improvements.

  • Junk Code without pushad/popad
  • P-code Junk code to really drive people tracing the protection nuts: 80% of useless instructions would definitevely drive anyone mad.
  • The encryption has a few weaknesses that were presented in the document. Mainly the static encryption algo and the static size of every layers.
  • Random constants in the first value of the address tables used by the Layers
  • Better BPX detection, it could be greatly improved.
  • The SEH could be used to initialise/decrypt part of the code in order to make sure they won't be nopped out.
  • Random Constants and code for the timing detection would make it harder to bypass with scripts.
  • The protection has been generated by my own tools and it is a bit repetitive. More variations would make automatic removal harder.
  • The Virtual Machine handlers are fairly simples. More code obfuscation (code flow and logic) could be used.
  • More Instructions in the Virtual Machine would have made it longer to analyse.
  • Complex Opcodes encoding would make it quite challenging to Reverse Engineer.
  • Utilisation of Cryptography rather than a simple algo to check for the serial number
  • More Layers and different ones. There are a lot of ways to stop ring 3 debuggers that could have been used to stop anyone trying to debug it.
  • Imports Protection to make sure noone knows the API function used until he meets them in the code.
  • Emulation Macros to emulate simple x86 instructions. With those macros remplacing simple instruction, it would be a lot harder to analyse. one instruction would have a 20 instructions equivalent block of code for instance.

Conclusion

Anti Reverse Engineering Techniques can be used to really slow down the analysis of a binary. Malwares could be using such techniques in a near futur and it is time to get used to it. Even though most of the malwares are programmed by clueless idiots without any programming skill, there is a minority able to write complex code. In the futur we could find exploit binaries on compromised systems that would be protected against Reverse Engineering to hide the vulnerability exploited. Spywares could also use such techniques to hide their activity. This binary had a lot of vulnerabilities, yet it was really challenging , even with a trivial password protection algorithm. The protection has been written within a week (a few hours per day), so with a little more effort, it can be a LOT harder.Finally, I would like to point out that Reverse Engineering isn't a pirate technique and that it is used by the Security Community on a daily basis. Some people in France doesn't seem to agree though..

Acknowledgement

I would like to thank the following people:

  • The Honeynet Project and Lance Spitzner who allowed me to create the challenge.
  • The authors of the submissions for taking the time to look at my binary and write a complete report. Thank you.
  • People at Datarescue for their Excellent tool IDA Pro used extensively while writing this binary.
  • You for reading this document

About the Author

Nicolas Brulez

Chief of Security for Digital River woking on the SoftwarePassport/Armadillo protection system, Nicolas specializes in anti-reverse engineering techniques to defend against software attacks. He has been active in researching viral threats and sharing that research with various anti-virus companies. He regularly writes for the French security magazine MISC and has authored a number of papers on reverse engineering. He currently teaches assembly programming and reverse engineering in French engineering schools.

The author has more than 7 years of Reverse Engineering Experience on Windows Operating Systems and is currently doing Research on Pocket PC devices. He plans to write a Protection system for those devices.


Nicolas Brulez - 0x90(at)rstack(dot)org
Chief of Security - The Armadillo Software Protection System
http://www.siliconrealms.com/armadillo.htm
SpringBoot+Vue+Mysql社区医院信息平台(源码+文档) SpringBoot+Vue+Mysql学生宿舍信息管理系统 WEB端软件项目(源码+文档) 项目包含:①完整源码(后端+管理后台+用户前台);②数据库脚本文件;③万字论文文档;④开题报告;⑤答辩PPT;⑥项目运行视频教程。 项目架构: B/S 架构、MVC 分层设计 技术栈: Java、MySQL、SpringBoot、MyBatis-Plus、Token 权限校验、管理后台 Vue+ElementUI+ECharts、用户前台 Layui+jQuery、Maven 构建。 一、角色设计 学生用户: 注册登录、浏览宿舍信息、查询宿舍安排、在线报修、缴费查询、失物招领、桶装水预订、评论交流、收藏等。 商家用户: 注册登录、桶装水品牌与商品信息维护、预订订单查看与处理。 维修人员: 注册登录、查看待处理报修、接单维修、填写报修处理结果与进度反馈。 管理员: 学生管理、班级管理、宿舍信息管理、宿舍安排管理、报修处理管理、缴费信息管理、卫生检查与文明宿舍管理、失物招领管理、桶装水与商家管理、维修人员管理、公告资讯与轮播图管理等。 二、前台功能模块(学生端) 1. 首页展示:轮播图、宿舍公告、最新动态推荐、常用服务快速入口 2. 宿舍信息模块:宿舍列表、楼栋与房间条件筛选、宿舍详情查看、我的宿舍安排查询 3. 在线报修模块:在线提交宿舍设施报修申请、上传故障描述、查看报修进度与维修处理结果 4. 缴费模块:水电等缴费记录查询、缴费状态查看 5. 卫生检查与文明宿舍:宿舍卫生检查结果公示、文明宿舍展示 6. 失物招领模块:失物信息发布、招领信息发布、详情查看、评论互动 7. 桶装水预订:桶装水品牌与商品浏览、在线预订、我的预订记录查询 8. 假期留宿:假期留校申请提交、申请记录查询 9. 评论与收藏:信息评论、查看他人评论、评论回复互动、收藏感兴趣的内容 10. 个人中心... 立即下载

相关推荐

YOLOv8作物病害检测实战源码

这是一套基于YOLOv8n的农田病虫害目标检测系统,对叶片照片做目标检测,识别健康叶片、褐斑病、白粉病、锈病四类,并给出对应防治建议。全流程纯CPU可跑,配套交付完整Python源码、9章实战教程(md/html/pdf三种格式)、100张自造样本及YOLO标签、训练曲线与混淆矩阵截图、Tkinter界面和答辩PPT。 系统围绕五项功能展开:同一套推理逻辑覆盖图像、视频、摄像头三种来源;图形界面选文件即检测,画面叠彩色框标注病名与置信度,右侧面板同步给出症状与防治措施;检测记录自动写入CSV,统计页一键生成四类病害占比饼图;训练结束自动产出损失与精度曲线;内置防治建议文案库,检测命中即回填面板。 选型用YOLOv8n nano档,参数量约320万、权重约6.2MB,CPU单帧几十毫秒,小样本训练5到8分钟完成。数据由data_gen.py程序化绘制100张叶片图,固定随机种子,重跑分布一致,不依赖公开数据集。依赖全部锁版本,教程第7章整理了19条真实报错及排查方法,第8章给出换模型档、换PlantVillage数据集的迁移路径。 适合做计算机视觉方向课程设计或毕业设计的学生,也适合智慧农业方向想了解检测模型如何落进业务面板的学习者。不需要GPU,不需要系统学过目标检测,教程第3章会按需补齐原理。 使用时先pip install -r requirements.txt装好依赖,download_data.py下载预训练权重,data_gen.py生成样本后train.py训练,完成后用main.py infer批量检测或main.py gui启动界面。

xde反汇编引擎源码

这是用c语言写的经典XDE反汇编引擎源代码 1.写在前面的话                        eXtended (XDE) disassembler engine                        ----------------------------------                                  version 1.02

软件调试的变革 6141

数据挖掘实战:决策树 + 聚类 + 关联规则,零 Key 离线可跑(Python 源码)

数据挖掘实战包。用 Python 标准库实现 CART 风格决策树、K-Means 聚类与 Apriori 关联规则,在虚构「星河便利店」会员表和购物篮上跑通分类、分群与购物篮分析,并给出准确率、轮廓系数、支持度/置信度/提升度。零第三方依赖、零 API Key 即可离线跑通。含 4 个样例、离线冒烟、中文 README(含「使用方法」)。均为作者原创可运行源码。MIT。作者 blog.youkuaiyun.com/mtl1994

LDE反汇编引擎的使用

可以使用该反汇编引擎来计算inline hook时所需要的字节数 // ;****************************************************** // ; LDE64 x64 relocatable (Length Disassembler Engine) for 64 bits plateforms // ; FREEWARE // ; // ; coded by BeatriX // ; beatrix2004(at)free(dot)fr // ; // ; r

qq_41490873的博客 1597

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?.docx

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?

产业园区如何高效对接企业技术需求与高校科研成果?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

vscode 所有的默认配置项

快速打开 使用快捷键 Ctrl+Shift+P ,然后搜索setting 首选项:打开默认设置(json),这个打开的是defaultSettings.json文件 可以在你的默认配置中看到这些,然后自己配置的时候,不懂的地方,可以参考这里 { // 控制在差异编辑器中是否把前导空格或尾随空格的改动显示为差异。 "diffEditor.ignoreTrimWhitespace": tru...

沧笙踏歌的博客 7万+

国央企创新负责人如何快速掌握本地科技资源分布与匹配情况?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

日期选择器:The value/defaultValue of DatePicker or MonthPicker must be a moment object after `antd@2.0`

翻译:DatePicker或MonthPicker的值/defaultValue必须是’ antd@2.0 '后面的moment对象 datepicker只支持moment类型,表单获取数据是string类型引起报错 解决办法 安装moment npm i moment --save-dev 引入moment 使用moment 显示ok ...

couch potato的博客 6521

国央企创新负责人如何实现科技成果的精准识别与高效筛选?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

高校技术转移办公室人员如何系统化管理科技成果信息?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

API-HOOK and ANTI-API-HOOK For Ring3

作者:Anskya时间:2007-01-07,20:56链接:http://bbs.pediy.com/showthread.php?t=37586<<API-HOOKandANTI-API-HOOKForRing3>>转载请保留版权.谢谢Anskya@Gmail.com今天突然看到"堕落天才"仁兄的两篇文章感谢他的的文章和共享精神.谢谢...突然...

weixin_33895604的博客 222

科技中介服务机构如何构建标准化、模块化的智能服务工具?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

Intel指令格式与长度反汇编引擎ADE32分析

标 题: 【翻译】intel指令格式与长度反汇编引擎ADE32分析 作 者: 火影 时 间: 2007-10-31,20:31 链 接: http://bbs.pediy.com/showthread.php?t=54180 我翻译的29A#7的一篇文章: ********************************    ***                          ***

Kendiv's Box 5965

fab-pm-optimizer-python-entry-py39-20261021-v1-1-0

fab-pm-optimizer-python-entry-py39-20261021-v1-1-0

PSIM-三相维也纳VIENNA整流器电压电流双闭环+中点电位平衡SVPWM控制仿真

内容概要:本文档详细介绍了一种基于PSIM软件实现的三相维也纳(VIENNA)整流器仿真模型,重点实现了电压电流双闭环控制与中点电位平衡的SVPWM调制策略。该仿真系统通过精确的控制算法有效提升了整流器的功率因数,降低了输入电流谐波含量,并解决了三电平拓扑中常见的直流侧中点电位偏移问题,具有较高的工程实用价值,适用于高效率、高功率密度的电力电子系统设计与科研验证。文档还整合了丰富的配套资源,涵盖多种电力电子变换器拓扑结构、先进控制算法及典型应用场景,形成了系统化的技术资料体系,有助于深入理解现代整流技术的核心原理与发展动态。; 适合人群:面向从事电力电子、电气工程、新能源变换系统等领域的科研人员、高校研究生以及具备一定仿真基础的工程技术人员。; 使用场景及目标:①深入研究三相VIENNA整流器的工作机理与控制特性;②掌握电压外环、电流内环双闭环控制结构的设计方法;③理解并实现SVPWM调制技术与中点电位平衡控制策略的协同作用;④开展相关控制算法的仿真验证、性能优化与实验对比,为高性能整流装置的研发提供理论支撑和技术原型。; 阅读建议:建议结合文中列出的其他仿真案例(如PWM整流器、LLC谐振变换器、DAB变换器等)进行横向对比学习,充分利用所提供的PSIM、MATLAB/Simulink等多平台仿真资源,重点关注控制逻辑与主电路的耦合关系,通过参数调试与波形分析深化对系统动态响应和稳态性能的理解,强化“理论—建模—仿真—优化”的闭环研究能力。

Hacker Disassembler Engine (HDE) v 0.09

Description:This is small disassembler engine intended to x86-32 code analyse. HDE get length of command, prefixes, ModR/M and SIB bytes, opcode, immediate value, displacement, etc. For example, y

Linhanshi Blog 8603

产业园区运营负责人如何利用数智工具提升园区企业的技术对接效率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

上一篇: 微软ping程序源代码完整版
下一篇: 著名IT公司名字来由
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值