Author Topic: How complicated is the code for Microsoft Windows?  (Read 2392 times)

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: How complicated is the code for Microsoft Windows?
« Reply #30 on: February 08, 2020, 01:00:08 AM »
@nidud,
I have not seen many times (sorry, actually I have never seen  :sad:) such trivial things as ActiveX controls, COM servers or even accessing the WMI functionality, from code produced in ASM. I know some developers have huge libraries supporting their ASM or pseudo-ASM tools (also called pure-MASM), but even with such arsenal nothing comes out.  :sad:

nidud

  • Member
  • *****
  • Posts: 1972
    • https://github.com/nidud/asmc
Re: How complicated is the code for Microsoft Windows?
« Reply #31 on: February 08, 2020, 02:36:03 AM »
So lets make a simple one then and see how it works. This from a sample adding syntax highlighting to a RichEdit control. We may then start with the user side first. The user needs the COM structure and GUID to load the object. The class is called IConfig and basically just read and write a .INI file.

.comdef IConfig : public IUnknown

    read            proc :string_t
    write           proc :string_t
    find            proc :string_t
    create          proc :string_t, :vararg
    getvalue        proc :string_t, :string_t
    delete          proc :string_t

    .ends

The test case loads the DLL and creates a simple config file.
Code: [Select]
include objbase.inc
include tchar.inc
include IConfig.inc
include locals.inc

    .data
    IID_IClassFactory IID _IID_IClassFactory
    IID_IConfig       IID _IID_IConfig
    CLSID_IConfig     IID _CLSID_IConfig

    .code

WinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPTSTR, nShowCmd:SINT

    local config:ptr IConfig
    local class:ptr IClassFactory
    local hr:HRESULT

    .ifd !CoInitialize(0)

        .ifd CoGetClassObject(&CLSID_IConfig, CLSCTX_INPROC_SERVER, 0, &IID_IClassFactory, &class)

            MessageBox(0, "Can't get IClassFactory", "CoGetClassObject error", MB_OK or MB_ICONEXCLAMATION)
        .else

            .ifd class.CreateInstance(0, &IID_IConfig, &config)

                MessageBox(0, "Can't create IConfig object", "CreateInstance error",
                        MB_OK or MB_ICONEXCLAMATION)

            .else

                assume rcx:ptr IConfig
                config.create( "Version" )
                [rcx].create ( "Base=%d.%d", 1, 0 )
                config.create( "Product" )
                [rcx].create ( "Source=IConfig COM component" )
                config.create( "URL" )
                [rcx].create ( "UpdateURL=https://github.com/nidud/asmc/tree/master/source/test/comdll" )
                config.find  ( "Version" )
                [rcx].create ( "Package=%d.%d", 1, 0 )
                config.write ( "test.ini" )
                config.Release()
            .endif
            class.Release()
        .endif
        CoUninitialize()
    .else
        MessageBox(0, "Can't initialize COM", "CoInitialize error", MB_OK or MB_ICONEXCLAMATION)
    .endif
    xor eax,eax
    ret

WinMain endp

    end _tstart

So to the hard part.

; locals.inc
; set the local path and use guidgen.exe to create the two GUIDs below.
; then remove this..
.err <GUIDs not created -- see locals.inc>

dll_path    equ <"D:\\Asmc\\source\\test\\comdll\\IConfig.dll">
dll_clsid   equ <"{09BE7B0C-2275-474D-9405-24AAB891614F}">

DEFINE_GUIDS(IID_IConfig,   "7BF9D277-13A2-4815-B3E9-CDE77C5906C3")
DEFINE_GUIDS(CLSID_IConfig, "09BE7B0C-2275-474D-9405-24AAB891614F")

Install
Code: [Select]
include windows.inc
include stdio.inc
include winreg.inc
include locals.inc

    .code

