windowsassemblyx86-64kernel32

Windows kernel32.dll only assembly draw pixel


I've been looking on stack overflow but couldn't seem to find anything

What commands and functions specifically from kernel32.dll on windows 64 bit can draw a single pixel on the screen, preferably with no user32.dll windows?


Solution

  • It turned out I was wrong in my comments.

    win32k.sys does create new syscalls. Retroactively speaking, it makes sense.

    win32k.sys handles the DirectComposition engine, it's kind of the Desktop and Windows manager of Windows:

    DirectComposition architecture

    The DirectComposition API is exposed to Win32 programs through COM which makes it quite difficult to work with without Visual Studio because we'd need the full IDL definition and, in assembly, make the offsets in the vtables manually.
    It is also quite abstract, so it'd be a pain to reverse-engineering the syscalls used and then use them.

    However, GDI, the legacy Windows' graphic API, is still supported, of course (it's emulated by/integrated with DirectComposition).
    This API is exposed through the exported functions of a few DLLs and rather than working in terms of composable surfaces it works in terms of Windows and Device Contexts (objects that allows one to draw).
    This makes it easy to reverse-engineering it and use it in assembly.

    The closest thing to draw a pixel on screen is drawing a pixel on the desktop device context (since the desktop is a fullscreen window).
    With the Win32 API we'd do that with two functions:

    Without the Win32 API but only with direct system calls it could be problematic because it's not a granted that these operations exist on the lower-level interface.
    For example, this interface could expose a shared memory area where to write the pixel, with an undocumented format.
    Analogously, the device context could be a Win32 abstraction.

    But luckily this is not the case, both GetDC and SetPixel have their own syscall.
    This is being very lucky and probably derives from the history of win32k.sys (which was much more coupled with the Win32 API, actually being the Win32 API, IIRC).

    I reverse-engineered those two Win32 API to get the syscall numbers and how they are used (both numbers are found in the second link in this answer).
    While GetDC is just a wrapper for the syscall call, SetPixel does some format conversion of the color.
    In the example given below, this conversion is omitted and if needed is up to you (you may not be able to write the colors you want without it).

    I've made an example program that draws a black 256x256 rectangle in the upper left corner of the screen.

    Black rectangle

    Some notes:

    There are no external PE dependencies:

    No dependencies

    DEFAULT REL
    
    GLOBAL main
    
    %define SYS_GetDC                 100ah
    %define SYS_SetPixel              10afh
    
    
    SECTION .text
    
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    ;
    ;Get the HDC of a window
    ;
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    
    ;arg0 (rcx) = HWND
    GetDC:
      mov r10, rcx
      mov eax, SYS_GetDC
      syscall
      ret
    
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    ;
    ;Set a pixel in an HDC
    ;
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    
    SetPixel:
      mov r10, rcx
      mov eax, SYS_SetPixel
      syscall
      ret
    
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    ;
    ;Main
    ;
    ;.    .     .     .     .     .     .     .     .     .     .     .     .     .
    ; . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -   . -
    
    main:
    
      ;Get the desktop HDC
    
      xor ecx, ecx
      call GetDC
      test eax, eax
      jz .abort
    
      ;Write a 256x256 black square
      mov ebx, eax          ;HDC into rbx (which is preserved)
      
      xor esi, esi          ;esi = x = 0    
      
    .x_loop:  
      xor edi, edi          ;edi = y = 0
      
    .y_loop: 
      mov ecx, ebx      ;HDC
      mov edx, edi      ;Y
      mov r8, rsi       ;X
      xor r9, r9        ;Color (black, beware of the correct format!)
      call SetPixel
    
      inc edi           ;y++
      cmp edi, 100h
      jb .y_loop        ;while (y < 256)
     
      inc esi           ;x++
      cmp esi,  100h    
      jb .x_loop        ;while (x < 256)
    
    
      ;In Windows we can return from main
      ret
    
    
    
    
      ;Here something went wrong
      ;We must do some eye-catching action, like invoking the debugger
    
    .abort:
      ;This will trigger a debug popup
      int3