Jump to content
Sign in to follow this  
joakim

DllCall - imagehlp.dll - MapFileAndCheckSum

Recommended Posts

joakim

Hi Autoiters.

I want to calculate the checksum of a PE file with the function MapFileAndCheckSum found in imagehlp.dll. But I am stuck and always get returned a "1" which indicates a file open failure. This is what I've got so far;

Dim $outHeaderSum
Dim $outCheckSum
Dim $input = '"' & @ScriptDir & "\1.exe" & '"'
;Dim $input = @ScriptDir & "\1.exe"
;Dim $input = "1.exe"

_MapFileAndCheckSum($input)

MsgBox( 0, "HeaderSum:", $outHeaderSum)
MsgBox( 0, "CheckSum:", $outCheckSum)

Func _MapFileAndCheckSum($input)
    Local $aResult
    $aResult = DllCall("imagehlp.dll", "long" , "MapFileAndCheckSum" , "str" ,$input , "dword" ,$outHeaderSum, "dword" ,$outCheckSum)
    If @error Then Return SetError(@error, 0, 0)
    MsgBox( 0, "$aResult[0] returned:", $aResult[0])
    Return $aResult[0]
EndFunc

What am I doing wrong? Do I need to use the function CheckSumMappedFile instead?

Joakim

Share this post


Link to post
Share on other sites
hawky358

Since $outHeaderSum and $outCheckSum are pointers (outputs) you need to define them as such for Autoit to use them.

$outHeaderSum = DLLStructCreate("dword") ;Here you create a structure for a pointer to write into 
$outCheckSum = DLLStructCreate("dword") 
Dim $input = '' & @ScriptDir & "\1.xls" & ''
;Dim $input = @ScriptDir & "\1.exe"
;Dim $input = "1.exe"

_MapFileAndCheckSum($input)

MsgBox( 0, "HeaderSum:", $outHeaderSum)
MsgBox( 0, "CheckSum:", $outCheckSum)

Func _MapFileAndCheckSum($input)
    Local $aResult
    $aResult = DllCall("imagehlp.dll", "long" , "MapFileAndCheckSum" , "str" ,$input , "ptr" ,DllStructGetPtr($outHeaderSum), "ptr" ,DllStructGetPtr($outCheckSum)) ; NOTE THAT THE TYPES CHANGED TO ptr
    If @error Then Return SetError(@error, 0, 0)
    MsgBox( 0, "$aResult[0] returned:", $aResult[0])
    $outHeaderSum = DllStructGetData($outHeaderSum, 1)  ;<Here you read out of your structure again
    $outCheckSum = DllStructGetData($outCheckSum,1) 
    Return $aResult
EndFunc

Check out the help for DLLStructCreate, DllStructGetPtr and DllStructGetData.

I didn't check the .dll functions so I'm not sure what the checksum returns, maybe it needs to be processed further.

Edit:

Oh I forgot to mention that you need to take out the starting and trailing " from the file path

Edited by hawky358

Share this post


Link to post
Share on other sites
joakim

Thank you very much. It now works.

$outHeaderSum is found at hex offset 150 in any pe file, whereas $outCheckSum is the actual checksum of a pe file. It is returned in hex in reverse order.

Btw, do you happen to know how to write arbitrary values (like the checksum) to a specific offset inside a binary file (instead of rewriting the whole file)?

Joakim

Share this post


Link to post
Share on other sites
joakim

Here is the source of my command line checksum fixer for exe and dll files;

If $cmdline[0] = 0 Then
    MsgBox( 0, "Hey you!!!", "This is command line tool - get me a file name as input.")
    Exit
EndIf
If $cmdline[0] <> 1 Then 
    MsgBox( 0, "Hey you!!!", "I only take 1 parameter (a file name).")
    Exit
EndIf
$input = $cmdline[1]
$struct_HeaderSum = "dword"
$struct_CheckSum = "dword"
$outHeaderSum = DLLStructCreate($struct_HeaderSum)
$outCheckSum = DLLStructCreate($struct_CheckSum) 

_MapFileAndCheckSum($input)

If Hex($outHeaderSum) = Hex($outCheckSum) Then
    MsgBox( 0, "Checksum Correct", "Nothing to do - exiting..", 2)
    Exit