wmain proc

  local hKey0:HKEY, hKey1:HKEY, hKey2:HKEY, hKey3:HKEY
  local Disposition:int_t, retval:int_t

    mov retval,1

    .if !RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Classes", 0, KEY_WRITE, &hKey0)

        .if !RegOpenKeyEx(hKey0, "CLSID", 0, KEY_ALL_ACCESS, &hKey1)

            .if !RegCreateKeyEx(hKey1, dll_clsid, 0, 0, REG_OPTION_NON_VOLATILE,
                    KEY_WRITE, 0, &hKey2, &Disposition)

                RegSetValueEx(hKey2, 0, 0, REG_SZ, "IConfig COM component", sizeof(DS0003))

                .if !RegCreateKeyEx(hKey2, "InprocServer32", 0, 0, REG_OPTION_NON_VOLATILE,
                        KEY_WRITE, 0, &hKey3, &Disposition)

                    .if !RegSetValueEx(hKey3, 0, 0, REG_SZ, dll_path, sizeof(DS0005))

                        .if !RegSetValueEx(hKey3, "ThreadingModel", 0, REG_SZ,
                                "both", sizeof(DS0007))

                            mov retval,0
                            wprintf("Successfully registered IConfig.dll as a COM component.\n")
                        .endif
                    .endif
                    RegCloseKey(hKey3)
                .endif
                RegCloseKey(hKey2)
            .endif
            RegCloseKey(hKey1)
        .endif
        RegCloseKey(hKey0)
    .endif

    .if ( retval )

        wprintf("Failed to registered IConfig.dll as a COM component.\n")

        .ifd !RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Classes", 0, KEY_WRITE, &hKey0)

            .ifd !RegOpenKeyEx(hKey0, "CLSID", 0, KEY_ALL_ACCESS, &hKey1)

                .ifd !RegCreateKeyEx(hKey1, dll_clsid, 0, 0, REG_OPTION_NON_VOLATILE,
                        KEY_WRITE, 0, &hKey2, &Disposition)

                    RegDeleteKey(hKey2, "InprocServer32")
                    RegCloseKey(hKey2)
                    RegDeleteKey(hKey1, dll_clsid)
                .endif
                RegCloseKey(hKey1)
            .endif
            RegCloseKey(hKey0)
        .endif
    .endif

    exit(retval)

wmain endp

    end wmain

UnInstall
Code: [Select]
include windows.inc
include stdio.inc
include winreg.inc
include locals.inc

    .code

wmain proc

  local hKey0:HKEY, hKey1:HKEY, hKey2:HKEY
  local Disposition:int_t, retval:int_t

    mov retval,1
    .ifd !RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Classes", 0, KEY_WRITE, &hKey0)

        .ifd !RegOpenKeyEx(hKey0, "CLSID", 0, KEY_ALL_ACCESS, &hKey1)

            .ifd !RegCreateKeyEx(hKey1, dll_clsid, 0, 0, REG_OPTION_NON_VOLATILE,
                        KEY_WRITE, 0, &hKey2, &Disposition)

                .ifd !RegDeleteKey(hKey2, "InprocServer32")

                    RegCloseKey(hKey2)
                    .ifd !RegDeleteKey(hKey1, dll_clsid)

                        mov retval,0
                        wprintf("Successfully removed IConfig.dll as a COM component.\n")
                    .endif
                .endif
            .endif
            RegCloseKey(hKey1)
        .endif
        RegCloseKey(hKey0)
    .endif

    .if retval

        wprintf("Failed to remove IConfig.dll as a COM component.\n")
    .endif

    exit(retval)
    ret

wmain endp

    end wmain

The DLL file
Code: [Select]
include objbase.inc
include intrin.inc
include stdio.inc
include IConfig.inc
include locals.inc

.comdef CIClassFactory

    ref_count       dd ?

    QueryInterface  proc :REFIID, :ptr
    AddRef          proc
    Release         proc

    CreateInstance  proc :ptr, :REFIID, :ptr
    LockServer      proc :BOOL

    .ends


_I_BASE             equ 0x01
_I_SECTION          equ 0x02
_I_ENTRY            equ 0x04
_I_COMMENT          equ 0x08


.comdef CIConfig

    count           uint_t ?
    flags           uint_t ?
    name            string_t ?
    union
      value         string_t ?
      list          ptr_t ?
    ends
    next            ptr_t ?
    Compare         proc local :string_t, :string_t

    QueryInterface  proc :REFIID, :ptr
    AddRef          proc
    Release         proc

    read            proc :string_t
    write           proc :string_t
    find            proc :string_t
    create          proc :string_t, :vararg
    getvalue        proc :string_t, :string_t
    delete          proc :string_t
    new             proc
    unlink          proc :ptr_t

    .ends


    .data

    IID_IUnknown    IID _IID_IUnknown
    IID_IConfig     IID _IID_IConfig
    CLSID_IConfig   IID _CLSID_IConfig
    IID_IClassFactory IID _IID_IClassFactory

    ClassFactoryVtbl CIClassFactoryVtbl { \
        CIClassFactory_QueryInterface,
        CIClassFactory_AddRef,
        CIClassFactory_Release,
        CIClassFactory_CreateInstance,
        CIClassFactory_LockServer }

    ConfigVtbl CIConfigVtbl { \
        CIConfig_QueryInterface,
        CIConfig_AddRef,
        CIConfig_Release,
        CIConfig_read,
        CIConfig_write,
        CIConfig_find,
        CIConfig_create,
        CIConfig_getvalue,
        CIConfig_delete,
        CIConfig_new,
        CIConfig_unlink }

    Config CIConfig { ConfigVtbl, 0, }
    ClassFactory CIClassFactory { ClassFactoryVtbl, 0 }
    OutstandingObjects int_t 0
    LockCount int_t 0

    .code

    assume rcx:ptr CIConfig

