Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C

Attacking the Stack

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
4 Aug 2018CPOL6 min read 9.4K   81   10  
Beware the abandoned stack frame, for it can be plundered at will.

Introduction

When I wrote the demonstration application to accompany sprintf_s: Speed Bumps Ahead, I noticed that the stack frames that lay below the frame that belonged to the active function remained intact until the the next time that a function was called, and that portions could survive for much longer, even outlinving the active function. The research that went into that article also paved the way for being able to read registeers by way of inline asssembly instructions, and to stash their values anywhere that I wanted, including out parameters of the active function.

Background

My renewed interest in that discovery was prompted by recent reading about how the Spectre and Melteown CPU flaws work. It occurred to me that if another process can peek at a look-aside table, then abandoned stack frames aren't safe, either. Though I am not sufficiently familiar with interprocess communication to demonstrate such an exploit, I intend to show how a routine that becomes part of the same process can do so, with potentially devastating consquences for data that was supposed to be secure.

Using the code

The archive contains the following directories.

  1. PilferMe is the source code of the program, along with the master copies of the shell script and response files required to put it through its paces, which include all permutations of operating parameters.
  2. Debug is the debug build of the program, along with a shell script and the ten response files required to run it. The script and response files are identical to those in the Release directory, and are copied into it by a post-build script.
  3. Release is the release build of the program, along with a shell script and the ten response files required to run it. The script and response files are identical to those in the Debug directory, and are copied into it by a post-build script.
  4. Util contains the utility program used by the shell script to suspend its termination so that you can view or copy the output, along with the support DLLs required by the main program and that utility. As well, there is a link from which you can obtain Microsoft Visual C++ 2015 Redistributable Update 3 RC, which you will need if you don't already have it.

The simplest way to run the script is by displaying the Release directory in the Windows File Explorer, then double-clicking or otherwise selecting PilferMe_UnitTests.CMD to run it.

  • When launched, the script executes PilferMe.exe, and puts it through one of the 10 scenarios that I identified. Since PilferMe_UnitTests.CMD feeds the inputs from a set of response files, all ten scenarios run "hands free" until the final prompt, which comes courtesy of WWPause.exe, and keeps the window open for your inspection and copying.
  • Due to a shortcoming of the design and implementation of WWPause.exe, you cannot use the spiffy new Ctrl-A and Ctrl-C keyboard accelerators that arrived with recent improvements in Windos 10 to copy the contents of the window into the clipboard. However, the old way of copying text from a console window that uses Alt-Spacebar followed by Ctrl-A, then the Enter key works just fine, and is how I captured the text that appears in the listing below.

Listing 1, shown below, is the output displayed on my machine from a recent run.

---------------------------------------------------------------------
Info: C:\Users\DAG\Documents\Articles_2018\Pilfering_Old_Stack_Frames\PilferMe\Release\PilferMe_UnitTests.CMD, version 2018/07/23 03:00 Begin
---------------------------------------------------------------------


-------------------------------------------------------------------
Add the utilities directory to the system PATH list.
-------------------------------------------------------------------

Added to PATH = C:\Users\DAG\Documents\Articles_2018\Pilfering_Old_Stack_Frames\PilferMe\Release\..\utl


-------------------------------------------------------------------
Case 01: Play by the rules.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:03:57 Central Daylight Time


The caller insists on playing by the rules by decrypting the message.


The caller insists on leaving the plaintext buffer exposed.

A command line argument covered the cheat/honest flag.
Since the Cheat flag is OFF, the Scrub flag is moot.

Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x0075f9b0

    achPlainText                    = 0x0075b9ac

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

