News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

IMAGE_OPTIONAL_HEADER

Started by jj2007, June 18, 2017, 07:48:25 PM

Previous topic - Next topic

jj2007

I suppose many of us know this structure:
IMAGE_OPTIONAL_HEADER32 STRUCT
  Magic                         WORD       ?
  MajorLinkerVersion            BYTE       ?
  MinorLinkerVersion            BYTE       ?
  SizeOfCode                    DWORD      ?
  SizeOfInitializedData         DWORD      ?
  SizeOfUninitializedData       DWORD      ?
  AddressOfEntryPoint           DWORD      ?
  BaseOfCode                    DWORD      ?
  BaseOfData                    DWORD      ?
  ImageBase                     DWORD      ?
...


Below a screenshot from PeView. What I don't understand are the entries AddressOfEntryPoint and BaseOfCode.
When looking at the exe through the eyes of Olly, it appears that the codebase is 401000h, not 1A6000h.
The entry point is at 5B6410h, not 1B6410h.
Adding ImageBase+SizeOfInitializedData, I get 401000h, the point where my code really starts.

What am I missing?

aw27

Quote from: jj2007 on June 18, 2017, 07:48:25 PM
What am I missing?

May be someone will be more comprehensive, I will only add these points:
1) ImageBase may not be where the image actually loads in memory. Odds are it will not be with ALSR.
2) BaseOfCode is beginning of code section relative to ImageBase
3) AddressOfEntryPoint - Starting address when execution begins.

Gary Nebbett, (remember his book?) posted a nice application of these concepts here


jj2007

Thanks, José and Erol :icon14:

It turns out that codebase is "wrong" because the exe is UPX-ed. So it really starts at the exotic address 5B6410h, but only to decompress itself. Afterwards, codebase is 401000h, i.e. the usual 400000h plus the initialised data. Hmpffff ::)

aw27

Quote from: jj2007 on June 18, 2017, 08:30:08 PM
Thanks, José and Erol :icon14:

It turns out that codebase is "wrong" because the exe is UPX-ed. So it really starts at the exotic address 5B6410h, but only to decompress itself. Afterwards, codebase is 401000h, i.e. the usual 400000h plus the initialised data. Hmpffff ::)
Not usual unless you disabled ALSR in the Registry or the program was built without dynamic codebase. In your case, probably UPX always creates space at that address to uncompress.

jj2007

Right. Now I stumbled over an issue that gives me a real headache:
Dump of file ImageBaseBuglink614.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (i386)
               3 number of sections
        5946727A time date stamp Sun Jun 18 14:30:50 2017
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             10F characteristics
                   Relocations stripped
                   Executable
                   Line numbers stripped
                   Symbols stripped
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic #
            5.12 linker version
            5400 size of code
           B3600 size of initialized data
               0 size of uninitialized data
            1000 RVA of entry point
            1000 base of code
            7000 base of data
          400000 image base
            1000 section alignment
             200 file alignment
            4.00 operating system version
            0.00 image version
            4.00 subsystem version
               0 Win32 version
           BB000 size of image
             400 size of headers
               0 checksum
               3 subsystem (Windows CUI)
               0 DLL characteristics
          100000 size of stack reserve
            1000 size of stack commit
          100000 size of heap reserve
            1000 size of heap commit


Everything is perfect except B3600 size of initialized data. SizeOfImage is 765952 dec, also ridiculously high.
Can't be. The exe is 27648 bytes only ::)

Now this is cute: After a lot of googling I found a book written by a certain Kaspersky...

Note the "including senseless" ::)

aw27

Size of initialized data is frequently wrong. The file will load with any value you put there. Sizeofimage is a consequence.

QuoteKaspersky
The virus guy  ;)

jj2007

Quote from: aw27 on June 18, 2017, 11:54:57 PM
Size of initialized data is frequently wrong. The file will load with any value you put there. Sizeofimage is a consequence.
Yep. Fortunately some other values are valid ;)