CIConfig::QueryInterface proc riid:LPIID, ppv:ptr ptr

    mov rax,[rdx]
    mov rdx,[rdx+8]
    .if rax == qword ptr IID_IUnknown
        cmp rdx,qword ptr IID_IUnknown[8]
    .endif
    .ifnz
        .if rax == qword ptr IID_IConfig
            cmp rdx,qword ptr IID_IConfig[8]
        .endif
    .endif
    .ifz
        mov [r8],rcx
        [rcx].AddRef()
        mov eax,NOERROR
    .else
        xor eax,eax
        mov [r8],rax
        mov eax,E_NOINTERFACE
    .endif
    ret

CIConfig::QueryInterface endp


CIConfig::AddRef proc

    inc [rcx].count
    mov eax,[rcx].count
    ret

CIConfig::AddRef endp


    assume rbx:ptr CIConfig

CIConfig::Release proc uses rbx

    dec [rcx].count
    .ifz

        mov rbx,rcx
        _InterlockedDecrement(&OutstandingObjects)

        .while rbx
            free([rbx].name)
            .if [rbx].flags & _I_SECTION
                mov rcx,[rbx].list
                .if rcx
                    [rcx].Release()
                .endif
            .endif
            mov rcx,rbx
            mov rbx,[rbx].next
            free(rcx)
        .endw

        xor eax,eax
    .else
        mov eax,[rcx].count
    .endif
    ret

CIConfig::Release endp


TruncateString proc private string:LPSTR

    .repeat

        mov al,[rcx]
        inc rcx

        .continue(0) .if al == ' '
        .continue(0) .if al == 9
        .continue(0) .if al == 10
        .continue(0) .if al == 13
    .until 1
    dec rcx

    mov rdx,rcx
    xor eax,eax
    .while [rcx] != al

        inc rcx
    .endw

    .repeat

        .break .if rcx <= rdx

        dec rcx
        mov al,[rcx]
        mov [rcx],ah

        .continue(0) .if al == ' '
        .continue(0) .if al == 9
        .continue(0) .if al == 10
        .continue(0) .if al == 13

        mov [rcx],al
        inc rcx

    .until 1

    sub rcx,rdx
    mov rax,rcx
    ret

TruncateString endp


CIConfig::read proc uses rsi rdi rbx r12 file:string_t

  local fp:LPFILE, buffer[256]:sbyte

    mov r12,rcx
    mov rbx,rcx
    mov rcx,rdx
    .if fopen(rcx, "rt")

        .for ( rdi = rax, rsi = &buffer : fgets(rsi, 256, rdi) : )

            .continue .ifd !TruncateString(rsi)

            .if byte ptr [rdx] == '['

                .if strchr(rsi, ']')

                    mov byte ptr [rax],0
                    .break .if ![rbx].create(&[rsi+1])
                    mov rbx,rax
                .endif
            .else
                [rbx].create(rdx)
            .endif
        .endf
        fclose(rdi)
        mov rax,r12
    .endif
    ret

CIConfig::read endp


CIConfig::write proc uses rsi rdi rbx file:string_t

    mov rbx,rcx
    mov rcx,rdx
    .if fopen(rcx, "wt")

        .for ( rdi = rax : rbx : rbx = [rbx].next )

            .if [rbx].flags & _I_SECTION

                fprintf(rdi, "\n[%s]\n", [rbx].name)
            .endif

            .for ( rsi=[rbx].list : rsi : rsi=[rsi].CIConfig.next )

                mov r8,[rsi].CIConfig.name
                mov eax,[rsi].CIConfig.flags
                .if eax & _I_ENTRY

                    fprintf(rdi, "%s=%s\n", r8, [rsi].CIConfig.value)
                .elseif eax & _I_COMMENT
                    fprintf(rdi, "%s\n", r8)
                .else
                    fprintf(rdi, ";%s\n", r8)
                .endif
            .endf
        .endf
        fclose(rdi)
        mov eax,1
    .endif
    ret
