• 标 题:用OLLDBG脱ASPR1.23(翻译)
  • 作 者:ikki
  • 时 间:2003年10月09日 07:32
  • 链 接:http://bbs.pediy.com

这个是论坛上别人转的一篇帖子,看看里面手工修复IAT的部分写的挺详细,所以大概翻译了一下,希望能给和我一样的新手一些帮助。
----by  ikki

用OLLDBG脱ASPR1.23
(ASPR 1.23 Unpacking "Step-By-Step")                                  
=========================================================           
目标连接:              : www.systemcleaner.com                     
程序:                  : System Cleaner. 4.91d                     
作者:                  : LaBBa                                     
                                                  
========================                                            
在我开始尝试脱这个程序和别的新版本的aspr压缩的程序的时候,我dump下来的文件都是错误的,
因为我在错误的地方dump程序,并因此常常导致脱壳后的程序崩溃。所以,这个教程将说明这个
错误的原因以及这样对这个版本的aspr文件脱壳。
                                
需要的工具:                                                 
-----------------------                                             
1) 你的头脑                                
2) OllyDbg 1.09d             
3) Olly的插件 : Cmdline and Plug108 (http://home.t-online.de/home/Ollydbg/)
4) ProcDump 或者 Lord-Pe                                              
5) Hview 或者 Hex Editor                                              
6) ImpRec 1.6 Final Public (Mackt/UCF)                              
* 以及一些pe文件结构的知识 ..                       
===============================                                     
脱壳步骤:                                        
===============================                                     
1) 如何绕过Anti-Debugger保护                           
2) 如何找到合适的位置来dump文件                  
3) 如何找到程序真正的OEP                                             
4) 关于stolen bytes                                        
5) 如何用ImpRec修复IAT                                       
6) 如何修复OEP并补上Stolen bytes                      
7) 致谢                                                          
=======================================                             
如何绕过Anti-Debugger保护:                              
=======================================                             
这个版本的ASPR含有Atni-Debugger代码                       
在Cmdline中简单的设置BP IsDebuggerPresents然后按Shift+F9我们将会到达这里:                                             
77E72E92 > 64:A1 18000000   MOV EAX,DWORD PTR FS:[18]               
77E72E98   8B40 30          MOV EAX,DWORD PTR DS:[EAX+30]           
77E72E9B   0FB640 02        MOVZX EAX,BYTE PTR DS:[EAX+2]           
77E72E9F   C3               RETN                                    
在: MOVZX EAX,BYTE PTR DS:[EAX+2]处                                  
我们看到: BYTE PTR DS:[EAX+2] ==7FFDF002 == 01                  
01 - 表示发现调试器 .. 所以每次都要把这个值修改为00以便我们能够运行程序。按Ctrl+F2重新运行程序,运行到上面那个地方,然后在dump窗口中右键单击选择:转到-->表达式(GoTo->Expression),输入地址:7FFDF002,把内存中这个地址的值修改为:00。现在我们可以开始脱壳了.