52 68 2d 8c e7 14 42 d1 d7 ce c4 8f ed 72 8f 2b df f0 2c fe 40 5f 13 d9 28 86 1f ae a9 f6 e0 89 8c de 73 bd e4 11 c7 88 e2 6c 83 a0 1a 75 30 8b e2 95 8b 7e 63 8e 0c 19 54 d2 e9 56 b3 73 f0 ef f6 89 11 77 30 66 01 47 17 56 51 5a 34 c1 46 d5 c7 cf f8 df 36 08 8a f6 38 5a 41 47 03 ed d6 20 32 0d 15 b6 56 72 b9 c7 6a bd 3b fa 34 76 ee 60 ff 27 0e 79 98 cf d9 92 b2 b4 3b 1e 5e d4 8b 0d 13 7a 7d 08 dc 13 b2 18 cb 7c c8 0a dd 6b 15 fb c8 c2 cb 14 d4 d0 0b 6e 49 0b ca da 84 0d dd 57 8d b0

Character array achPlainText is intact in the stack frame.
Message digest of original, per CreateEncryptedMessage, and decrypted copy, per DecryptString_P6C, are identical.

Message text is as follows:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Done!
<*>


Status code = 0x0001 (1 decimal)
Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 02: Play by the rules AND ovwrwrite the plaintext.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:03:58 Central Daylight Time


The caller insists on playing by the rules by decrypting the message.


The caller instructed that the plaintext should be overwritten.

A command line argument covered the cheat/honest flag.
Since the Cheat flag is OFF, the Scrub flag is moot.

Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x0057fd3c

    achPlainText                    = 0x0057bd38

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

a2 ae db 63 e7 ed 19 a1 26 b2 68 88 37 51 b9 26 1e 9c a7 3e ae 13 09 52 41 e5 92 ab b7 6f b0 ac dc 91 55 9e f2 02 8d 5e 96 6c 6b 72 05 6b fb 96 72 d9 9a 5b ab 16 4a 2f 00 ab 0d f1 f6 75 40 58 56 c8 62 64 84 26 39 5c fe 54 97 32 50 c9 92 4b 07 bb 18 8e 7f 84 6f 45 f7 eb c2 0d 03 c8 1d 24 4d 28 e8 ff a7 a2 85 a1 42 b5 c7 b2 a5 02 18 ba 8a 6d f3 da 52 e9 8f 0a cc 37 84 f6 32 6f 02 9e 61 42 e1 90 2b 63 83 06 2c e4 59 6c 32 a5 1e ed fb 97 b6 2a f6 7c e8 16 7e 2b 8e 73 16 12 07 2a fc 4e

The leak has been plugged by overwriting character array achPlainText
with ASCII NULL characters.
Message digest of original, per CreateEncryptedMessage, and decrypted copy, per DecryptString_P6C, are identical.

Message text is as follows:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Done!
<*>


Status code = 0x0001 (1 decimal)
Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 03: Attempt to cheat BUT ovwrwrite the plaintext.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:03:59 Central Daylight Time


The caller granted permission to cheat by harvesting the plaintext from the working storage
that function CreateEncryptedMessage abandoned.


The caller instructed that the plaintext should be overwritten.

A command line argument covered the cheat/honest flag.
A command line argument covered the scrub/leave flag.

Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x00f6fc24

    achPlainText                    = 0x00f6bc20

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

a2 ae a7 46 e7 ed 7d 33 26 b2 f3 c2 37 51 e7 1b 1e 9c 8b cf ae 13 72 f9 41 e5 5e b7 b7 6f cc a1 30 ba aa 9f 10 d0 51 71 05 9c 85 2f a1 ee 68 8e 1b 93 68 5a 4b 0d e8 a0 9e b4 51 8b ac 01 d4 42 cd 1a 71 ce 01 2f 09 fd 11 c4 3a f9 64 2b 0f 89 e3 df a2 0c 62 dd eb d9 2c 13 ed 4b 1c f8 a0 77 96 b8 7d 55 a1 29 19 d9 d5 61 96 2d 2f 54 23 69 aa 90 d5 16 af ba 57 13 0f d9 6b d2 46 12 d1 38 59 9a ea 87 27 5f 42 1b 4c 92 4e 1b f9 69 38 b2 71 33 ca 55 34 7b 5a 47 e2 e2 49 97 9d a0 97 97 ab f2

