In the good ol' times everybody trusted anybody else.
We all downloaded programs from folks we never heard of and we did not even know, that those programs might pose a risk to our computers.
But one day Malware was invented. First as Viruses, later then Worms and the recent threats dujour are Trojans (actually Trojan Horses).
Once we were kicked out of our Shangri-La, someone got the idea to somehow protect software, by issuing a so-called "digital certificate".
The idea is that there is an honorable authority, handing out a certificate, once they have vetted the person or company,
which requested such a certificate to be issued on their name. The are no samaritans, they charge for their work. And it has become a big business.
When you have purchased such a precious gem and then digitally signed your binaries with this code-signing certificate issued on your name,
then your customers can look them up by doing a right-click on the file in the Windows Explorer, whether this binary file (EXE, DLL, OCX) has
your certificate and they will can be sure, that this software is from you or your company and that it is in exactly in that state,
as it have left your office. Not tampered by a virus, nor manipulated a bad person with a HEX-editor, having changed some internal data.
More on code-signing on Wikipedia.
However, no user will ever check your program for integrity! Most people do not even know, what that is (pun intended).
The challenge now is, to include this integrity check into our own products and, in case a fraudulent manipulation has been found,
give out a warning to the user, to prohibit further harm.
On 01. March 2019 we did a ClarionLive!-Webinar on this topic.
You can load the entire webinar here for watching.
The example, which got built during this webinar, was based on source by fellow programmer Thomas Glomb with some additional input by Graham Dawson.
it will be described on this page. There will also be a downloadable example for Clarion 10 at the bottom of this page.
Your first step is to open Global Properties -> Embeds and insert this into your Global Map:
module('wintrust') tgWinVerifyTrust( long, *string, *group), long, pascal, raw, proc, name( 'WinVerifyTrust') end module('win32 api') tgMultiByteToWideChar( long, long, *cstring, long, *cstring, long), long, pascal, raw, name( 'MultiByteToWideChar') tgGetLastError(), long, pascal, name('GetLastError') endThis will include the function WinVerifyTrust(), MultiByteToWideChar() and GetLastError() into your EXE. They are provided by MS-Windows in the system-file WinTrust.DLL and in the general Win32-API. This WinTrust.DLL holds some more functions, which are not needed for this purpose. So Thomas has stripped off the rest, made it a LIB on its own. You find this WinTrust.LIB in the attached ZIP. You also can make your own by using Clarions LibMaker.
The next step is to put the following into an embed like in After Globale Includes:
! ####################################################################################### ! # # ! # EQUATEs for WinTrustVerify SignatureCheck() => LOC:ResultSignatureCheck # ! # The returning GROUP will hold the contents of these Equates # ! # RetSignCheck:nStatus # ! # RetSignCheck:cStatus # ! # # ! ####################################################################################### ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * EQ:Signature_trusted_OK EQUATE(1) EQ:Signature_No_Signature EQUATE(0) ! when TRUST_E_NOSIGNATURE EQ:Signature_NOT_trusted EQUATE(-1) ! when TRUST_E_SUBJECT_NOT_TRUSTED EQ:Signature_Provider_unkown EQUATE(-2) ! when TRUST_E_PROVIDER_UNKNOWN EQ:Signature_Action_unknown EQUATE(-3) ! when TRUST_E_ACTION_UNKNOWN EQ:Signature_Subject_Form_unknown EQUATE(-4) ! when TRUST_E_SUBJECT_FORM_UNKNOWN EQ:Signature_Invalid_Signature EQUATE(-5) ! when some other reason, why ever! EQ:Signature_WideChar_Conversion_Problem EQUATE(-6) ! ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
What we now need is the PROCEDURE, that does the verification check.
In this procedure, we add the following to the DATA SECTION
lresult long, auto lcStatus STRING(200) l:ReturnGROUP GROUP,PRE(RETGROUP) nStatus LONG cStatus STRING(200) END WVTGuid group Data1 long( 0AAC56Bh) Data2 short( 0CD44h) Data3 short( 011D0h) Data4 byte, dim( 8) Data4Str string( 8), over( Data4) ! The contents will be assigned in the code itself end WTDgroup group cbStruct long( 52) !dword cbStruct = sizeof(WinTrustData) pPolicyCallbackData long( 0) !lpvoid pPolicyCallbackData = NULL pSIPClientData long( 0) !lpvoid pSIPClientData = NULL dwUIChoice long( 2) !dword dwUIChoice = WTD_UI_NONE 2 fdwRevocationChecks long( 0) !dword fdwRevocationChecks = WTD_REVOKE_NONE 0 dwUnionChoice long( 1) !dword dwUnionChoice = WTD_CHOICE_FILE 1 pFile long( 0) !union {*pFile *pCatalog *pBlob *pSgnr *pCert} dwStateAction long( 1) !dword dwStateAction = WTD_STATEACTION_VERIFY 1 hWVTStateData long( 0) !handle hWVTStateData = NULL pwszURLReference long( 0) !wchar *pwszURLReference = NULL dwProvFlags long( 0) !dwProvFlags ? dwUIContext long( 0) !dwUIContext = 0 pSignatureSettings long( 0) !*pSignatureSettings end FDgroup group cbStruct long( 16) !cbStruct = sizeof(WINTRUST_FILE_INFO) pcwszFilePath long( 0) !pcwszFilePath = pwszSourceFile; hFile long( 0) !hFile = NULL; pgKnownSubject long( 0) !pgKnownSubject = NULL; end cs cstring( 256), auto ws cstring( 512), auto
Now we come to the center of it all - the verification itself.
Add this snippet to Processed Code
cs = clip( command( '0')) ! cs holds the name of the actual program we will compile and run. ! Because this verification has been outsourced to its own procedure, you may ! pass a parameter, holding the name of another binary file to be tested. ! You then should be able to verify all your DLLs in the project, too. if tgMultiByteToWideChar( 0, 1, cs, -1, ws, size( ws)) <> 0 WVTguid:Data4Str= '<08Ch,0C2h,000h,0C0h,04Fh,0C2h,095h,0EEh>' FDgroup.pcwszFilePath= address( ws) WTDgroup.pFile= address( FDgroup) ! lresult= tgWinVerifyTrust( -1, WVTguid, WTDgroup) case tgWinVerifyTrust( -1, WVTguid, WTDgroup) of 0 ! ERROR_SUCCESS - signature Ok ! lresult = EQ:Signature_trusted_OK lcStatus = 'EQ:Signature_trusted_OK' of 800B0100h ! TRUST_E_NOSIGNATURE case tgGetLastError() ! GetLastError() of 800B0001h ! TRUST_E_PROVIDER_UNKNOWN lresult = EQ:Signature_Provider_unkown lcStatus = 'EQ:Signature_Provider_unkown' of 800B0003h ! TRUST_E_SUBJECT_FORM_UNKNOWN lresult = EQ:Signature_Subject_Form_unknown lcStatus = 'EQ:Signature_Subject_Form_unknown' of 800B0100h ! TRUST_E_NOSIGNATURE lresult = EQ:Signature_No_Signature lcStatus = 'EQ:Signature_No_Signature' else lresult = EQ:Signature_Invalid_Signature lcStatus = 'EQ:Signature_Invalid_Signature - but who knows why' end else lresult = EQ:Signature_Invalid_Signature lcStatus = 'EQ:Signature_Invalid_Signature for whatever reason' end WTDgroup.dwStateAction= 2 tgWinVerifyTrust( -1, WVTguid, WTDgroup) else lresult = EQ:Signature_WideChar_Conversion_Problem lcStatus = 'EQ:Signature_WideChar_Conversion_Problem' end RETGROUP:nStatus = lresult RETGROUP:cStatus = lcStatus ! dbgview('RETGROUP:nStatus = ' & RETGROUP:nStatus) ! dbgview('RETGROUP:cStatus = ' & RETGROUP:cStatus) RETURN (l:ReturnGROUP)You are free to change the text, which gets returned to the calling procedure, into something meaningful for your users.
Now we have all stuff laid out, we will put it together to work.
All we have to do now to put these data somewhere into our DATA SECTION
LOC:ResultSignatureCheck GROUP,PRE(RetSignCheck) ! nStatus LONG ! cStatus STRING(200) ! ENDand this somewhere close to the begin of the MAIN-procedure, like in ThisWindow.Init
LOC:ResultSignatureCheck = CheckSignature() ! LOC:ResultSignatureCheck is the GROUP, that holds the results we got back: ! RetSignCheck:nStatus ! RetSignCheck:cStatus IF RetSignCheck:nStatus < 0 THEN ! MESSAGE(some Warning) => RETURN Level:Break MESSAGE('A problem with the integrity of this program was found!|' & | 'This program has been modified, probably by a virus.||' & | 'Please inform your IT-department!||' & | 'Result: ' & RetSignCheck:nStatus & ' - ' & CLIP(RetSignCheck:cStatus) & '||' & | 'The program will now be closed.', 'W A R N I N G!', | ICON:Hand) RETURN Level:Fatal ! Level:Break END
Use at your own risk!
In a following webinar we will make this source into a template. So stay tuned!
Join us on ClarionLive!, its free!
During the Webinars #503 the code was presented on ClarionLive!
Thats all! Add salt and pepper as you like....
You can download the sample source (Clarion 10).
Jane Fleming converted this procedure into a class, made a fully functional demo and allowed to download it from here. Thank you, Jane!
During the Webinars #512 and #515 Mike Hanson poured Janes code into a template, adding even more comfortable feature inoto it.
You can now add a bunch of binary files into a list and each one will get checked at startup for its signature to be valid.
As a bonus, you can replace the default messages to the enduser in your own words. You also can use variables for runtime translation.
There is an installer for this template in the making, so come back soon or have a look into the Third-Party-Newsgroup on the
SV-NewsServer.
Meanwhile we turned the source into a Template. You can find it here.
Please be aware, that this ZIP does not contain an installer. It holds all files in a matching folder structure for you to copy by hand to the
according directories and apply the tamplate manually to the IDE. Sorry, but I have no idea, how to teach the installer to register the template for you.
Please note, that the template was made and tested with Clarion 11.
The HEX-Editor XVI32 we used during the webinar can be found here.
This page was made on 06.03.2019, updated 25. 05. 2019, updated 30. 09. 2020, updated again 23. 03. 2021.
Here you find some more samples