===================================================                 
如何找到合适的位置来dump文件                     
===================================================                 
一次次的按Shift+F9,一直到我们到达这儿:
00FF3A2C   3100             XOR DWORD PTR DS:[EAX],EAX              
00FF3A2E   64:8F05 00000000 POP DWORD PTR FS:[0]                    
00FF3A35   58               POP EAX                                 
00FF3A36   833D B07EFF00 00 CMP DWORD PTR DS:[FF7EB0],0             
00FF3A3D   74 14            JE SHORT 00FF3A53                       
00FF3A3F   6A 0C            PUSH 0C                                 
00FF3A41   B9 B07EFF00      MOV ECX,0FF7EB0                         
00FF3A46   8D45 F8          LEA EAX,DWORD PTR SS:[EBP-8]            
00FF3A49   BA 04000000      MOV EDX,4                               
00FF3A4E   E8 EDD0FFFF      CALL 00FF0B40                           
00FF3A53   FF75 FC          PUSH DWORD PTR SS:[EBP-4]               
00FF3A56   FF75 F8          PUSH DWORD PTR SS:[EBP-8]               
00FF3A59   8B45 F4          MOV EAX,DWORD PTR SS:[EBP-C]            
00FF3A5C   8338 00          CMP DWORD PTR DS:[EAX],0                
00FF3A5F   74 02            JE SHORT 00FF3A63                       
00FF3A61   FF30             PUSH DWORD PTR DS:[EAX]                 
00FF3A63   FF75 F0          PUSH DWORD PTR SS:[EBP-10]              
00FF3A66   FF75 EC          PUSH DWORD PTR SS:[EBP-14]              
00FF3A69   C3               RETN                                    
按F2在RET那里设置一个断点,然后再次按Shift+F9 ..                                                             
我们会在停在RET这一行..                                  
现在是设置跟踪命令(Trace Command)的时候了..                               
我们知道,大多数pe文件的开始地址是 : 400000 -- FFFFFFFF                                      
但是小程序并不是这样 .. 小程序通常结束于 9FFFFF
我们现在停在的地址是 : 00FF3A69                                 
所以我们可以通过命令行插件来跟踪这个程序直到它返回OEP,就象这样:                                 
按Alt+F1再命令行插件的输入框中输入: TC EIP<900000 然后回车。
这样,当程序运行到小于900000的地址时,将会象我们希望的那样自动中断下来..                                       
我们停在这里 :                                                
00407278  -FF25 2C235700    JMP DWORD PTR DS:[57232C] <<--这里     
0040727E   8BC0             MOV EAX,EAX                             
00407280  -FF25 28235700    JMP DWORD PTR DS:[572328]               
00407286   8BC0             MOV EAX,EAX                             
00407288  -FF25 24235700    JMP DWORD PTR DS:[572324]               
0040728E   8BC0             MOV EAX,EAX                             
00407290  -FF25 20235700    JMP DWORD PTR DS:[572320]               
00407296   8BC0             MOV EAX,EAX                             
00407298   50               PUSH EAX                                
00407299   6A 40            PUSH 40                                 
0040729B   E8 E0FFFFFF      CALL SystemCl.00407280                  
004072A0   C3               RETN                                    
如果我们按一次F8我们将回到壳的代码中,    
所以我们按一次F8并再次执行跟踪命令(Trace Command)       
这一次,我们来到这里:                                     
0040734D   A3 68E65600      MOV DWORD PTR DS:[56E668],EAX            ; SystemCl.00400000
00407352   A1 68E65600      MOV EAX,DWORD PTR DS:[56E668]           
00407357   A3 D8505600      MOV DWORD PTR DS:[5650D8],EAX           
0040735C   33C0             XOR EAX,EAX                             
0040735E   A3 DC505600      MOV DWORD PTR DS:[5650DC],EAX           
00407363   33C0             XOR EAX,EAX                             
00407365   A3 E0505600      MOV DWORD PTR DS:[5650E0],EAX           


0040736A   E8 C1FFFFFF      CALL SystemCl.00407330                  
0040736F   BA D4505600      MOV EDX,SystemCl.005650D4               
00407374   8BC3             MOV EAX,EBX                             
00407376   E8 75D8FFFF      CALL SystemCl.00404BF0                  
0040737B   5B               POP EBX                                 
0040737C   C3               RETN                                    
这些是什么东西??                                             
这个问题花了我一些时间但在调试了更多的别的程序之后,我认为这是对GetModuleHandleA的调用代码的一部分,看起来应该象这样的:                               
CALL <JMP.&kernel32.GetModuleHandleA>                               
MOV DWORD PTR DS:[584668],EAX            ; SystemCl.00400000        
MOV EAX,DWORD PTR DS:[584668]                                       
MOV DWORD PTR DS:[5780D8],EAX                                       
XOR EAX,EAX                                                         
MOV DWORD PTR DS:[5780DC],EAX                                       
XOR EAX,EAX                                                         
MOV DWORD PTR DS:[5780E0],EAX                                       
CALL SystemCl.00407450                                              
MOV EDX,SystemCl.005780D4                                           
MOV EAX,EBX                                                         
CALL SystemCl.00404BFC                                              
POP EBX                                                             
RETN                                                                
因为这是我们需要Dump的文件的一部分,所以我们应该在第二次跟踪到达这里的时候dump: 0040734D..
打开ProcDump / Lord-PE 选择dump(full)来dump进程                  
=======================                                             
如何找到程序真正的OEP                                                
=======================                                             
地址0040734D并不是OEP,我们按F8单步执行,直到RET,返回(ret)后我们来到这里:                                           
00564BEC   FF15 A4D15600    CALL DWORD PTR DS:[56D1A4]               ; SystemCl.00564460
00564BF2   E8 C101EAFF      CALL SystemCl.00404DB8                  
00564BF7   90               NOP                                     
00564BF8   0000             ADD BYTE PTR DS:[EAX],AL                
00564BFA   0000             ADD BYTE PTR DS:[EAX],AL                
00564BFC   0000             ADD BYTE PTR DS:[EAX],AL                
好了.. 现在我们可以用ProcDump/Pe-Tool来dump进程了,然后得到一个dump文件 ..                                                  
但那里才是OEP ?                                                
是这儿吗: 00564BEC ??                                                 
NO!                                                                 
如果你往上看,你会发现这样的代码:                             
00564BD3   0000             ADD BYTE PTR DS:[EAX],AL                
00564BD5   0000             ADD BYTE PTR DS:[EAX],AL                
00564BD7   00A444 56000000  ADD BYTE PTR SS:[ESP+EAX*2+56],AH       
00564BDE   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE0   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE2   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE4   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE6   00E8             ADD AL,CH                               
真正的OEP是 :                                                   
00564BDC                                                     