The leak has been plugged by overwriting character array achPlainText
with ASCII NULL characters.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x00f6fc48

    intPlainTextLength              = 0x00f6fc24 (16186404 decimal)
    lpszPlainText                   = 0x00f6bc20

    lpszPlainText:

CreateEncryptedMessage ran in secure mode and scrubbed the plaintext.



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 04: Attempt to cheat AND leave the plaintext exposed.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:00 Central Daylight Time


The caller granted permission to cheat by harvesting the plaintext from the working storage
that function CreateEncryptedMessage abandoned.


The caller insists on leaving the plaintext buffer exposed.

A command line argument covered the cheat/honest flag.
A command line argument covered the scrub/leave flag.

Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x00f8f724

    achPlainText                    = 0x00f8b720

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

f2 f4 61 d3 e7 c6 9e b3 76 95 58 39 81 2f 7c 75 5d 47 1b d8 1c c8 dd f7 59 44 65 89 c6 e8 10 d3 7e 2f 11 20 f2 f4 4d a1 ec e9 f2 60 41 70 f5 56 c1 1d 45 99 8a 19 7a 35 3e 28 c8 12 55 3d 11 13 20 05 ce 7b e0 61 b6 f8 48 c1 df 35 ca 3d 3c 0b 9a 61 27 db 11 c3 d5 d5 10 bb 82 ad 49 d3 eb 75 4c 71 ba ef b3 6d b3 fe 52 9f ed b0 07 3e 80 81 17 55 e2 cd 1f c7 47 0b 2d bb 0c b0 b1 12 5b 9d 00 33 63 96 d2 32 67 07 00 92 b6 29 af 69 b8 b6 9b 9e 17 d1 b4 0d 74 b9 fb 31 47 8d c8 dc c1 64 8b 5b

Character array achPlainText is intact in the stack frame.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x00f8f748

    intPlainTextLength              = 0x00f8f724 (16316196 decimal)
    lpszPlainText                   = 0x00f8b720

    lpszPlainText:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 05: Attempt to cheat AND leave the plaintext exposed,
         but prompt for the second option, responding with NO.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:01 Central Daylight Time


The caller granted permission to cheat by harvesting the plaintext from the working storage
that function CreateEncryptedMessage abandoned.

A command line argument covered the cheat/honest flag.
Scrub the plaintext output buffer or leave it intact?
Enter Y to scrub it or N to leave it exposed. >>
    Response from user = n


Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x010ffd58

    achPlainText                    = 0x010fbd54

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

42 3b e0 21 e8 9f 4f 42 c6 78 19 cc cb 0d 43 d0 9c f2 93 1e 89 7c 41 42 72 a3 87 79 d4 61 21 3e 8e e2 1c 7e 6a b6 c9 f4 4a 0b e4 a4 52 c1 4a 17 c1 65 b0 8d be 4a 4a a5 94 01 47 5d 2f 66 0c b8 7c 9b 18 2e 6c 34 5b c2 39 62 b1 c9 73 28 b3 8e ed b1 78 1f f0 34 db e8 49 25 58 1c 64 05 90 ae 2a 56 38 4d 3d 25 47 74 c3 45 b6 fd 2a 2b bf 2a 9f a9 ad b5 d8 a8 2b c9 1f 48 c4 57 9d a7 8c ac 2a 29 9d a9 3b 09 14 21 67 04 24 47 2b 99 fc fa 0d b1 64 56 72 f6 f4 d8 41 d6 70 0c 42 a2 0a d6 74 b6

Character array achPlainText is intact in the stack frame.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x010ffd7c

    intPlainTextLength              = 0x010ffd58 (17825112 decimal)
    lpszPlainText                   = 0x010fbd54

    lpszPlainText:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 06: Attempt to cheat AND overwrite the plaintext,
         but prompt for the second option, responding with YES.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:02 Central Daylight Time


The caller granted permission to cheat by harvesting the plaintext from the working storage
that function CreateEncryptedMessage abandoned.