CIConfig::write endp


CIConfig::find proc uses rsi rdi rbx string:string_t

    xor edi,edi
    mov rsi,rdx
    xor eax,eax

    .while rcx

        .if ( [rcx].flags & ( _I_SECTION or _I_ENTRY ) )

            mov rbx,rcx
            .if !( [rbx].Compare([rbx].name, rsi) )

                mov rdx,rdi
                mov rcx,rbx
                mov rax,rbx
                .break
            .endif
            xor eax,eax
            mov rcx,rbx
        .endif
        mov rdi,rcx
        mov rcx,[rcx].next
    .endw
    ret

CIConfig::find endp


CIConfig::getvalue proc uses rsi rdi rbx Section:string_t, Entry:string_t

    mov rbx,rcx
    mov rsi,rdx
    mov rdi,r8

    .if [rbx].find(rdx)

        mov rax,[rcx].list
        .if rax

            [rax].CIConfig.find(rdi)
        .endif
    .endif
    ret

CIConfig::getvalue endp


CIConfig::create proc uses rsi rdi rbx r12 format:string_t, argptr:vararg

 local string[256]:sbyte

    mov rbx,rcx
    lea rdi,string

    .repeat

        .break .ifd !vsprintf(rdi, rdx, &argptr)
        .break .ifd !TruncateString(rdi)

        xor esi,esi
        mov rdi,rdx

        .if byte ptr [rdx] != ';'

            mov al,'='
            repnz scasb
            .ifz
                mov rsi,rdi
                mov byte ptr [rdi-1],0
                mov rdi,rdx
                TruncateString(rsi)
                TruncateString(rdi)
                mov rdx,rdi
            .endif
            mov rdi,rdx
        .endif

        .break .if [rbx].find(rdx)
        .break .if ![rbx].new()

        mov ecx,_I_SECTION
        .if esi
            mov byte ptr [rsi-1],'='
            mov ecx,_I_ENTRY
        .elseif byte ptr [rdi] == ';'
            mov ecx,_I_COMMENT
        .endif
        mov [rax].CIConfig.flags,ecx

        mov r12,rdi
        mov rdi,rax

        .break .if !malloc( &[ strlen( r12 ) + 1 ] )
        mov [rdi].CIConfig.name,strcpy( rax, r12 )

        mov rax,[rbx].next
        .if esi && [rbx].flags & _I_SECTION

            mov rax,[rbx].list
            .if !rax

                mov [rbx].list,rdi
                xor rbx,rbx
            .endif
        .endif

        .while rax

            mov rbx,rax
            mov rax,[rbx].next
        .endw

        .if rbx

            mov [rbx].next,rdi
        .endif

        mov rbx,rdi
        .if esi

            .if strchr([rbx].name, '=')

                mov byte ptr [rax],0
                inc rax
                mov [rbx].value,rax
            .endif
        .endif

        mov rax,rdi
    .until 1
    mov rcx,rax
    ret

CIConfig::create endp


CIConfig::new proc

    .return .if !malloc(CIConfig)

    mov rcx,rax
    mov [rcx].count,1
    lea rax,ConfigVtbl
    mov [rcx].lpVtbl,rax
    xor eax,eax
    mov [rcx].next,rax
    mov [rcx].name,rax
    mov [rcx].list,rax
    mov [rcx].flags,eax
    lea rax,strcmp
    mov [rcx].Compare,rax
    mov rax,rcx
    ret

CIConfig::new endp


CIConfig::delete proc SectionName:string_t

    .if [rcx].find(rdx)

        .if rdx

            [rax].CIConfig.unlink(rdx)
        .endif
    .endif
    ret

CIConfig::delete endp


CIConfig::unlink proc Parent:ptr

    mov rax,[rcx].next
    mov [rdx].CIConfig.next,rax
    xor eax,eax
    mov [rcx].next,rax
    [rcx].Release()
    ret

CIConfig::unlink endp


    assume rbx:nothing
    assume rcx:ptr CIClassFactory

CIClassFactory::QueryInterface proc riid:LPIID, ppv:ptr ptr

    mov rax,[rdx]
    mov rdx,[rdx+8]
    .if rax == qword ptr IID_IUnknown
        cmp rdx,qword ptr IID_IUnknown[8]
    .endif
    .ifnz
        .if rax == qword ptr IID_IClassFactory
            cmp rdx,qword ptr IID_IClassFactory[8]
        .endif
    .endif
    .ifz

        mov [r8],rcx
        [rcx].AddRef()
        mov eax,NOERROR

    .else

        xor eax,eax
        mov [r8],rax
        mov eax,E_NOINTERFACE
    .endif
    ret