你可能会问"为什么 ?"...                                          
ASPR使用了一种叫做 "Steal-Bytes"的技术:                           
asrp隐藏了PE文件OEP开始处的字节,并把这些字节从原始PE文件中擦除..
=====================                                               
关于stolen bytes                                                    
=====================                                               
稍微说明一下:                                                   
每一种编译工具例如 : VC++ , Delphi , Borland , etc..               
在OEP有一个唯一的/相同的PE头                             
其中的一些是这样的:                                            
Push EBP                                                            
MOV Ebp,Esp                                                         
Add ESP , -010                                                      
Mov EAX, SOME_VALUE                                                 
(共11bytes)                                                  
或者:                                                                
Push EBP                                                            
MOV Ebp,Esp                                                         
Add ESP , -010                                                      
Push EBX                                                            
Push ESi                                                            
Push EDi                                                            
Mov EAX, SOME_VALUE                                                 
(共14 bytes)                                                  
我不能确定ADD ESP的数值,但在这个程序中是:add esp,-10,当然,不是所有的程序都一样的。 
在这个程序中,我们看看:                                            
00564BD7   00A444 56000000  ADD BYTE PTR SS:[ESP+EAX*2+56],AH       
00564BDE   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE0   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE2   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE4   0000             ADD BYTE PTR DS:[EAX],AL                
00564BE6   00E8             ADD AL,CH                               
从地址: 00564BE6开始到我们找到的真正的OEP:00564BDC,总共是11字节            
所以我们需要补上这11字节,现在的问题就是:EAX的值是多少?                        
我发现这其实很容易找到..         
看看这段代码:                                                
0040734D   A3 68E65600      MOV DWORD PTR DS:[56E668],EAX            ; SystemCl.00400000
00407352   A1 68E65600      MOV EAX,DWORD PTR DS:[56E668]                                        
00407357   A3 D8505600      MOV DWORD PTR DS:[5650D8],EAX                                        
0040735C   33C0             XOR EAX,EAX                                                          
0040735E   A3 DC505600      MOV DWORD PTR DS:[5650DC],EAX                                        
00407363   33C0             XOR EAX,EAX                                                          
00407365   A3 E0505600      MOV DWORD PTR DS:[5650E0],EAX                                        
0040736A   E8 C1FFFFFF      CALL SystemCl.00407330                                               
0040736F   BA D4505600      MOV EDX,SystemCl.005650D4                                            
00407374   8BC3             MOV EAX,EBX                                                          
00407376   E8 75D8FFFF      CALL SystemCl.00404BF0                                               
0040737B   5B               POP EBX                                                              
0040737C   C3               RETN                                                                 
注意这里:                                                                                      
00407374   8BC3             MOV EAX,EBX                                                          
我们将得到我们需要的EAX的正确值                                                          
在这个例子是 : 5644CC                                                                        
ok,现在剩下的工作就是修复IAT了。                                                        
=================                                                                                
如何IAT                                                                                   
=================                                                                                
在improved ImpRec 1.6 Final中选取进程,然后     
点击: IAT AutoSearch                                                                
修改RVA的大小(Size of the RVA )为1000                                                           
然后点击获取输入表(GetImports)                                                                   点击显示无效地址(Show Invalid)                                                                   在无效地址列表(invalid list)中点击右键选择"Trace Level 1"                                        这一步骤完成之后,把那些太大的需要删除的地址清除掉(右键 , Cut Thunks)                                                                 
例如:                                                                       
1           00172278                kernel32.dll            019D        GetStartupInfoA          
0           0017227C                ?           0000        00FF17E4                <<-- don't cut
0           00172280                ?           0000        00FF1CA4                            <<-- don't cut
1           00172284                kernel32.dll            0166        GetModuleFileNameA       
1           00172288                kernel32.dll            015E        GetLocaleInfoA           
1           0017228C                kernel32.dll            015B        GetLastError             
1           00172290                kernel32.dll            012E        GetCurrentDirectoryA     
0           00172294                ?           0000        00FF1D18                            <<-- don't cut
1           00172298                kernel32.dll            00E6        FreeLibrary              
1           0017229C                kernel32.dll            00C6        FindFirstFileA           
1           001722A0                kernel32.dll            00C2        FindClose                
1           001722A4                kernel32.dll            00AC        ExitProcess              
1           001722A8                kernel32.dll            00AD        ExitThread               
1           001722AC                kernel32.dll            0066        CreateThread             
1           001722B0                kernel32.dll            0377        WriteFile                
1           001722B4                kernel32.dll            0343        UnhandledExceptionFilter 
1           001722B8                kernel32.dll            02F2        SetFilePointer           
1           001722BC                kernel32.dll            02E9        SetEndOfFile             
1           001722C0                kernel32.dll            02B2        RtlUnwind                
1           001722C4                kernel32.dll            0291        ReadFile                 
1           001722C8                kernel32.dll            0284        RaiseException           
1           001722CC                kernel32.dll            024B        MoveFileA                
1           001722D0                kernel32.dll            019F        GetStdHandle             
1           001722D4                kernel32.dll            014E        GetFileSize              
1           001722D8                kernel32.dll            0151        GetFileType              
1           001722DC                kernel32.dll            0079        DeleteFileA              
1           001722E0                kernel32.dll            004B        CreateFileA              
1           001722E4                kernel32.dll            002D        CloseHandle              
0           001722E8                ?           0000        CCB36727                            <<-- CUT !
1           001722EC                user32.dll              0128        GetKeyboardType         