A command line argument covered the cheat/honest flag.
Scrub the plaintext output buffer or leave it intact?
Enter Y to scrub it or N to leave it exposed. >>
    Response from user = y


Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x0057f9d4

    achPlainText                    = 0x0057b9d0

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

92 81 bb 0e e8 78 b2 f5 16 5c ca 9e 15 ec ed e1 da 9d 89 82 f7 30 8b ae 8b 02 32 01 e3 da 09 b4 d7 b5 86 1e 28 27 f5 be 59 05 61 65 de f6 f3 67 91 3e 01 51 94 24 71 5d 0d 9c 0a 3b 0e 95 9a 1e 6f 19 a2 3f 97 ce 73 02 19 89 63 7b ee bf eb e5 64 0a 42 4a 99 f8 b7 c7 fe 66 e9 b0 a5 bf 76 9b cc b6 fc 2e 8a a9 1f dc b2 89 25 55 d0 91 fd eb 5a 3f b6 43 15 1d 91 5f 2c a1 93 c5 e9 f0 36 14 5a 2e be 72 3e b4 5a c2 8d b6 76 11 1c a8 a7 d6 3c a4 0f 0d 84 bf 65 4d 40 7c 50 2b 21 be ab cf 82 d5

The leak has been plugged by overwriting character array achPlainText
with ASCII NULL characters.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x0057f9f8

    intPlainTextLength              = 0x0057f9d4 (5765588 decimal)
    lpszPlainText                   = 0x0057b9d0

    lpszPlainText:

CreateEncryptedMessage ran in secure mode and scrubbed the plaintext.



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 07: Play by the rules in responde to a single command line
         argument. There should be no prompt.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:03 Central Daylight Time


The caller insists on playing by the rules by decrypting the message.

A command line argument covered the cheat/honest flag.
Since the Cheat flag is OFF, the Scrub flag is moot.

Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x00effcd8

    achPlainText                    = 0x00efbcd4

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

e2 c7 a9 bc e8 51 d3 61 66 3f 82 63 5f ca db 6f 19 49 c8 3f 65 e5 45 56 a3 61 23 8a f1 53 1a 31 c3 90 ba 70 bc a8 ca 7e 61 11 e8 e7 91 9a 2e 95 ab d8 f5 94 e2 e1 39 d9 bd 2d 3b ea 64 56 f0 46 7a 3a 66 84 be 08 09 cf ec 08 42 ff 1e a6 46 01 84 28 b1 fe ac 44 0c 30 6d 8c ae 62 42 85 38 d2 d5 13 ef 1f b4 ae af 80 e6 9d 0b d7 db fc 75 80 a4 2b 6e ad c1 cc 52 7c fd 42 12 e1 f0 12 22 85 7e f0 0c 07 af b0 a7 cf d2 dc 10 3f 1d 2b e3 fd 9e 31 b7 bb 0a e4 02 3e 9d 2b e3 a3 3b d5 48 9c ff 32

Character array achPlainText is intact in the stack frame.
Message digest of original, per CreateEncryptedMessage, and decrypted copy, per DecryptString_P6C, are identical.

Message text is as follows:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Done!
<*>


Status code = 0x0001 (1 decimal)
Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 08: Play by the rules in response to a prompt. The command line
         argument list is empty.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:04 Central Daylight Time


The command line was input bare (without arguments).
Prompting for selection:

OK to cheat? Enter Y to cheat, or N to play by the rules. >>
    Response from user = n


Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x0053fe68

    achPlainText                    = 0x0053be64

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

e2 c7 b2 1f e8 51 00 f7 66 3f 6b 8a 5f ca 92 c9 19 49 ca 11 65 e5 b2 cb a3 61 4f 7a f1 53 64 b5 11 87 48 14 7c 13 27 b2 84 58 00 c4 1b 1f 39 22 7e f4 7c 70 40 fe 1a e9 d3 17 6b e1 58 0c 89 f7 4a 30 35 05 31 5f 81 db b3 73 3e 16 06 3d a6 16 66 04 2f 71 6c ed 0a 09 a3 57 c1 8d c2 7f 4e de da a9 9e 97 9b 15 3b 9d e5 27 3f 62 3c 83 13 71 84 0d a3 14 6d b4 8e 7e 77 25 eb 4f f8 53 71 95 99 05 42 37 9e 27 58 f7 fa d2 d6 3d f5 26 5c c6 6a ac 39 8a f5 33 91 12 98 53 78 57 bc 3d ee 66 17 e3

