攻击堆栈





5.00/5 (7投票s)
当心被抛弃的堆栈帧,因为它可能会被随意掠夺。
引言
当我编写演示应用程序以配合sprintf_s: Speed Bumps Ahead时,我注意到,属于活动函数的帧以下的堆栈帧会一直保留,直到下一次调用函数,并且其中的部分内容可以保留更长时间,甚至超出活动函数。为那篇文章进行的研究也为通过内联汇编指令读取寄存器并将其值存储在任何我想要的地方(包括活动函数的输出参数)铺平了道路。
背景
我最近阅读了关于 Spectre 和 Melteown CPU 漏洞的工作原理,这重新激起了我对这一发现的兴趣。我想到,如果另一个进程可以窥探一个查找表,那么被抛弃的堆栈帧也不安全了。虽然我对进程间通信的了解不足以演示这种利用,但我打算展示同一个进程中的例程如何做到这一点,并可能对本应安全的数据造成毁灭性的后果。
使用代码
归档文件包含以下目录。
PilferMe
是程序的源代码,以及用于对其进行充分测试的 shell 脚本和响应文件的母版,这些测试包括所有操作系统参数的排列组合。Debug
是程序的调试版本,以及一个 shell 脚本和运行它所需的十个响应文件。脚本和响应文件与Release
目录中的相同,并通过一个构建后脚本复制到其中。Release
是程序的发布版本,以及一个 shell 脚本和运行它所需的十个响应文件。脚本和响应文件与Debug
目录中的相同,并通过一个构建后脚本复制到其中。Util
包含 shell 脚本使用的实用程序,用于暂停终止以便您可以查看或复制输出,以及主程序和该实用程序所需的支持 DLL。此外,还有一个链接,您可以从中获取 Microsoft Visual C++ 2015 Redistributable Update 3 RC,如果您还没有它,则需要此项。
运行脚本的最简单方法是使用 Windows 文件资源管理器显示 Release 目录,然后双击或以其他方式选择 PilferMe_UnitTests.CMD
来运行它。
- 启动后,脚本将执行
PilferMe.exe
,并对其进行我确定的10 种场景之一的测试。由于PilferMe_UnitTests.CMD
从一组响应文件中读取输入,因此所有十种场景都将“无人值守”运行,直到最后一个提示,这是由WWPause.exe
提供的,它会保持窗口打开供您检查和复制。 - 由于
WWPause.exe
的设计和实现存在缺陷,您无法使用 Windows 10 近期更新中引入的Ctrl-A
和Ctrl-C
键盘快捷键将窗口内容复制到剪贴板。但是,从使用Alt-Spacebar
然后Ctrl-A
,再按Enter
键的控制台窗口复制文本的旧方法仍然有效,这也是我捕获下面列表中文本的方式。
列表 1,如下所示,是我最近一次运行的机器上的输出。
--------------------------------------------------------------------- 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...
PilferMe_UnitTests.CMD
的完整输出。- 由于密文的十六进制转储被写成长文本行,它们没有换行。
- 尽管消息文本和加密密钥相同,但每次运行生成的密文都不同,这要归功于 AES 上下文块的初始化向量中的盐值。
- 由于程序在启用了地址空间布局随机化 (ASLR) 的情况下进行链接,因此即使在同一台机器上,两次运行产生的寄存器值也不会相同。
虽然我可以轻松地包含命令提示符窗口的屏幕截图,但这不足以显示足够的输出以供文章使用,因此编辑们不必担心确保图像文件能被刊登。
关注点
前面我提到过,我已经学会了如何使用内联汇编语言来抓取任意内存块,甚至 CPU 寄存器,并将它们存储在 C++ 源代码可见的位置。为了这个演示,我没有费这么大劲。代码已经清楚地表明,函数 CreateEncryptedMessage
的基址指针很容易被提取并使其对调用例程可见,然后该例程可以计算出保存消息的缓冲区的偏移量。下面该例程的摘录部分演示了这一点。
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 );
CreateEncryptedMessage
的其余部分实现了为一个嵌套的 IF
块,其中最重要的部分如下所示。 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.
列表 2 是 CreateEncryptedMessage
的关键部分,它创建并加密了可供调用例程使用的消息。例程的其余部分只是显示我工作的内容,然后将指向 rlpCipherInfo
的指针返回给主例程。
当控制权返回到主例程时,CreateEncryptedMessage
的结尾恢复了其堆栈和基址指针,使其堆栈帧被丢弃,但完好无损。
_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" ) );
列表 3 是普通的 C++ 和内联汇编的组合,它从被丢弃的 CreateEncryptedMessage
堆栈帧中收集数据。
由于主例程立即开始从中收集数据,因此它可以从属于 CreateEncryptedMessage
的堆栈帧中获取任何它想要的东西。
但是,并非一切都完了!
如果 CreateEncryptedMessage
在第二个问题回答“是”时,花费力气覆盖消息缓冲区,那么主例程就没有什么可显示的了,它必须使用密钥来解锁消息。
当然,这些对我注重安全的同事来说都不是什么新鲜事,他们已经养成了在任何内存块被丢弃之前覆盖敏感数据的习惯。尽管如此,这仍然提醒我们,这个规则必须应用于任何敏感数据,即使它位于那些栖身于创建它们的函数的堆栈帧中的“自动”变量中。
我不知道有什么编译器选项可以设置,让编译器生成覆盖被丢弃堆栈帧所需的代码。因此,您必须记住,被丢弃的堆栈帧会保留在内存中,直到其他堆栈帧覆盖它们。因此,它们可以在内存中保留很长时间,尤其是在它们为了满足深度嵌套的方法或函数调用的需求而出现时。
参考文献
- sprintf_s: Speed Bumps Ahead 不仅涵盖了导致此发现的工作,而且提供了理解此概念验证程序如何工作的基本背景。
- Microsoft Visual C++ 2015 Redistributable Update 3 RC 是您获取与最新版本 Microsoft Visual Studio 一起发布的 CRT 库的来源,运行演示程序需要此库。
历史
2018 年 8 月 4 日星期六 - 本文已提交出版。