1           001722F0                user32.dll              01C9        LoadStringA              
1           001722F4                user32.dll              01DD        MessageBoxA              
1           001722F8                user32.dll              002B        CharNextA                
0           001722FC                ?           0000        2C24B7E9                            <<-- CUT !
1           00172300                advapi32.dll            01EC        RegQueryValueExA         
1           00172304                advapi32.dll            01E2        RegOpenKeyExA            
1           00172308                advapi32.dll            01C9        RegCloseKey              
0           0017230C                ?           0000        48E33A34                            <<-- CUT !
1           00172310                oleaut32.dll            0006        SysFreeString            
1           00172314                oleaut32.dll            0005        SysReAllocStringLen      
1           00172318                oleaut32.dll            0004        SysAllocStringLen        
在清除完后,我们还有8个无效的项需要修复 :                                         
1) 00FF1CCC                                                                                      
2) 00FF17E4                                                                                      
3) 00FF1CA4                                                                                      
4) 00FF1D18                                                                                      
5) 00FF1D08                                                                                      
6) 00FF1D00                                                                                      
7) 00FF1CF8                                                                                      
8) 00FF1D30                                                                                      
点击show invalid并右键单击选择其中的一个,让我们来修复它 :                                                                             
00FF1D30 <- 右键单击选择DeasmHexView查看:             
push ebp                                                                                         
00FF1D31                                                                                         
mov ebp,esp                                                                                      
00FF1D33                                                                                         
mov eax,[FF7E24]    // DWORD value: 00152398                                                     
00FF1D39                                                                                         
pop ebp                                                                                          
00FF1D3A                                                                                         
retn 4                                                                                           
仔细瞧瞧 .. 这个函数在开始的时候push EBP然后在结束的时候Pop EBP,所以这个函数什么都没执行,应该是LockResource或者FreeResource                                                                  
我选择的是: FreeResource                                                                         
----------------------------------------------------------------------------------------------                                                                                           
在新版本的imprec中有个Deasmbler/HexView的功能,你可以选择一个无效的api查看调用代码 ...                                                            
修复8个无效的地址:                                                                               
1) 00FF1CCC                                                                                      
00FF1CCC   FF35 147EFF00    PUSH DWORD PTR DS:[FF7E14]                                           
00FF1CD2   58               POP EAX                                                              
00FF1CD3   C3               RETN                                                                 
如果你执行这段代码你会看到:                                     
00407235   25 FF000000      AND EAX,0FF                                                          
在我这里,执行完这段代码后(OS WinXP Pro)EAX=5                                                    这个是操作系统的值。每个操作系统都有一个唯一的标识             
所以这个是: GetVersion  <-- Good Imprec !                                                       
----------------------------------------------------------------------------------               
2) 00FF17E4                                                                             