EndIf
;Setting the correct checksum in the right format
$1 = StringMid(Hex($outCheckSum), 7, 2)
$2 = StringMid(Hex($outCheckSum), 5, 2)
$3 = StringMid(Hex($outCheckSum), 3, 2)
$4 = StringMid(Hex($outCheckSum), 1, 2)
$rev_checksum = $1 & $2 & $3 & $4
;Get signature at offset 150 and prepare replacement
$openinput = FileOpen($input, 16)
FileSetPos($openinput, Dec(150), 0)
$readinput = FileRead($openinput)
$search = StringMid($readinput, 3, 32)
$replace = $rev_checksum & StringMid($search, 9, 24)
FileFlush($openinput)
FileClose($openinput)
$openinput = FileOpen($input, 16)
$readinput = FileRead($openinput)
$content = StringReplace($readinput,$search,$replace)
;Write file with correct checksum
$inputlen = StringLen($input)
$inputcut = StringMid($input, 1, $inputlen - 4)
$newfile = $inputcut & ".bin"
$open_new = FileOpen($newfile, 18)
FileWrite($open_new,$content)
FileClose($open_new)
FileClose($openinput)
;Backup old file
$inputlen = StringLen($input)
$inputcut = StringMid($input, 1, $inputlen - 4)
FileMove($input, $inputcut & ".bak", 1)
$outputlen = StringLen($newfile)
$outputcut = StringMid($newfile, 1, $outputlen - 4)
FileMove($newfile, $outputcut & ".exe", 1)

Func _MapFileAndCheckSum($input)
    Local $aResult
    $aResult = DllCall("imagehlp.dll", "long" , "MapFileAndCheckSum" , "str" ,$input , "ptr" ,DllStructGetPtr($outHeaderSum), "ptr" ,DllStructGetPtr($outCheckSum))
    If @error Then Return SetError(@error, 0, 0)
    If $aResult[0] <> 0 Then 
        MsgBox( 0, "Error Occured", "$aResult[0] returned: " & $aResult[0])
        Exit
    EndIf
    $outHeaderSum = DllStructGetData($outHeaderSum, 1)
    $outCheckSum = DllStructGetData($outCheckSum,1) 
    Return $aResult
EndFunc

Nothing fancy and there probably should have been more error checking.

Joakim

Share this post


Link to post
Share on other sites
JFX

$outHeaderSum is found at hex offset 150 in any pe file

Hi Joakim,

It's not on the same adress in any PE file. This should helps reading the right adress.

Func _PECheckSumAdress($PEfile)
    If FileExists($PEfile) Then
        $File = FileOpen($PEfile, 16)
        $var = FileRead($File, 0x3f + 1)
        FileClose($File)
        $Adress =  Dec(_ReverseHex(StringRight($var, 4 * 2))) + Dec(58); Checksum Adress Is a 4byte value at adress 0x3C
        Return $Adress
    EndIf
EndFunc

Func _ReverseHex($Input)
    Local $Output = ''
    IF Round(stringlen($Input) / 2,0) <> stringlen($Input) / 2 Then ; String must be a multiple of 2, if not add a Zero to beginning
        $Input = '0' & $Input
    EndIf
    For $i = 2 to StringLen($Input)  Step 2
        $Output = $Output & StringRight($Input,2)
        $Input = StringTrimRight($Input,2)
    Next
    Return $Output
EndFunc

Share this post


Link to post
Share on other sites
joakim

I thought the offsets of the components of IMAGE_OPTIONAL_HEADER was somewhat fixed. Do you have an example of when the offset of checksum would be different? What do you mean by address of 0x3c?

It's too late for me to dig into it now. Looking more to it tomorrow.

Btw, the code I posted works on my 32-bit Intel (could that be the difference?).

Joakim

Share this post


Link to post
Share on other sites
JFX

Sorry for my bad description, as example take imagex.exe for win 7 waik is checksum is located on offset 138 hexadecimal

On address 0x3C you have the offset of the PE Header in little-endian format (_ReverseHex)

Going 88 bytes (+ Dec(58)) further from that point you have the right address of the pe checksum

Share this post


Link to post
Share on other sites
joakim

@JFX

You are right. Thanks.

Maybe I assumed that both the size and the offset were fixed, although just one of them are fixed. I will now assume that the pointer to the pe header are fixed and always at 0x3c.

When I am back from vacation I will update the code here as well as the patcher for winload.exe (load alternative SYSTEM hive).

Joakim

Share this post


Link to post
Share on other sites
joakim

I just managed to squeese in some time before the holiday, to modify the srcipt;

If $cmdline[0] = 0 Then
    MsgBox( 0, "Hey you!!!", "This is command line tool - get me a file name as input.")
    Exit
EndIf
If $cmdline[0] <> 1 Then 
    MsgBox( 0, "Hey you!!!", "I only take 1 parameter (a file name).")
    Exit