Character array achPlainText is intact in the stack frame.
Message digest of original, per CreateEncryptedMessage, and decrypted copy, per DecryptString_P6C, are identical.

Message text is as follows:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Done!
<*>


Status code = 0x0001 (1 decimal)
Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 09: Permit cheating, but prevent it by overwriting the
         plaintext. All options are responses to prompts.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:05 Central Daylight Time


The command line was input bare (without arguments).
Prompting for selection:

OK to cheat? Enter Y to cheat, or N to play by the rules. >>
    Response from user = y

Scrub the plaintext output buffer or leave it intact?
Enter Y to scrub it or N to leave it exposed. >>
    Response from user = y


Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x013ff888

    achPlainText                    = 0x013fb884

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

32 0e d5 d3 e9 2a e9 a2 b6 22 e3 62 a9 a8 e5 bc 58 f4 99 40 d2 99 69 17 bc c0 23 98 ff cc 35 ff 79 64 9a 75 4c 9e bd c9 a5 c2 01 ce fa 2e b5 c5 00 0b bc 7e 18 68 25 f2 fe 8e 6d c5 7e a3 11 01 f7 cb 4c 0b 06 0d b3 de 9f 69 a3 17 95 2a 13 38 17 09 c7 24 a8 bb 92 8f cb 3c 4e 4b d1 b1 85 25 63 db 2b 37 b6 f9 19 fb b0 74 b8 d4 45 38 51 f2 d5 4f ff 20 f1 04 c5 f2 5b a6 8b 71 25 f2 82 86 ae de 2b f6 78 55 17 63 04 8f 90 bb 8a 37 34 d1 a9 cb ee f2 a4 8d 33 d4 0f 15 aa 76 46 13 f2 00 ef 32

The leak has been plugged by overwriting character array achPlainText
with ASCII NULL characters.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x013ff8ac

    intPlainTextLength              = 0x013ff888 (20969608 decimal)
    lpszPlainText                   = 0x013fb884

    lpszPlainText:

CreateEncryptedMessage ran in secure mode and scrubbed the plaintext.



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.

-------------------------------------------------------------------
Case 10: Permit cheating, but prevent it by overwriting the
         plaintext. All options are responses to prompts.
-------------------------------------------------------------------

PilferM begin at 2018/08/04 18:04:05 Central Daylight Time


The command line was input bare (without arguments).
Prompting for selection:

OK to cheat? Enter Y to cheat, or N to play by the rules. >>
    Response from user = y

Scrub the plaintext output buffer or leave it intact?
Enter Y to scrub it or N to leave it exposed. >>
    Response from user = n


Values within function CreateEncryptedMessage:

    lpSubEBP                        = 0x008ff77c

    achPlainText                    = 0x008fb778

Message 1 = This is message 1 of 2.
Message 2 = This is message 2 of 2.

Message Text:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>


Encrypting the message.... Done

Plaintext length  = 130
Ciphertext length = 162

Encrypted message:

82 54 d5 26 e9 03 12 c1 06 06 97 79 f3 86 9c 0f 97 9f 89 f2 40 4e 55 8a d5 1f e6 4b 0e 46 b5 0e 1e dd 03 50 10 8a 9d 46 f7 ad 38 5e 75 d8 d8 11 ac 2e 5d 96 f9 65 99 50 32 9d ba 2b 37 79 2a d1 2e 9d 51 d5 94 b9 10 74 17 e0 cd 76 a2 28 8e da e4 9f 3b 90 cf 73 70 34 2b 5c a7 c4 8e 21 13 6c b8 e9 54 19 50 9c a8 4a f0 ed 14 36 6b 53 02 a1 15 9d b7 c3 69 0a 0f 17 89 a4 9f 47 c7 00 30 7c 05 20 bc 45 b9 b1 af 89 02 3f 4a 4c 09 72 e4 1b ad ee bd 09 50 19 d9 00 50 46 9d 34 73 ca 74 91 e6 18