00FF17E4   55               PUSH EBP                                
00FF17E5   8BEC             MOV EBP,ESP                             
00FF17E7   8B55 0C          MOV EDX,DWORD PTR SS:[EBP+C]            
00FF17EA   8B45 08          MOV EAX,DWORD PTR SS:[EBP+8]            
00FF17ED   8B0D 8464FF00    MOV ECX,DWORD PTR DS:[FF6484]           
00FF17F3   8B09             MOV ECX,DWORD PTR DS:[ECX]              
00FF17F5   3BC8             CMP ECX,EAX                             
00FF17F7   75 09            JNZ SHORT 00FF1802                      
00FF17F9   8B0495 D863FF00  MOV EAX,DWORD PTR DS:[EDX*4+FF63D8]     
00FF1800   EB 07            JMP SHORT 00FF1809                      
00FF1802   52               PUSH EDX                                
00FF1803   50               PUSH EAX                                
00FF1804   E8 B739FFFF      CALL 00FE51C0     ; JMP to kernel32.GetProcAddress
00FF1809   5D               POP EBP                                 
00FF180A   C2 0800          RETN 8                                  
正如我们看到的那样: GetProcAddress  <-- Good Imprec !             
-----------------------------------------------------------------------------------
3) 00FF1CA4                                                         
00FF1CA4   55               PUSH EBP                                
00FF1CA5   8BEC             MOV EBP,ESP                             
00FF1CA7   8B45 08          MOV EAX,DWORD PTR SS:[EBP+8]            
00FF1CAA   85C0             TEST EAX,EAX                            
00FF1CAC   75 13            JNZ SHORT 00FF1CC1                      
00FF1CAE   813D A47AFF00 00>CMP DWORD PTR DS:[FF7AA4],400000   ; ASCII "MZP"
00FF1CB8   75 07            JNZ SHORT 00FF1CC1                      
00FF1CBA   A1 A47AFF00      MOV EAX,DWORD PTR DS:[FF7AA4]           
00FF1CBF   EB 06            JMP SHORT 00FF1CC7                      
00FF1CC1   50               PUSH EAX                                
00FF1CC2   E8 F134FFFF      CALL 00FE51B8    ; JMP to kernel32.GetModuleHandleA
00FF1CC7   5D               POP EBP                                 
00FF1CC8   C2 0400          RETN 4                                  
00FF1CCB   90               NOP                                     
00FF1CCC   FF35 147EFF00    PUSH DWORD PTR DS:[FF7E14]              
00FF1CD2   58               POP EAX                                 
00FF1CD3   C3               RETN                                    
如上: GetModuleHandleA   <-- Good Imprec !          
------------------------------------------------------------------------------------
4) 00FF1D18                                                         
00FF1D18   6A 00            PUSH 0                                  
00FF1D1A   E8 9934FFFF      CALL 00FE51B8    ; JMP to kernel32.GetModuleHandleA
00FF1D1F   FF35 147EFF00    PUSH DWORD PTR DS:[FF7E14]              
00FF1D25   58               POP EAX                                 
00FF1D26   8B05 247EFF00    MOV EAX,DWORD PTR DS:[FF7E24]           
00FF1D2C   C3               RETN                                    
仔细看看,你会发现这个可不是GetModuleHandleA
为什么呢? 因为在API GetModuleHandleA的调用之后程序利用这段代码来获取某些信息存入EAx作为调用的返回值 ..                          
那么这个是是什么函数呢? 执行完这段代码之后你会发现EAX中是程序路径的ascii字符串。                 所以这个是: GetCommandLineA   <-- Good Imprec !                    
------------------------------------------------------------------------------------
5) 00FF1D08                                                         
00FF1D08   55               PUSH EBP                                
00FF1D09   8BEC             MOV EBP,ESP                             
00FF1D0B   8B05 247EFF00    MOV EAX,DWORD PTR DS:[FF7E24]           
00FF1D11   8B45 08          MOV EAX,DWORD PTR SS:[EBP+8]            
00FF1D14   5D               POP EBP                                 
00FF1D15   C2 0400          RETN 4                                  
仔细瞧瞧 .. 这个函数在开始的时候push EBP然后在结束的时候Pop EBP,所以这个函数什么都没执行,应该是LockResource或者FreeResource                                                                  
我选择的是: FreeResource       <-- Good Imprec !                      
------------------------------------------------------------------------------------
6) 00FF1D00                                                         
00FF1D00   A1 207EFF00      MOV EAX,DWORD PTR DS:[FF7E20]           
00FF1D05   C3               RETN                                    
这个真有趣 .. 执行这段代码你会看到在EAX中有一个值, 这个值是进程的PID!                        
打开ImpRec在进程列表中你会看到同样的PID值(PID - Process ID)                                        
所以这个是 : GetCurrentProcessId   <-- Good Imprec !                
------------------------------------------------------------------------------------
7) 00FF1CF8                                                         
00FF1CF8   A1 187EFF00      MOV EAX,DWORD PTR DS:[FF7E18]           
00FF1CFD   C3               RETN                                    
在:00FF1CF8和00FF1D00这两个邻近的地址中我只能在00FF1D00这个地址设置断点,发现是: GetCurrentProcessId  
我只能得出一个结论 .. 这个api一定是: GetCurrentProcess
新版本的imprec也把这个地址修复为GetCurrentProcessId...   
在新版本的Imprec的News.txt中说明了:                                  
"出色的disassembler/hew-viewer功能有助于你查看重定向代码. 你不再需要ASProtect修复插件,因为ASProtect修复插件并没有更新,只是做为一个例子放在那儿"                                           问题不是出在ImpRec而是那个插件,我说这些只是为了你能有一些了解。
.. GetCurrentProcessId 也工作的很好...
------------------------------------------------------------------------------------
8) 00FF1D30                                                         
00FF1D30   55               PUSH EBP                                
00FF1D31   8BEC             MOV EBP,ESP                             
00FF1D33   8B05 247EFF00    MOV EAX,DWORD PTR DS:[FF7E24]           
00FF1D39   5D               POP EBP                                 
00FF1D3A   C2 0400          RETN 4                                  
如前所叙,我选的是   : FreeResource    <-- Good Imprec !                       
好了,IAT修复完了 ..           
=============================================                       
如何修复OEP并补上Stolen bytes                         
=============================================                       
用Pe-Editor of ProcDump  Lord-PE把OEP修改为:564BDC 
然后打开Hex-Edit  Hview把OEP处的"00"修改为
Push EBP                                                       
Mov EBP,ESP                                                         
Add ESP,-010                                                        
Mov eax, 05644CC                                                    
到这里aspr的脱壳就完成了...                                      
致谢 :                                                           
=============                                                       
to all the ppl that have helped me lern and beeing there for me ..  
Mackt , ^Daemon^ , VAG , EvilWT , dynm8 , urad0x , parabytes , Jb__ , NeOXQuiCK , SexyGeek , 
ThE-SAiNt                                                           
SAC , NchantA , Eternal Bliss , evaluator , Bengaly , Termin-X , jond .
all the ppl at http://woodmann.cjb.net -  RCE Messageboard's        
and to all the ppl the i forgoted :D