Windbg/cdb - 将慢速条件断点替换为内存补丁(32 位)
Windbg/cdb - Replace a slow conditional breakpoint with an in memory patch (32-bit)
使用 cdb,我有以下条件断点
bp 004bf9f8 ".if (@eax = 0) {.echotime;.echo Breakpoint 004bf9f8;r};gc"
由于这是为了调查一个间歇性问题,它必须保持连接很长一段时间,但条件断点减慢了应用程序的整体性能,我无法做到负担得起。
- 有人知道如何优化条件断点吗?它所做的只是检查
eax=0
。我已经阅读了 some posts explaining 为什么条件断点放慢了这么多,而且这些非常有意义,所以我认为这是一个死胡同。
- 我几乎可以用内存中的图像做我想做的事。是否可以修补一些指令以插入类似
if eax = 0 { <whatever>}
的内容,以便我可以在 <whatever>
? 上添加 unconditional 断点
编辑
根据评论,行动计划是编写如下脚本
.dvalloc <bytes>
和 调用了 补丁
a 004bfa08 JMP patch
a patch MOV eax,esi
a patch+x CMP eax,0
a patch+y JNE 004bfa0a
a patch+z JMP 004bfa0a
现在我应该能够在 patch+z
上添加一个无条件断点来转储我需要的信息而无需停止应用程序。
bp patch+z ".echotime;.echo Breakpoint patch+z;~.;r;!dpx;gc"
编辑 2
以下 POC 在实时调试会话中工作,但仍必须将其转换为不会停止应用程序、等待按键或任何其他操作的脚本。
执行的命令
.dvalloc 1000
a 004bfa00
JMP 0x0c570000
NOP
a 0xc570000
mov edi,edx
mov esi,eax
mov ebp,edi
cmp eax,0
jne 0x004bfa06
jmp 0x004bfa06
bp 0c570011 ".echo Hello World"
执行的命令包括output/context
0:010> .dvalloc 1000
Allocated 1000 bytes starting at 0c570000
0:010> u 004bf9f8 LD
application+0xbf9f8:
004bf9f8 53 push ebx
004bf9f9 56 push esi
004bf9fa 57 push edi
004bf9fb 55 push ebp
004bf9fc 51 push ecx
004bf9fd 890c24 mov dword ptr [esp],ecx
004bfa00 8bfa mov edi,edx |
004bfa02 8bf0 mov esi,eax |-> these get overwritten so repeat in patch
004bfa04 8bef mov ebp,edi |
004bfa06 8bd5 mov edx,ebp
004bfa08 8bc6 mov eax,esi
004bfa0a e8e5feffff call application+0xbf8f4 (004bf8f4)
0:010> a 004bfa00
JMP 0x0c570000
NOP
0:010> u 004bf9f8 LD
application+0xbf9f8:
004bf9f8 53 push ebx
004bf9f9 56 push esi
004bf9fa 57 push edi
004bf9fb 55 push ebp
004bf9fc 51 push ecx
004bf9fd 890c24 mov dword ptr [esp],ecx
004bfa00 e9fb050b0c jmp 0c570000
004bfa05 90 nop
004bfa06 8bd5 mov edx,ebp
004bfa08 8bc6 mov eax,esi
004bfa0a e8e5feffff call application+0xbf8f4 (004bf8f4)
0:010> a 0xc570000
0c570000 mov edi,edx
mov edi,edx
0c570002 mov esi,eax
mov esi,eax
0c570004 mov ebp,edi
mov ebp,edi
0c570006 cmp eax,0
cmp eax,0
0c57000b jne 0x004bfa06
jne 0x004bfa06
0c570011 jmp 0x004bfa06
jmp 0x004bfa06
0c570016
0:010> u 0x0c570000 L6
0c570000 8bfa mov edi,edx
0c570002 8bf0 mov esi,eax
0c570004 8bef mov ebp,edi
0c570006 3d00000000 cmp eax,0
0c57000b 0f85f5f9f4f3 jne application+0xbfa06 (004bfa06)
0c570011 e9f0f9f4f3 jmp application+0xbfa06 (004bfa06)
0:010> bp 0c570011 ".echo Hello World"
编辑 3
手动修补 7 个 运行 可执行文件已成功,但根据 .dvalloc
返回的地址,汇编的 JMP
指令包含不同的指令。我以为这就像从 .dvalloc
获得的地址中减去我们跳转到的地址一样简单,但事实并非如此。
-------------------------------------------------------------
.dvalloc+0x11 |a jmp 004bfa06 |opcode |cd |LE
--------------|---------------|-------|---------|------------
1. 00df0011 |e9f0f96cff |e9 |f0f96cff |ff 6c f9 f0
2. 00e30011 |e9f0f968ff |e9 |f0f968ff |ff 68 f9 f0
3. 00f00011 |e9f0f95bff |e9 |f0f95bff |ff 5b f9 f0
4. 00ff0011 |e9f0f94cff |e9 |f0f94cff |ff 4c f9 f0
5. 093a0011 |e9f0f911f7 |e9 |f0f911f7 |f7 11 f9 f0
6. 0c570011 |e9f0f9f4f3 |e9 |f0f9f4f3 |f3 f4 f9 f0
7. 0ce70011 |e9f0f964f3 |e9 |f0f964f3 |f3 64 f9 f0
-------------------------------------------------------------
第一个f
可能是一个符号位?
编辑 4
毕竟计算很简单,虽然花了我足够长的时间。第一个f
确实是标志。
- 获取要跳转到的地址。就我而言
004bfa06
- 减去
jmp 004bfa06
指令的内存位置的末尾。在我的例子中,它总是 .dvalloc+0x16
(.dvalloc+0x11
是指令的开始)
应用于我的最后一次尝试 (7),得到
004fba01 - 0ce70016 = f3 64 f9 f0.
The instruction to edit the memory at 0ce70011 then becomes e9f0f964f3.
以下是我设置断点的函数序言。 004bfa08 (MOV param_1,ESI)
处的指令是多余的,因为之前 004bfa02 (MOV ESI,param_1)
处的指令可能有用,但我不知道如何从这里开始。
**************************************************************
* FUNCTION *
**************************************************************
int * __register FUN_004bf9f8(int param_1, int param_2,
int * EAX:4 <RETURN>
int EAX:4 param_1
int EDX:4 param_2
int ECX:4 param_3
undefined4 Stack[-0x14]:4 local_14
004bf9f8 53 PUSH EBX
004bf9f9 56 PUSH ESI
004bf9fa 57 PUSH EDI
004bf9fb 55 PUSH EBP
004bf9fc 51 PUSH param_3
004bf9fd 89 0c 24 MOV dword ptr [ESP]=>local_14,param_3
004bfa00 8b fa MOV EDI,param_2
004bfa02 8b f0 MOV ESI,param_1
004bfa04 8b ef MOV EBP,EDI
004bfa06 8b d5 MOV param_2,EBP
004bfa08 8b c6 MOV param_1,ESI
004bfa0a e8 e5 fe CALL FUN_004bf8f4
ff ff
004bfa0f 8b d8 MOV EBX,param_1
这可能很不专业。严重地。我的 x86 汇编程序知识几乎为零。我真的很想看到@blabb 或其他真正了解他的工作的人的回答。无论如何,这是我使用 32 位记事本 (C:\Windows\SysWow64\notepad.exe
) 取得的成果。
$t8 中的新内存地址
0:007> ~0s
0:000> .foreach /pS 5 (addr {.dvalloc 100000}) {r $t8=${addr}}; ? $t8
Evaluate expression: 179699712 = 0ab60000
$t7 中要修补的方法地址
0:000> .foreach /pS 3 (addr {.frame 2}) {r $t7=${addr}}; ? $t7
0:000> .writemem winmain.mem $t7 L200
0:000> u $t7 L7
notepad!WinMain+0x1a3:
007ec4a6 85c0 test eax,eax
007ec4a8 0f8567ffffff jne notepad!WinMain+0x112 (007ec415)
007ec4ae 8d442438 lea eax,[esp+38h]
007ec4b2 50 push eax
007ec4b3 6a00 push 0
007ec4b5 ff3500288000 push dword ptr [notepad!szFileName (00802800)]
007ec4bb ff150c328000 call dword ptr [notepad!_imp__GetFileAttributesExW (0080320c)]
补丁:跳转到新内存。用 NOP 填充它,直到我们结束下一条语句
0:000> eb $t7+0 50 B8
0:000> ed $t7+2 $t8+20
0:000> eb $t7+6 FF E0 58 90 90 90
0:000> u $t7 L7
notepad!WinMain+0x1a3:
007ec4a6 50 push eax // because we need it for the jump
007ec4a7 b80000b60a mov eax,0AB60000h
007ec4ac ffe0 jmp eax
007ec4ae 58 pop eax
007ec4af 90 nop
007ec4b0 90 nop
007ec4b1 90 nop
准备目的地:添加 hello world 代码
0:000> eu $t8 "Hello world!"
0:000> eb $t8+20 B8 00 00 00 00 50 B8
0:000> ed $t8+27 $t8
0:000> eb $t8+2b 50 50 B8 00 00 00 00 50 B8
0:000> ed $t8+34 USER32!MessageBoxW
0:000> eb $t8+38 FF D0 58 58 58 58 58
0:000> u $t8+20 LE
0ab60020 b800000000 mov eax,0 // MB_OK
0ab60025 50 push eax
0ab60026 b80000b60a mov eax,0AB60000h // "Hello world!"
0ab6002b 50 push eax // Text
0ab6002c 50 push eax // Caption
0ab6002d b800000000 mov eax,0 // Desktop HWND
0ab60032 50 push eax
0ab60033 b87013b077 mov eax,offset USER32!MessageBoxW (77b01370)
0ab60038 ffd0 call eax
0ab6003a 58 pop eax // Desktop HWND
0ab6003b 58 pop eax // Caption
0ab6003c 58 pop eax // Text
0ab6003d 58 pop eax // MB_OK
0ab6003e 58 pop eax // EAX which was rescued before jumping here
从 WinMain 添加代码:
0:000> .readmem winmain.mem $t8+3f L200
Reading 200 bytes.
0:000> u $t8+3f L3
0ab6003f 85c0 test eax,eax
0ab60041 0f8567ffffff jne 0ab5ffae
0ab60047 8d442438 lea eax,[esp+38h]
0F85是相对JNE。
007ec4a8 0f8567ffffff jne notepad!WinMain+0x112 (007ec415)
0:000> ? dwo($t8+43)
Evaluate expression: -153 = ffffff67
让我们解决这个问题:
0:000> ed $t8+43 ($t7+2)-($t8+41)+dwo($t8+43)
0:000> u $t8+3f L3
0ab6003f 85c0 test eax,eax
0ab60041 0f85cec3c8f5 jne notepad!WinMain+0x112 (007ec415)
0ab60047 8d442438 lea eax,[esp+38h]
然后回去
0:000> eb $t8+4b 50 B8
0:000> ed $t8+4d $t7+8
0:000> eb $t8+51 ff e0
0:000> u $t8+4b L3
0ab6004b 50 push eax // Because we need it for the jump
0ab6004c b8aec47e00 mov eax,offset notepad!WinMain+0x1ab (007ec4ae)
0ab60051 ffe0 jmp eax
让我们打破看看它发生
0:000> bp $t8+20
0:000> bp $t7
0:000> bl
0 e Disable Clear 0aea0020 0001 (0001) 0:****
1 e Disable Clear 007ec4a6 0001 (0001) 0:**** notepad!WinMain+0x1a3
0:000> g
Breakpoint 1 hit
007ec4a6 50 push eax
0:000> p
007ec4a7 b82000ea0a mov eax,0AEA0020h
0:000> p
007ec4ac ffe0 jmp eax {0aea0020}
0:000> p
Breakpoint 0 hit
0aea0020 b800000000 mov eax,0
0:000> g
Breakpoint 1 hit
007ec4a6 50 push eax
0:000> bd 0
0:000> bd 1
经过一些消息框,我得到
STATUS_STACK_BUFFER_OVERRUN encountered
(2f28.4008): Break instruction exception - code 80000003 (first chance)
我真的不知道为什么。
E9 操作码只能跳转 +- 2GB,所以它在 x64 中有点残废
您可能需要加载暂存器并跳转到那里
.dvalloc 有一个开关 /b,你可以用它指定一个地址,这样你就不必处理 .parsing .dvalloc
x64 assemble 命令在 windbg 中不起作用,您可能需要一个外部工具,如 keystone
keystone is c but python bindings are available (installable from msi)
假设您有 python 和梯形校正
from keystone import *
CODE = b"mov r15,0x200000000;jmp r15"
ks = Ks(KS_ARCH_X86,KS_MODE_64)
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
将输出这 12 个字节(您将损坏 12 个字节,因此您需要保存它们)
0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7
现在这是我绕过的任意函数的完整反汇编
0:000> uf .
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 890424 mov dword ptr [rsp],eax
6 00000001`4000100d 8b05ed1f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`40001013 ffc0 inc eax
6 00000001`40001015 8905e51f0000 mov dword ptr [deto!count (00000001`40003000)],eax
6 00000001`4000101b 8b0424 mov eax,dword ptr [rsp]
7 00000001`4000101e 4883c418 add rsp,18h
7 00000001`40001022 c3 ret
我在地址14000100a绕行
我已经使用 .dvalloc 在 0x200000000 处分配了 0x1000 大小的内存并再次重新组装
0:000> .dvalloc /b 200000000 1000
Allocated 1000 bytes starting at 00000002`00000000
0:000> eb 14000100a 0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7
0:000> uf .
Flow analysis was incomplete, some code may be missing
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 49bf0000000002000000 mov r15,200000000h
6 00000001`40001014 41ffe7 jmp r15
assemble 您在 0x200000000 中的逻辑恢复损坏的字节并 return 回流
如果 return 地址在一条指令的中间,您可能需要使用正确的地址
对于上面显示的代码,您可能必须 return 返回到 14000101b,因为仅损坏 12 个字节将损坏指令 140001015 处的第一个字节
添加另一个答案
我们将在 vs2017 社区中使用编译为 x64 的源代码
我们的任务是仅在 eax = 0x1337 时中断,没有任何条件 bps
cl /Zi /W4 /analyze /EHsc /Od /nologo patch.cpp /link /release /entry:main /subsystem:windows
来源
#include <windows.h>
#pragma comment(lib,"kernel32.lib")
int count = 0;
int somefunc()
{
return count++;
}
int main(void)
{
for (;;)
{
somefunc();
Sleep(2);
}
}
函数somefunc()反汇编如下
F:\src\deto>cdb -c "uf deto!somefunc;q" deto.exe | f:\git\usr\bin\awk.exe "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf deto!somefunc;q'
deto!somefunc:
00000001`40001000 4883ec18 sub rsp,18h
00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
00000001`4000100a 890424 mov dword ptr [rsp],eax
00000001`4000100d 8b05ed1f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
00000001`40001013 ffc0 inc eax
00000001`40001015 8905e51f0000 mov dword ptr [deto!count (00000001`40003000)],eax
00000001`4000101b 8b0424 mov eax,dword ptr [rsp]
00000001`4000101e 4883c418 add rsp,18h
00000001`40001022 c3 ret
quit:
我将使用这个 JavaScript
在三个地方编辑字节
function log(a)
{
host.diagnostics.debugLog( a + '\n');
}
function exec (cmdstr)
{
return host.namespace.Debugger.Utility.Control.ExecuteCommand(cmdstr);
}
function patch()
{
log("hi")
exec(".dvalloc /b 0x200000000 1000")
exec("eb 0x14000100a 0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7")
exec("eb 0x200000000 0x3d 0x37 0x13 0x0 0x0 0x49 0xbf 0x0 0x8 0x0 0x0 0x2 0x0 0x0 0x0 0x74 0x25 0x36 0x89 0x4 0x24 0xa1 0x0 0x30 0x0 0x40 0x1 0x0 0x0 0x0 0xff 0xc0 0xa3 0x0 0x30 0x0 0x40 0x1 0x0 0x0 0x0 0x49 0xbf 0x1b 0x10 0x0 0x40 0x1 0x0 0x0 0x0 0x41 0xff 0xe7 0x41 0xff 0xe7")
exec("eb 0x200000800 0xcc")
}
要修补的字节取自此 python 脚本
from keystone import *
print ("assemble at 0x14000100a\n")
CODE = b"mov r15,0x200000000;jmp r15"
ks = Ks(KS_ARCH_X86,KS_MODE_64)
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
print ("Assemble at 0x200000000\n")
CODE = b"cmp eax,0x1337;\
mov r15,0x200000800;\
je here;\
mov dword ptr ss:[rsp],eax;\
mov eax,dword ptr ds:[0x140003000];\
inc eax;\
mov dword ptr ds:[0x140003000],eax;\
mov r15, 0x14000101b;\
jmp r15;\
here:;\
jmp r15;"
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
print ("Assemble at 0x200000800 an int 3 aka 0xcc")
在0x200000800加载了打补丁的windbg中的exe并执行破解
Microsoft (R) Windows Debugger Version 10.0.17763.132 AMD64
ntdll!LdrpDoDebuggerBreak+0x30:
00007ff9`b33f121c cc int 3
0:000> .scriptload f:\wdscr\patch.js
JavaScript script successfully loaded from 'f:\wdscr\patch.js'
0:000> dx @$scriptContents.patch()
hi
@$scriptContents.patch()
0:000> uf deto!somefunc
Flow analysis was incomplete, some code may be missing
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 49bf0000000002000000 mov r15,200000000h <<<<<<<<<<<
6 00000001`40001014 41ffe7 jmp r15 <<<<<<<<<<<<<<<<<<<
0:000> g
(3aec.405c): Break instruction exception - code 80000003 (first chance)
00000002`00000800 cc int 3 <<<<<<<<<<<<<<
0:000> r eax
eax=1337
致谢
非常感谢 blabb 和 Thomas 提供的帮助、耐心和示例。如果有人偶然发现这个问题并从中得到帮助,请给他们投票!
具体到我的情况,这些是修补 0x004bf9f8
处的函数所需的手动操作,以跳转到新创建的补丁,我可以在其中测试 eax 是否为 nil 并设置无条件断点。
手动操作
这行得通,但仍然很麻烦。父应用程序生成我需要修补的应用程序的子应用程序,因此这需要相当多的努力,并且存在输入错误或遗漏应用程序的风险。
* Make a note of the address returned (pe. 0x00df0000)
.dvalloc 100
* Replace all <patch> by previous returned address. Copy/past following 3 lines (+enter)
a 004bfa00
JMP <patch>
NOP
* Copy/paste following 7 lines (+enter)
a <patch>
mov edi,edx
mov esi,eax
mov ebp,edi
cmp eax,0
jne 0x004bfa06
jmp 0x004bfa06
* Copy/paste following line 1 in cdb
bp <patch>+0x11 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r!dpx};gc"
* Verify
u 004bf9f8 LD
u <patch> LD
手动操作脚本
同样,如果没有提供的帮助,我将不知道从哪里开始。
以下是尝试编写手动操作脚本的问题
Windbg/cdb中的a
命令不能在脚本中使用(免责声明:非常确定)
没有a
命令,我不得不求助于编辑内存并查找所需的操作码
计算相对跳跃
带有最少注释的脚本
* Make note of returned address and store in $t8
.foreach /pS 5 (patch {.dvalloc 100}) {r $t8=${patch}}; ? $t8
* Patch the function to jump to $t8
r $t9=$t8-(0x004bfa00+0x05)
eb 0x004bfa00 e9
ed 0x004bfa01 $t9
eb 0x004bfa05 90
* Create the patch at $t8
ew $t8+0x00 fa8b
ew $t8+0x02 f08b
ew $t8+0x04 ef8b
eb $t8+0x06 3d
ed $t8+0x07 00000000
r $t9=0x004bfa06-($t8+0x11)
ew $t8+0x0b 850f
ed $t8+0x0d $t9
r $t9=0x004bfa06-($t8+0x16)
eb $t8+0x11 e9
ed $t8+0x12 $t9
* Set the unconditional breakpoint
bp $t8 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r;!dpx};gc"
* Verify
u 004bf9f8 LD
u $t8 LD
脚本有点过分评论
***** Allocate 100 bytes of memory to hold our patch
* Remember the start adress in the $t8 pseudo register
.foreach /pS 5 (patch {.dvalloc 100}) {r $t8=${patch}}; ? $t8
***** Patch the function to jump to $t8
* Jump to our patch when entering function 0x004bf948 to test for nil pointer
* 1 0x004bfa00 e9<offset> jmp <patch> ($t8 LE)
* 2 0x004bfa05 90 nop
***** 1 0x004bfa00 e9<offset> jmp <patch> ($t8 LE)
* r $t9 the size of the jump from 0x004bfa00+0x05 to $t8
* eb e9 is the opcode for JMP
* ed append with offset where to jump to
r $t9=$t8-(0x004bfa00+0x05)
eb 0x004bfa00 e9
ed 0x004bfa01 $t9
***** 2 0x004bfa05 90 nop
* eb 90 is the opcode for NOP
eb 0x004bfa05 90
***** Create the patch at $t8
* Repeat the replaced code at 0x004bfa00 used to jump to our new address
* Add a compare with nil
* Jump back to where we left off (0x004bfa06)
* 1 0x00000000 8bfa mov edi,edx
* 2 0x00000002 8bf0 mov esi,eax
* 3 0x00000004 8bef mov ebp,edi
* 4 0x00000006 3d00000000 cmp eax,0
* 5 0x0000000b 0f85<offset> jne app+0xbfa06 (004bfa06)
* 6 0x00000011 e9<offset> jmp app+0xbfa06 (004bfa06)
***** 1 0x0000000 mov edi,edx
* ew 8b is the opcode for MOV
* fa is the opcode for ebp,edi
ew $t8+0x00 fa8b
***** 2 0x0000002 mov esi,eax
* ew 8b is the opcode for MOV
* f0 is the opcode for esi,eax
ew $t8+0x02 f08b
***** 3 0x0000004 mov ebp,edi
* ew 8b is the opcode for MOV
* ef is the opcode for ebp,edi
ew $t8+0x04 ef8b
***** 4 0x0000006 cmp eax,0
* eb 3d is the opcode for JNE
* ed append with what to compare with
eb $t8+0x06 3d
ed $t8+0x07 00000000
***** 5 0x000000b jne app+0xbfa06 (004bfa06)
* r $t9 the size of the jump from $t8+11 to 0x004bfa06
* ew 0f 85 is the opcode for JNE
* ed append with offset where to jump to
r $t9=0x004bfa06-($t8+0x11)
ew $t8+0x0b 850f
ed $t8+0x0d $t9
***** 6 jmp app+0xbfa06 (004bfa06)
* r $t9 the size of the jump from $t8+16 to 0x004bfa06
* eb e9 is the opcode for JMP
* ed append with offset where to jump to
r $t9=0x004bfa06-($t8+0x16)
eb $t8+0x11 e9
ed $t8+0x12 $t9
***** Conditional Breakpoint/Log on address $t8
bp $t8+0x11 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r;!dpx};gc"
***** Verify
u 004bf9f8 LD
u $t8 LD
使用 cdb,我有以下条件断点
bp 004bf9f8 ".if (@eax = 0) {.echotime;.echo Breakpoint 004bf9f8;r};gc"
由于这是为了调查一个间歇性问题,它必须保持连接很长一段时间,但条件断点减慢了应用程序的整体性能,我无法做到负担得起。
- 有人知道如何优化条件断点吗?它所做的只是检查
eax=0
。我已经阅读了 some posts explaining 为什么条件断点放慢了这么多,而且这些非常有意义,所以我认为这是一个死胡同。 - 我几乎可以用内存中的图像做我想做的事。是否可以修补一些指令以插入类似
if eax = 0 { <whatever>}
的内容,以便我可以在<whatever>
? 上添加 unconditional 断点
编辑
根据评论,行动计划是编写如下脚本
.dvalloc <bytes>
和
a 004bfa08 JMP patch
a patch MOV eax,esi
a patch+x CMP eax,0
a patch+y JNE 004bfa0a
a patch+z JMP 004bfa0a
现在我应该能够在 patch+z
上添加一个无条件断点来转储我需要的信息而无需停止应用程序。
bp patch+z ".echotime;.echo Breakpoint patch+z;~.;r;!dpx;gc"
编辑 2
以下 POC 在实时调试会话中工作,但仍必须将其转换为不会停止应用程序、等待按键或任何其他操作的脚本。
执行的命令
.dvalloc 1000
a 004bfa00
JMP 0x0c570000
NOP
a 0xc570000
mov edi,edx
mov esi,eax
mov ebp,edi
cmp eax,0
jne 0x004bfa06
jmp 0x004bfa06
bp 0c570011 ".echo Hello World"
执行的命令包括output/context
0:010> .dvalloc 1000
Allocated 1000 bytes starting at 0c570000
0:010> u 004bf9f8 LD
application+0xbf9f8:
004bf9f8 53 push ebx
004bf9f9 56 push esi
004bf9fa 57 push edi
004bf9fb 55 push ebp
004bf9fc 51 push ecx
004bf9fd 890c24 mov dword ptr [esp],ecx
004bfa00 8bfa mov edi,edx |
004bfa02 8bf0 mov esi,eax |-> these get overwritten so repeat in patch
004bfa04 8bef mov ebp,edi |
004bfa06 8bd5 mov edx,ebp
004bfa08 8bc6 mov eax,esi
004bfa0a e8e5feffff call application+0xbf8f4 (004bf8f4)
0:010> a 004bfa00
JMP 0x0c570000
NOP
0:010> u 004bf9f8 LD
application+0xbf9f8:
004bf9f8 53 push ebx
004bf9f9 56 push esi
004bf9fa 57 push edi
004bf9fb 55 push ebp
004bf9fc 51 push ecx
004bf9fd 890c24 mov dword ptr [esp],ecx
004bfa00 e9fb050b0c jmp 0c570000
004bfa05 90 nop
004bfa06 8bd5 mov edx,ebp
004bfa08 8bc6 mov eax,esi
004bfa0a e8e5feffff call application+0xbf8f4 (004bf8f4)
0:010> a 0xc570000
0c570000 mov edi,edx
mov edi,edx
0c570002 mov esi,eax
mov esi,eax
0c570004 mov ebp,edi
mov ebp,edi
0c570006 cmp eax,0
cmp eax,0
0c57000b jne 0x004bfa06
jne 0x004bfa06
0c570011 jmp 0x004bfa06
jmp 0x004bfa06
0c570016
0:010> u 0x0c570000 L6
0c570000 8bfa mov edi,edx
0c570002 8bf0 mov esi,eax
0c570004 8bef mov ebp,edi
0c570006 3d00000000 cmp eax,0
0c57000b 0f85f5f9f4f3 jne application+0xbfa06 (004bfa06)
0c570011 e9f0f9f4f3 jmp application+0xbfa06 (004bfa06)
0:010> bp 0c570011 ".echo Hello World"
编辑 3
手动修补 7 个 运行 可执行文件已成功,但根据 .dvalloc
返回的地址,汇编的 JMP
指令包含不同的指令。我以为这就像从 .dvalloc
获得的地址中减去我们跳转到的地址一样简单,但事实并非如此。
-------------------------------------------------------------
.dvalloc+0x11 |a jmp 004bfa06 |opcode |cd |LE
--------------|---------------|-------|---------|------------
1. 00df0011 |e9f0f96cff |e9 |f0f96cff |ff 6c f9 f0
2. 00e30011 |e9f0f968ff |e9 |f0f968ff |ff 68 f9 f0
3. 00f00011 |e9f0f95bff |e9 |f0f95bff |ff 5b f9 f0
4. 00ff0011 |e9f0f94cff |e9 |f0f94cff |ff 4c f9 f0
5. 093a0011 |e9f0f911f7 |e9 |f0f911f7 |f7 11 f9 f0
6. 0c570011 |e9f0f9f4f3 |e9 |f0f9f4f3 |f3 f4 f9 f0
7. 0ce70011 |e9f0f964f3 |e9 |f0f964f3 |f3 64 f9 f0
-------------------------------------------------------------
第一个f
可能是一个符号位?
编辑 4
毕竟计算很简单,虽然花了我足够长的时间。第一个f
确实是标志。
- 获取要跳转到的地址。就我而言
004bfa06
- 减去
jmp 004bfa06
指令的内存位置的末尾。在我的例子中,它总是.dvalloc+0x16
(.dvalloc+0x11
是指令的开始)
应用于我的最后一次尝试 (7),得到
004fba01 - 0ce70016 = f3 64 f9 f0.
The instruction to edit the memory at 0ce70011 then becomes e9f0f964f3.
以下是我设置断点的函数序言。 004bfa08 (MOV param_1,ESI)
处的指令是多余的,因为之前 004bfa02 (MOV ESI,param_1)
处的指令可能有用,但我不知道如何从这里开始。
**************************************************************
* FUNCTION *
**************************************************************
int * __register FUN_004bf9f8(int param_1, int param_2,
int * EAX:4 <RETURN>
int EAX:4 param_1
int EDX:4 param_2
int ECX:4 param_3
undefined4 Stack[-0x14]:4 local_14
004bf9f8 53 PUSH EBX
004bf9f9 56 PUSH ESI
004bf9fa 57 PUSH EDI
004bf9fb 55 PUSH EBP
004bf9fc 51 PUSH param_3
004bf9fd 89 0c 24 MOV dword ptr [ESP]=>local_14,param_3
004bfa00 8b fa MOV EDI,param_2
004bfa02 8b f0 MOV ESI,param_1
004bfa04 8b ef MOV EBP,EDI
004bfa06 8b d5 MOV param_2,EBP
004bfa08 8b c6 MOV param_1,ESI
004bfa0a e8 e5 fe CALL FUN_004bf8f4
ff ff
004bfa0f 8b d8 MOV EBX,param_1
这可能很不专业。严重地。我的 x86 汇编程序知识几乎为零。我真的很想看到@blabb 或其他真正了解他的工作的人的回答。无论如何,这是我使用 32 位记事本 (C:\Windows\SysWow64\notepad.exe
) 取得的成果。
$t8 中的新内存地址
0:007> ~0s
0:000> .foreach /pS 5 (addr {.dvalloc 100000}) {r $t8=${addr}}; ? $t8
Evaluate expression: 179699712 = 0ab60000
$t7 中要修补的方法地址
0:000> .foreach /pS 3 (addr {.frame 2}) {r $t7=${addr}}; ? $t7
0:000> .writemem winmain.mem $t7 L200
0:000> u $t7 L7
notepad!WinMain+0x1a3:
007ec4a6 85c0 test eax,eax
007ec4a8 0f8567ffffff jne notepad!WinMain+0x112 (007ec415)
007ec4ae 8d442438 lea eax,[esp+38h]
007ec4b2 50 push eax
007ec4b3 6a00 push 0
007ec4b5 ff3500288000 push dword ptr [notepad!szFileName (00802800)]
007ec4bb ff150c328000 call dword ptr [notepad!_imp__GetFileAttributesExW (0080320c)]
补丁:跳转到新内存。用 NOP 填充它,直到我们结束下一条语句
0:000> eb $t7+0 50 B8
0:000> ed $t7+2 $t8+20
0:000> eb $t7+6 FF E0 58 90 90 90
0:000> u $t7 L7
notepad!WinMain+0x1a3:
007ec4a6 50 push eax // because we need it for the jump
007ec4a7 b80000b60a mov eax,0AB60000h
007ec4ac ffe0 jmp eax
007ec4ae 58 pop eax
007ec4af 90 nop
007ec4b0 90 nop
007ec4b1 90 nop
准备目的地:添加 hello world 代码
0:000> eu $t8 "Hello world!"
0:000> eb $t8+20 B8 00 00 00 00 50 B8
0:000> ed $t8+27 $t8
0:000> eb $t8+2b 50 50 B8 00 00 00 00 50 B8
0:000> ed $t8+34 USER32!MessageBoxW
0:000> eb $t8+38 FF D0 58 58 58 58 58
0:000> u $t8+20 LE
0ab60020 b800000000 mov eax,0 // MB_OK
0ab60025 50 push eax
0ab60026 b80000b60a mov eax,0AB60000h // "Hello world!"
0ab6002b 50 push eax // Text
0ab6002c 50 push eax // Caption
0ab6002d b800000000 mov eax,0 // Desktop HWND
0ab60032 50 push eax
0ab60033 b87013b077 mov eax,offset USER32!MessageBoxW (77b01370)
0ab60038 ffd0 call eax
0ab6003a 58 pop eax // Desktop HWND
0ab6003b 58 pop eax // Caption
0ab6003c 58 pop eax // Text
0ab6003d 58 pop eax // MB_OK
0ab6003e 58 pop eax // EAX which was rescued before jumping here
从 WinMain 添加代码:
0:000> .readmem winmain.mem $t8+3f L200
Reading 200 bytes.
0:000> u $t8+3f L3
0ab6003f 85c0 test eax,eax
0ab60041 0f8567ffffff jne 0ab5ffae
0ab60047 8d442438 lea eax,[esp+38h]
0F85是相对JNE。
007ec4a8 0f8567ffffff jne notepad!WinMain+0x112 (007ec415)
0:000> ? dwo($t8+43)
Evaluate expression: -153 = ffffff67
让我们解决这个问题:
0:000> ed $t8+43 ($t7+2)-($t8+41)+dwo($t8+43)
0:000> u $t8+3f L3
0ab6003f 85c0 test eax,eax
0ab60041 0f85cec3c8f5 jne notepad!WinMain+0x112 (007ec415)
0ab60047 8d442438 lea eax,[esp+38h]
然后回去
0:000> eb $t8+4b 50 B8
0:000> ed $t8+4d $t7+8
0:000> eb $t8+51 ff e0
0:000> u $t8+4b L3
0ab6004b 50 push eax // Because we need it for the jump
0ab6004c b8aec47e00 mov eax,offset notepad!WinMain+0x1ab (007ec4ae)
0ab60051 ffe0 jmp eax
让我们打破看看它发生
0:000> bp $t8+20
0:000> bp $t7
0:000> bl
0 e Disable Clear 0aea0020 0001 (0001) 0:****
1 e Disable Clear 007ec4a6 0001 (0001) 0:**** notepad!WinMain+0x1a3
0:000> g
Breakpoint 1 hit
007ec4a6 50 push eax
0:000> p
007ec4a7 b82000ea0a mov eax,0AEA0020h
0:000> p
007ec4ac ffe0 jmp eax {0aea0020}
0:000> p
Breakpoint 0 hit
0aea0020 b800000000 mov eax,0
0:000> g
Breakpoint 1 hit
007ec4a6 50 push eax
0:000> bd 0
0:000> bd 1
经过一些消息框,我得到
STATUS_STACK_BUFFER_OVERRUN encountered
(2f28.4008): Break instruction exception - code 80000003 (first chance)
我真的不知道为什么。
E9 操作码只能跳转 +- 2GB,所以它在 x64 中有点残废
您可能需要加载暂存器并跳转到那里
.dvalloc 有一个开关 /b,你可以用它指定一个地址,这样你就不必处理 .parsing .dvalloc
x64 assemble 命令在 windbg 中不起作用,您可能需要一个外部工具,如 keystone
keystone is c but python bindings are available (installable from msi)
假设您有 python 和梯形校正
from keystone import *
CODE = b"mov r15,0x200000000;jmp r15"
ks = Ks(KS_ARCH_X86,KS_MODE_64)
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
将输出这 12 个字节(您将损坏 12 个字节,因此您需要保存它们)
0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7
现在这是我绕过的任意函数的完整反汇编
0:000> uf .
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 890424 mov dword ptr [rsp],eax
6 00000001`4000100d 8b05ed1f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`40001013 ffc0 inc eax
6 00000001`40001015 8905e51f0000 mov dword ptr [deto!count (00000001`40003000)],eax
6 00000001`4000101b 8b0424 mov eax,dword ptr [rsp]
7 00000001`4000101e 4883c418 add rsp,18h
7 00000001`40001022 c3 ret
我在地址14000100a绕行 我已经使用 .dvalloc 在 0x200000000 处分配了 0x1000 大小的内存并再次重新组装
0:000> .dvalloc /b 200000000 1000
Allocated 1000 bytes starting at 00000002`00000000
0:000> eb 14000100a 0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7
0:000> uf .
Flow analysis was incomplete, some code may be missing
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 49bf0000000002000000 mov r15,200000000h
6 00000001`40001014 41ffe7 jmp r15
assemble 您在 0x200000000 中的逻辑恢复损坏的字节并 return 回流 如果 return 地址在一条指令的中间,您可能需要使用正确的地址
对于上面显示的代码,您可能必须 return 返回到 14000101b,因为仅损坏 12 个字节将损坏指令 140001015 处的第一个字节
添加另一个答案
我们将在 vs2017 社区中使用编译为 x64 的源代码
我们的任务是仅在 eax = 0x1337 时中断,没有任何条件 bps
cl /Zi /W4 /analyze /EHsc /Od /nologo patch.cpp /link /release /entry:main /subsystem:windows
来源
#include <windows.h>
#pragma comment(lib,"kernel32.lib")
int count = 0;
int somefunc()
{
return count++;
}
int main(void)
{
for (;;)
{
somefunc();
Sleep(2);
}
}
函数somefunc()反汇编如下
F:\src\deto>cdb -c "uf deto!somefunc;q" deto.exe | f:\git\usr\bin\awk.exe "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf deto!somefunc;q'
deto!somefunc:
00000001`40001000 4883ec18 sub rsp,18h
00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
00000001`4000100a 890424 mov dword ptr [rsp],eax
00000001`4000100d 8b05ed1f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
00000001`40001013 ffc0 inc eax
00000001`40001015 8905e51f0000 mov dword ptr [deto!count (00000001`40003000)],eax
00000001`4000101b 8b0424 mov eax,dword ptr [rsp]
00000001`4000101e 4883c418 add rsp,18h
00000001`40001022 c3 ret
quit:
我将使用这个 JavaScript
在三个地方编辑字节function log(a)
{
host.diagnostics.debugLog( a + '\n');
}
function exec (cmdstr)
{
return host.namespace.Debugger.Utility.Control.ExecuteCommand(cmdstr);
}
function patch()
{
log("hi")
exec(".dvalloc /b 0x200000000 1000")
exec("eb 0x14000100a 0x49 0xbf 0x0 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x41 0xff 0xe7")
exec("eb 0x200000000 0x3d 0x37 0x13 0x0 0x0 0x49 0xbf 0x0 0x8 0x0 0x0 0x2 0x0 0x0 0x0 0x74 0x25 0x36 0x89 0x4 0x24 0xa1 0x0 0x30 0x0 0x40 0x1 0x0 0x0 0x0 0xff 0xc0 0xa3 0x0 0x30 0x0 0x40 0x1 0x0 0x0 0x0 0x49 0xbf 0x1b 0x10 0x0 0x40 0x1 0x0 0x0 0x0 0x41 0xff 0xe7 0x41 0xff 0xe7")
exec("eb 0x200000800 0xcc")
}
要修补的字节取自此 python 脚本
from keystone import *
print ("assemble at 0x14000100a\n")
CODE = b"mov r15,0x200000000;jmp r15"
ks = Ks(KS_ARCH_X86,KS_MODE_64)
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
print ("Assemble at 0x200000000\n")
CODE = b"cmp eax,0x1337;\
mov r15,0x200000800;\
je here;\
mov dword ptr ss:[rsp],eax;\
mov eax,dword ptr ds:[0x140003000];\
inc eax;\
mov dword ptr ds:[0x140003000],eax;\
mov r15, 0x14000101b;\
jmp r15;\
here:;\
jmp r15;"
encoding,count = ks.asm(CODE)
for i in encoding:
print(hex(i),end = " ")
print("\n")
print ("Assemble at 0x200000800 an int 3 aka 0xcc")
在0x200000800加载了打补丁的windbg中的exe并执行破解
Microsoft (R) Windows Debugger Version 10.0.17763.132 AMD64
ntdll!LdrpDoDebuggerBreak+0x30:
00007ff9`b33f121c cc int 3
0:000> .scriptload f:\wdscr\patch.js
JavaScript script successfully loaded from 'f:\wdscr\patch.js'
0:000> dx @$scriptContents.patch()
hi
@$scriptContents.patch()
0:000> uf deto!somefunc
Flow analysis was incomplete, some code may be missing
deto!somefunc [f:\src\deto\deto.cpp @ 5]:
5 00000001`40001000 4883ec18 sub rsp,18h
6 00000001`40001004 8b05f61f0000 mov eax,dword ptr [deto!count (00000001`40003000)]
6 00000001`4000100a 49bf0000000002000000 mov r15,200000000h <<<<<<<<<<<
6 00000001`40001014 41ffe7 jmp r15 <<<<<<<<<<<<<<<<<<<
0:000> g
(3aec.405c): Break instruction exception - code 80000003 (first chance)
00000002`00000800 cc int 3 <<<<<<<<<<<<<<
0:000> r eax
eax=1337
致谢
非常感谢 blabb 和 Thomas 提供的帮助、耐心和示例
具体到我的情况,这些是修补 0x004bf9f8
处的函数所需的手动操作,以跳转到新创建的补丁,我可以在其中测试 eax 是否为 nil 并设置无条件断点。
手动操作
这行得通,但仍然很麻烦。父应用程序生成我需要修补的应用程序的子应用程序,因此这需要相当多的努力,并且存在输入错误或遗漏应用程序的风险。
* Make a note of the address returned (pe. 0x00df0000)
.dvalloc 100
* Replace all <patch> by previous returned address. Copy/past following 3 lines (+enter)
a 004bfa00
JMP <patch>
NOP
* Copy/paste following 7 lines (+enter)
a <patch>
mov edi,edx
mov esi,eax
mov ebp,edi
cmp eax,0
jne 0x004bfa06
jmp 0x004bfa06
* Copy/paste following line 1 in cdb
bp <patch>+0x11 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r!dpx};gc"
* Verify
u 004bf9f8 LD
u <patch> LD
手动操作脚本
同样,如果没有提供的帮助,我将不知道从哪里开始。 以下是尝试编写手动操作脚本的问题
Windbg/cdb中的
a
命令不能在脚本中使用(免责声明:非常确定)没有
a
命令,我不得不求助于编辑内存并查找所需的操作码计算相对跳跃
带有最少注释的脚本
* Make note of returned address and store in $t8
.foreach /pS 5 (patch {.dvalloc 100}) {r $t8=${patch}}; ? $t8
* Patch the function to jump to $t8
r $t9=$t8-(0x004bfa00+0x05)
eb 0x004bfa00 e9
ed 0x004bfa01 $t9
eb 0x004bfa05 90
* Create the patch at $t8
ew $t8+0x00 fa8b
ew $t8+0x02 f08b
ew $t8+0x04 ef8b
eb $t8+0x06 3d
ed $t8+0x07 00000000
r $t9=0x004bfa06-($t8+0x11)
ew $t8+0x0b 850f
ed $t8+0x0d $t9
r $t9=0x004bfa06-($t8+0x16)
eb $t8+0x11 e9
ed $t8+0x12 $t9
* Set the unconditional breakpoint
bp $t8 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r;!dpx};gc"
* Verify
u 004bf9f8 LD
u $t8 LD
脚本有点过分评论
***** Allocate 100 bytes of memory to hold our patch
* Remember the start adress in the $t8 pseudo register
.foreach /pS 5 (patch {.dvalloc 100}) {r $t8=${patch}}; ? $t8
***** Patch the function to jump to $t8
* Jump to our patch when entering function 0x004bf948 to test for nil pointer
* 1 0x004bfa00 e9<offset> jmp <patch> ($t8 LE)
* 2 0x004bfa05 90 nop
***** 1 0x004bfa00 e9<offset> jmp <patch> ($t8 LE)
* r $t9 the size of the jump from 0x004bfa00+0x05 to $t8
* eb e9 is the opcode for JMP
* ed append with offset where to jump to
r $t9=$t8-(0x004bfa00+0x05)
eb 0x004bfa00 e9
ed 0x004bfa01 $t9
***** 2 0x004bfa05 90 nop
* eb 90 is the opcode for NOP
eb 0x004bfa05 90
***** Create the patch at $t8
* Repeat the replaced code at 0x004bfa00 used to jump to our new address
* Add a compare with nil
* Jump back to where we left off (0x004bfa06)
* 1 0x00000000 8bfa mov edi,edx
* 2 0x00000002 8bf0 mov esi,eax
* 3 0x00000004 8bef mov ebp,edi
* 4 0x00000006 3d00000000 cmp eax,0
* 5 0x0000000b 0f85<offset> jne app+0xbfa06 (004bfa06)
* 6 0x00000011 e9<offset> jmp app+0xbfa06 (004bfa06)
***** 1 0x0000000 mov edi,edx
* ew 8b is the opcode for MOV
* fa is the opcode for ebp,edi
ew $t8+0x00 fa8b
***** 2 0x0000002 mov esi,eax
* ew 8b is the opcode for MOV
* f0 is the opcode for esi,eax
ew $t8+0x02 f08b
***** 3 0x0000004 mov ebp,edi
* ew 8b is the opcode for MOV
* ef is the opcode for ebp,edi
ew $t8+0x04 ef8b
***** 4 0x0000006 cmp eax,0
* eb 3d is the opcode for JNE
* ed append with what to compare with
eb $t8+0x06 3d
ed $t8+0x07 00000000
***** 5 0x000000b jne app+0xbfa06 (004bfa06)
* r $t9 the size of the jump from $t8+11 to 0x004bfa06
* ew 0f 85 is the opcode for JNE
* ed append with offset where to jump to
r $t9=0x004bfa06-($t8+0x11)
ew $t8+0x0b 850f
ed $t8+0x0d $t9
***** 6 jmp app+0xbfa06 (004bfa06)
* r $t9 the size of the jump from $t8+16 to 0x004bfa06
* eb e9 is the opcode for JMP
* ed append with offset where to jump to
r $t9=0x004bfa06-($t8+0x16)
eb $t8+0x11 e9
ed $t8+0x12 $t9
***** Conditional Breakpoint/Log on address $t8
bp $t8+0x11 ".echo Breakpoint 004bf9f8 Nil Pointer Unconditional Check;r;!dpx};gc"
***** Verify
u 004bf9f8 LD
u $t8 LD