Character array achPlainText is intact in the stack frame.

Cheating...


Values within function main:

    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x0000404c (16460 decimal)
    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x00004028 (16424 decimal)

    MAIN_OFFSET_TO_PLAINTEXT        = 0x00004028 (16424 decimal)
    SUB_OFFSET_TO_PLAINTEXT         = 0x00004004 (16388 decimal)

    OFFSET_TO_FUNCTION_EBP          = 0x00000024 (36 decimal)

    lpMainEBP                       = 0x008ff7a0

    intPlainTextLength              = 0x008ff77c (9435004 decimal)
    lpszPlainText                   = 0x008fb778

    lpszPlainText:

The two messages follow.

    Message 1: This is message 1 of 2.

    Message 2: This is message 2 of 2.

End of Transmission
<*>



Cheating... Done!


Done!
<*>

Please press the Return (ENTER) key to exit the program.
WWPause version 3, 0, 0, 4
Sat 04 Aug 2018 18:04:06 Local

Please press RETURN when ready...
Listing 1 is the entire output of a recent run of PilferMe_UnitTests.CMD on my development machine.
 
There are a few things to call to your attention about the listing.
  1. Since the hexadecmial dumps of the ciphertext were written as long lines of text, they didn't wrap around.
  2. Although the the message text and encryption key are identical, the ciphertext generated on each run is different, thanks to the salt in the initialization vector of the AES context block.
  3. Since the program was linked with Adress Space Location Randomization (ASLR) enabled, no two runs will yield the same register values, even on the same machine.

Although I could easily have included an image of the command prompt window, it wouldn't show enough of the output to contribute to the article, and the editors are thereby relieved of having to ensure that the image file makes it into the publication.

Points of Interest

Earlier, I mentioned that I had learned how to use inline assembly language to grab arbitrary blocks of memory, and even CPU registers, and stuff them into locations that are visible to the C++ source code, I didn't go to that much trouble for this demonstration. As it is, the code makes it clear that the base pointer of function  CreateEncryptedMessage could easily be harvested and made visible to the calling routine, which could then work out the offset to the beginning of the buffer that holds the message. That is partially demonstrated by the following excerpt from that routine.

C#
LPVOID  lpSubEBP            = NULL ;
LPVOID  lpPlainText         = NULL;

__asm {
    lea eax , [ ebp ];
    mov lpSubEBP , eax;

    lea eax , [ achPlainText ];
    mov lpPlainText , eax;
}   // Back to the Land of C we go.

_tprintf ( TEXT( "\nValues within function %s:\n\n" ) ,
           __FUNCTION__ );
_tprintf ( TEXT ( "    lpSubEBP                        = 0x%08x\n\n" ) ,
           ( DWORD_PTR ) lpSubEBP );
_tprintf ( TEXT ( "    achPlainText                    = 0x%08x\n\n" ) ,
           ( DWORD_PTR ) lpPlainText );

_tprintf ( TEXT ( "Message 1 = %s\n" ) ,
           plpMessage1 );
_tprintf ( TEXT ( "Message 2 = %s\n" ) ,
           plpMessage2 );
Following the above demonstration of register pilfering, the real work begins; the two input messages are substituted into a template, which is subsequently encrypted and returned to the calling routine. Note that, officially, the caller has access only to the two input messages and the encrypted message; it knows nothing about the template.
 
The remainder of CreateEncryptedMessage is implemted as a nested IF block, of which the most important parts are shown next.
 