And I managed to translate them into a new macro, @MbRet, see attachment:

include \masm32\MasmBasic\MasmBasic.inc      ; download
CodeBelow PROTO :DWORD, :DWORD, :DWORD

CodeAbove proc uses edi hwnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL MyLocVar:DWORD, LocBuffer[260]:BYTE
  ClearLocals
  push esi            ; esi to stack
  lea esi, LocBuffer
  nop
  push eax           ; ouch, we are not good at counting pushes and pops!
  pop esi              ; restore the stack
  @MbRet
CodeAbove endp

Has2Args proc uses esi edi ebx mode, arg2
; no locals here
  push eax
  mov ecx, mode
  pop eax
  push eax            ; one push too much
  @MbRet
Has2Args endp

Has3Args proc uses ecx mode, arg2, arg3
  push eax
  mov ecx, mode
  pop eax
  pop eax            ; one pop too much
  @MbRet
Has3Args endp

  Init
  invoke CodeAbove, 11111111h, 22222222h, 33333333h, 44444444h
  invoke CodeBelow, 11111111h, 22222222h, 33333333h
  invoke Has3Args, 12345678h, 22222222h, 33333333h
  invoke Has2Args, 12345678h, 22222222h
  Inkey "ok?"
  Exit

CodeBelow proc uses esi edi ebx ecx edx eax arg1, arg2, arg3
LOCAL MyLocVar:DWORD
LOCAL LocBuffer[40h]:BYTE
ClearLocals                ; optional: zero-initialise local variables (not with uses in proc head)
  lea esi, LocBuffer      ; whatever
  pop eax                   ; ouch, again we are not good at counting pushes and pops!
  @MbRet
CodeBelow endp
EndOfCode

Output:
stack error in line 11, (push-pop)=-1
stack error in line 43, (push-pop)=1
line 27: retaddr 12345678 is above code segment


@MbRet generates just an ordinary ret for usedeb=0. So for testing, set usedeb=1 and see what happens. I have applied it to RichMasm, over 130 rets are now active observers of stack corruption, at least in my debug version :P

Warning: Do not use for Windows callback functions. It won't crash, but no warnings either. Own procedures without a stack frame will issue a warning and crash directly afterwards, as expected.

aw27

May be I should have checked better, probably is part of the MasmBasic already, but I could not find the new macro.  :shock:

jj2007

Yes it is, version 19 June. If you are really curious, I grant you an exclusive personal license to put an int 3 before the @MbRet in the source attached above :P

aw27

Quote from: jj2007 on June 19, 2017, 05:17:31 PM
I grant you an exclusive personal license to put an int 3 before the @MbRet in the source attached above :P
Oh, thank you I owe you a beer. :badgrin:

jj2007

Red wine, please :biggrin:

Note that you need a console. RichMasm auto-detects a console program, but if you have a message loop etc, you can force it with OPT_Susy Console somewhere in the source.

If the push count is higher than the pop count, you may see this output:
line 1573: retaddr 0018FE48 is a local address (too many pushes?), below code segment

If the pop count is higher than the push count, you will probably see the first argument that you passed to the proc, in hex notation:
line 1573: retaddr 00000005 is below code segment

Finally, for checking a large source,
- make a backup
- load it in RichMasm
- press Ctrl F
- check the Case and FW boxes (=full word)
- type ret**endp (this finds all occurrences of ret <crlf> endp)
- warning: if you have the habit of using ret 3*DWORD, it won't work; retn 3*DWORD will be OK because of the FW
- hit Alt R (like Replace)
- type @MbRet in the replace box
- select the matches you want to replace
- put the cursor in the replace box and hit Return

Afterwards, try if it still builds (it should). Then put useMbRet=1 near the top of the source (this activates the macro), and run your program. It is a runtime check, so only procs that are actually in use may trigger error messages.

Go and play with an extra push or pop and an int 3 before the @MbRet ;)

Remember that with useMbRet=0, no extra code will be generated - just the usual ret before the endp.

aw27