EndIf
$input = $cmdline[1]
$struct_HeaderSum = "dword"
$struct_CheckSum = "dword"
$outHeaderSum = DLLStructCreate($struct_HeaderSum)
$outCheckSum = DLLStructCreate($struct_CheckSum) 

_MapFileAndCheckSum($input)

If Hex($outHeaderSum) = Hex($outCheckSum) Then
    MsgBox( 0, "Checksum Correct", "Nothing to do - exiting..", 2)
    Exit
EndIf
;Setting the correct checksum in the right format
$1 = StringMid(Hex($outCheckSum), 7, 2)
$2 = StringMid(Hex($outCheckSum), 5, 2)
$3 = StringMid(Hex($outCheckSum), 3, 2)
$4 = StringMid(Hex($outCheckSum), 1, 2)
$rev_checksum = Binary('0x' & $1 & $2 & $3 & $4)
MsgBox( 0, "", $rev_checksum)
;Detect PE header and correct checksum
Dim $PEfile, $AdressDec
_PECheckSumAdress($input)
$openinput = FileOpen($input, 16)
$Adress = $AdressDec
FileFlush($openinput)
FileClose($openinput)
$openinput = FileOpen($input, 16)
$inputlen = StringLen($input)
$inputcut = StringMid($input, 1, $inputlen - 4)
$newfile = $inputcut & ".bin"
;1
$open_new = FileOpen($newfile, 18)
$write1 = FileRead($openinput, $Adress)
FileWrite($open_new,$write1)
;2
$write2 = $rev_checksum
FileWrite($open_new,$write2)
FileClose($open_new)
;3
$open_new = FileOpen($newfile, 17)
$part2 = FileGetSize($input) - ($Adress + 4)
FileSetPos($openinput, $Adress + 4, 0)
$write3 = FileRead($openinput, $part2)
FileWrite($open_new,$write3)
FileClose($open_new)
FileFlush($openinput)
FileClose($openinput)

Dim $renamedout, $renamedin
Dim $extin = "bak"
Dim $extout = "exe"
_inprename($input, $extin)
_outprename($newfile, $extout)

Func _outprename($newfile, $extout)
    Local $outputlen = StringLen($newfile)
    Local $outputcut = StringMid($newfile, 1, $outputlen - 3)
    Local $renamedout = $outputcut & $extout
    FileMove($newfile, $renamedout, 1)
EndFunc

Func _inprename($input, $extin)
    Local $inputlen = StringLen($input)
    Local $inputcut = StringMid($input, 1, $inputlen - 3)
    Local $renamedin = $inputcut & $extin
    FileMove($input, $renamedin, 1)
EndFunc

Func _MapFileAndCheckSum($input)
    Local $aResult
    $aResult = DllCall("imagehlp.dll", "long" , "MapFileAndCheckSum" , "str" ,$input , "ptr" ,DllStructGetPtr($outHeaderSum), "ptr" ,DllStructGetPtr($outCheckSum))
    If @error Then Return SetError(@error, 0, 0)
    If $aResult[0] <> 0 Then 
        MsgBox( 0, "Error Occured", "$aResult[0] returned: " & $aResult[0])
        Exit
    EndIf
    $outHeaderSum = DllStructGetData($outHeaderSum, 1)
    $outCheckSum = DllStructGetData($outCheckSum,1) 
    Return $aResult
EndFunc

Func _PECheckSumAdress($PEfile)
    If FileExists($PEfile) Then
        $File = FileOpen($PEfile, 16)
        $var = FileRead($File, 0x3f + 1)
        FileClose($File)
        $AdressDec =  Dec(_ReverseHex(StringRight($var, 4 * 2))) + Dec(58); Checksum Adress Is a 4byte value at adress 0x3C
        Return $AdressDec
    EndIf
EndFunc

Func _ReverseHex($Input)
    Local $Output = ''
    IF Round(stringlen($Input) / 2,0) <> stringlen($Input) / 2 Then ; String must be a multiple of 2, if not add a Zero to beginning
        $Input = '0' & $Input
    EndIf
    For $i = 2 to StringLen($Input)  Step 2
        $Output = $Output & StringRight($Input,2)
        $Input = StringTrimRight($Input,2)
    Next
    Return $Output
EndFunc

I changed it a bit and did the reassembly of the final binary in 3 stages. First part is up the offset of the checksum. Then in the second part the new checksum is added after the first part. Then finally the third part, starting after the checksum and to the end of file, is added after the second part.

I suppose a simpler search/replace routine would be better, but could not figure out a working way.

Joakim

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.