C++
if ( ( rlpCipherInfo->intPlainTextLen = _stprintf_s ( achPlainText ,
                                                      MESSAGE_BUFFER_SIZE ,
                                                      TEXT ( "The two messages follow.\n\n    Message 1: %s\n\n    Message 2: %s\n\nEnd of Transmission\n<*>\n" ) ,
                                                      plpMessage1 ,
                                                      plpMessage2 ) )
        > SPRINTF_ERROR_RESULT )
{
    _tprintf ( TEXT ( "\nMessage Text:\n\n%s\n\nEncrypting the message.... " ) ,
               achPlainText );

    if ( unsigned char * lpKeyBuffer = ( unsigned char * ) AllocBytes_WW ( SHA256_DIGEST_SIZE ) )
    {
        if ( rlpCipherInfo->dwStatusCode = KeyGen ( lpKeyBuffer , SHA256_DIGEST_SIZE ) == ERROR_SUCCESS )
        {   // Since KeyGen succeeded, the string can be encrypted.
            if ( rlpCipherInfo->lpCipherText = EncryptString_P6C ( achPlainText ,
                                                                   rlpCipherInfo->intPlainTextLen ,
                                                                   lpKeyBuffer ,
                                                                   SHA256_DIGEST_SIZE ) )
            {   // Since EncryptString_P6C succeeded, show the outcome.

Listing 2 is the essential part of CreateEncryptedMessage, whichi creates and encrypts the message that is made available to the calling routine. The remainder of the routine just shows my work before a pointer to the rlpCipherInfo is returned to the main routine.

When control returns to the main routine, the epilogue of CreateEncryptedMessage restores its stack and base pointers, leaving its stack frame abandoned, but completely intact.

C++
_tprintf ( TEXT ( "\nCheating... \n\n" ) );

//  ----------------------------------------------------
//  Define and initialize these before entering the ASM
//  block that sets them to their correct values, based
//  on the offsets into CreateEncryptedMessage working
//  storage worked out by tracing through it in a
//  diassembly view.
//  ----------------------------------------------------

int     intPlainTextLength  = STRLEN_EMPTY_P6C;
TCHAR * lpszPlainText       = NULL;
LPVOID  lpMainEBP           = NULL ;

_tprintf ( TEXT( "\nValues within function %s:\n\n" ) ,
           __FUNCTION__ );

_tprintf ( TEXT ( "    MAIN_OFFSET_TO_PLAINTEXT_LENGTH = 0x%08x (%d decimal)\n" ) ,
           MAIN_OFFSET_TO_PLAINTEXT_LENGTH ,
           MAIN_OFFSET_TO_PLAINTEXT_LENGTH );
_tprintf ( TEXT ( "    SUB_OFFSET_TO_PLAINTEXT_LENGTH  = 0x%08x (%d decimal)\n\n" ) ,
    SUB_OFFSET_TO_PLAINTEXT_LENGTH ,
    SUB_OFFSET_TO_PLAINTEXT_LENGTH );

_tprintf ( TEXT ( "    MAIN_OFFSET_TO_PLAINTEXT        = 0x%08x (%d decimal)\n" ) ,
    MAIN_OFFSET_TO_PLAINTEXT ,
    MAIN_OFFSET_TO_PLAINTEXT );
_tprintf ( TEXT ( "    SUB_OFFSET_TO_PLAINTEXT         = 0x%08x (%d decimal)\n\n" ) ,
    SUB_OFFSET_TO_PLAINTEXT ,
    SUB_OFFSET_TO_PLAINTEXT );

_tprintf ( TEXT ( "    OFFSET_TO_FUNCTION_EBP          = 0x%08x (%d decimal)\n\n" ) ,
    OFFSET_TO_FUNCTION_EBP ,
    OFFSET_TO_FUNCTION_EBP );

__asm {
    lea eax , [ ebp ];
    mov lpMainEBP , eax;

    mov eax , dword ptr [ ebp - MAIN_OFFSET_TO_PLAINTEXT_LENGTH ];
    mov dword ptr [ intPlainTextLength ] , eax;

    lea eax , [ ebp - MAIN_OFFSET_TO_PLAINTEXT ];
    mov lpszPlainText , eax;
}   // Back to the Land of C we go.

_tprintf ( TEXT ( "    lpMainEBP                       = 0x%08x\n\n" ) ,
           ( DWORD_PTR ) lpMainEBP );

_tprintf ( TEXT ( "    intPlainTextLength              = 0x%08x (%d decimal)\n" ) ,
           intPlainTextLength ,
           intPlainTextLength );

_tprintf ( TEXT ( "    lpszPlainText                   = 0x%08x\n\n" ) ,
           ( DWORD_PTR ) lpszPlainText );

_tprintf ( TEXT ( "    lpszPlainText:\n\n%s\n\n" ) ,
           StringIsNullOrEmptyWW ( lpszPlainText )
                ? "CreateEncryptedMessage ran in secure mode and scrubbed the plaintext.\n" :
                lpszPlainText );

_tprintf ( TEXT ( "\nCheating... Done!\n\n" ) );

Listing 3 is the combination of ordinary C++ and inline assembly that harvests data from the abandoned CreateEncryptedMessage stack frame.

Since the main routine immediately sets about harvesting data from it, it can grab whatever it wants from the stack frame that belonged to CreateEncryptedMessage.

However, all is not lost!

If CreateEncryptedMessage takes the trouble to overwrite the message buffer, which it does when the second question is answered affirmatively, there is nothing for the main routine to show, and it must use the key to unlick the message.

Of course, none of this is news to my security-conscious colleagues, who developed the habit of ovewriting sensitive data before any block of memory is abandoned. Nevertheless, it serves as a reminder that this rule must be applied to any sensitive data, even if it lives in one of those ephemeral "automatic" variables that make their home on the stack frame of the function that created them.

I am unaware of any compiler option that you can set that causes the compiler to generate the code that would be necessary to overwrite abandoned stack frames. That being the case, you must keep in mind that abandoned stack frames remain in memory until other stack frames overwrite them. Hence, they can stay in memory for a long time, especially when they arise to meet the needs of a deeply nested method or function call.

References

  1. sprintf_s: Speed Bumps Ahead not only covers the work that led to this discovery, but it provides background that is essential to understanding how this proof of concept program works.
  2. Microsoft Visual C++ 2015 Redistributable Update 3 RC is a location from which you can get the CRT libriary that ships with the most recent versions of Microsoft Visual Studio, which you will need in order to run the demonstration program.

History

Saturday, 04 August 2018 - This article was submitted for publicathon.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I deliver robust, clean, adaptable, future-ready applications that are properly documented for users and maintainers. I have deep knowledge in multiple technologies and broad familiarity with computer and software technologies of yesterday, today, and tomorrow.

While it isn't perceived as sexy, my focus has always been the back end of the application stack, where data arrives from a multitude of sources, and is converted into reports that express my interpretation of The Fundamental Principle of Tabular Reporting, and are the most visible aspect of the system to senior executives who approve the projects and sign the checks.

While I can design a front end, I prefer to work at the back end, getting data into the system from outside sources, such as other computers, electronic sensors, and so forth, and getting it out of the system, as reports to IDENTIFY and SOLVE problems.

When presented with a problem, I focus on identifying and solving the root problem for the long term.

Specialties: Design: Relational data base design, focusing on reporting; organization and presentation of large document collections such as MSDS libraries

Development: Powerful, imaginative utility programs and scripts for automated systems management and maintenance

Industries: Property management, Employee Health and Safety, Services

Languages: C#, C++, C, Python, VBA, Visual Basic, Perl, WinBatch, SQL, XML, HTML, Javascript

Outside Interests: Great music (mostly, but by no means limited to, classical), viewing and photographing sunsets and clouds, traveling by car on small country roads, attending museum exhibits (fine art, history, science, technology), long walks, especially where there is little or no motor traffic, reading, especially nonfiction and thoughtfully written, thought provoking science fiction

Comments and Discussions

 
-- There are no messages in this forum --