CIClassFactory::QueryInterface endp


CIClassFactory::AddRef proc

    _InterlockedIncrement(&OutstandingObjects)
    ret

CIClassFactory::AddRef endp


CIClassFactory::Release proc

    _InterlockedDecrement(&OutstandingObjects)
    ret

CIClassFactory::Release endp


CIClassFactory::CreateInstance proc uses rdi rdi Unknown:ptr IUnknown, riid:REFIID, ppv:ptr

    xor eax,eax
    mov [r9],rax

    .if rdx

        mov eax,CLASS_E_NOAGGREGATION
    .else

        .if !malloc(sizeof(CIConfig))

            mov eax,E_OUTOFMEMORY
        .else

            assume rsi:ptr CIConfig
            mov rsi,rax
            lea rax,ConfigVtbl
            mov [rsi].lpVtbl,rax
            mov [rsi].count,1
            xor eax,eax
            mov [rsi].next,rax
            mov [rsi].name,rax
            mov [rsi].list,rax
            mov [rsi].flags,eax
            lea rax,strcmp
            mov [rsi].Compare,rax
            mov edi,[rsi].QueryInterface(riid, ppv)
            [rsi].Release()
            .if !edi

                _InterlockedIncrement(&OutstandingObjects)
            .endif
            assume rsi:nothing
            mov eax,edi
        .endif
    .endif
    ret

CIClassFactory::CreateInstance endp


CIClassFactory::LockServer proc flock:BOOL

    .if edx
        _InterlockedIncrement(&LockCount)
    .else
        _InterlockedDecrement(&LockCount)
    .endif

    mov eax,NOERROR
    ret

CIClassFactory::LockServer endp


    option win64:rsp nosave noauto

DllGetClassObject proc export frame rclsid:REFCLSID, riid:REFIID, ppv:ptr ptr

    mov rax,[rcx]
    mov rcx,[rcx+8]
    .if rax == qword ptr CLSID_IConfig
        cmp rcx,qword ptr CLSID_IConfig[8]
    .endif
    .ifz
        CIClassFactory::QueryInterface(&ClassFactory, rdx, r8)
    .else
        xor eax,eax
        mov [r8],rax
        mov eax,CLASS_E_CLASSNOTAVAILABLE
    .endif
    ret

DllGetClassObject endp


DllCanUnloadNow proc export

    mov eax,OutstandingObjects
    or  eax,LockCount
    .if eax
        mov eax,S_FALSE
    .else
        mov eax,S_OK
    .endif
    ret

DllCanUnloadNow endp


DllMain proc frame hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID

    .if edx == DLL_PROCESS_ATTACH

        DisableThreadLibraryCalls(rcx)
    .endif
    mov eax,TRUE
    ret

DllMain endp

    end DllMain

makefile

test.exe:
    asmc64 -nologo -pe -gui $*.asm
    asmc64 -nologo -pe -ws install.asm
    asmc64 -nologo -pe -ws uninstall.asm
    asmc64 -nologo -D__GUID__ IConfig.asm
    linkw option quiet system dll_64 file IConfig.obj
    install.exe
    $@
    pause

This should give the output:
Successfully registered IConfig.dll as a COM component.
Then uninstall:
Successfully removed IConfig.dll as a COM component.
Again:
Failed to remove IConfig.dll as a COM component.

There should be a test.ini file created on success.

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: How complicated is the code for Microsoft Windows?
« Reply #32 on: February 08, 2020, 03:43:30 AM »
It looks like a complete example, but I suspect it will not entice the Linux crowd to move to Windows :rolleyes:, in particular, with the argument that it is easy to do COM from ASM.

BTW, you can register the COM server in HKEY_CURRENT_USER, so there is no need to launch as administrator.

caballero

  • Member
  • *****
  • Posts: 1450
  • Matrix - Noah
    • abre ojos ensamblador
Re: How complicated is the code for Microsoft Windows?
« Reply #33 on: February 08, 2020, 04:12:43 AM »
Interesting  :thumbsup:
The logic of the error is hidden among the most unexpected lines of the program

Vortex

  • Member
  • *****
  • Posts: 2266
Re: How complicated is the code for Microsoft Windows?
« Reply #34 on: February 08, 2020, 04:41:05 AM »
Hi morgot,

Quote
But in Linux all things are simpler, that in Windows. This is fact.

It depends. While I always tell that Linux is very good as a server operating system, some configuration files can be very complicated.