1731101646
2024-11-08 17:43:00
ある顧客は、プログラムが最初の命令で失敗したことを示すクラッシュ レポートに困惑していました。
クラッシュ ダンプの 1 つを開いたところ、それは非常に奇妙で、デバッガーは何が問題なのかさえ判断できませんでした。
ERROR: Unable to find system thread FFFFFFFF
ERROR: The thread being debugged has either exited or cannot be accessed
ERROR: Many commands will not work properly
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
ERROR: Exception C0000005 occurred on unknown thread FFFFFFFF
(61c.ffffffff): Access violation - code c0000005 (first/second chance not available)
0:???> r
WARNING: The debugger does not have a current process or thread
WARNING: Many commands will not work
^ Illegal thread error in 'r'
0:???> .ecxr
WARNING: The debugger does not have a current process or thread
WARNING: Many commands will not work
0:???>
どのようなスレッドがあるかを見てみましょう。
0:???> ~ WARNING: The debugger does not have a current process or thread WARNING: Many commands will not work 0 Id: 61c.12b4 Suspend: 1 Teb: 000000c7`9604d000 Unfrozen 1 Id: 61c.22d4 Suspend: 1 Teb: 000000c7`9604f000 Unfrozen 2 Id: 61c.1ab0 Suspend: 1 Teb: 000000c7`96051000 Unfrozen 3 Id: 61c.3308 Suspend: 1 Teb: 000000c7`96053000 Unfrozen 4 Id: 61c.2af0 Suspend: 1 Teb: 000000c7`96055000 Unfrozen 5 Id: 61c.2054 Suspend: 1 Teb: 000000c7`96059000 Unfrozen 0:???>
彼らは何をしているのだろうか。
各スレッドに切り替えて、どのような命令が行われているかを確認します。
0:???> ~0s
WARNING: The debugger does not have a current process or thread
WARNING: Many commands will not work
ntdll!RtlUserThreadStart:
00007ffa`bb16df50 4883ec78 sub rsp,78h
0:000> ~*s
^ Illegal thread error in '~*s'
0:000> ~1s
00000293`42074058 66894340 mov word ptr [rbx+40h],ax ds:00007ff6`e4600040=1f0e
0:001> ~2s
ntdll!ZwWaitForWorkViaWorkerFactory+0x14:
00007ffa`bb1b29c4 c3 ret
0:002> ~3s
ntdll!ZwWaitForWorkViaWorkerFactory+0x14:
00007ffa`bb1b29c4 c3 ret
0:003> ~4s
ntdll!ZwWaitForWorkViaWorkerFactory+0x14:
00007ffa`bb1b29c4 c3 ret
0:004> ~5s
ntdll!ZwDelayExecution+0x14:
00007ffa`bb1af3f4 c3 ret
クラッシュの表向きの理由は無効な書き込み命令であり、スレッド 1 のみが書き込みを行っています。何を書き込もうとしているのかを詳しく見てみましょう。
0:001> !address @rbx Usage: Image Base Address: 00007ff6`e4600000 End Address: 00007ff6`e4601000 Region Size: 00000000`00001000 ( 4.000 kB) State: 00001000 MEM_COMMIT Protect: 00000002 PAGE_READONLY Type: 01000000 MEM_IMAGE Allocation Base: 00007ff6`e4600000 Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY Image Path: C:Program FilesContosoContosoDeluxe.exe Module Name: ContosoDeluxe Loaded Image Name: ContosoDeluxe.exe Mapped Image Name: C:Program FilesContosoContosoDeluxe.exe More info: lmv m ContosoDeluxe More info: !lmi ContosoDeluxe More info: ln 0x7ff6e4600000 More info: !dh 0x7ff6e4600000 Content source: 2 (mapped), length: 400 0:001> ln @rbx (00000000`00000000) ContosoDeluxe!__ImageBase
さて、ContosoDeluxe 自体のマップされたイメージ ヘッダーに書き込んでいます。これは読み取り専用ページです (PAGE_)、これが書き込みアクセス違反を考慮する理由です。
実際、私たちは画像ヘッダーに書き込みを行っていますが、これは通常誰も行うことではありません。これはかなり疑わしいようです。
スタックを要求すると、次のようになります。
0:001> ~*k 0 Id: 61c.12b4 Suspend: 1 Teb: 000000c7`9604d000 Unfrozen Child-SP RetAddr Call Site 000000c7`962ffd48 00000000`00000000 ntdll!RtlUserThreadStart 1 Id: 61c.22d4 Suspend: 1 Teb: 000000c7`9604f000 Unfrozen Child-SP RetAddr Call Site 000000c7`963ff900 00007ff6`e4600000 0x00000293`42074058 2 Id: 61c.1ab0 Suspend: 1 Teb: 000000c7`96051000 Unfrozen Child-SP RetAddr Call Site 000000c7`964ff718 00007ffa`bb145a0e ntdll!ZwWaitForWorkViaWorkerFactory+0x14 000000c7`964ff720 00007ffa`ba25244d ntdll!TppWorkerThread+0x2ee 000000c7`964ffa00 00007ffa`bb16df78 kernel32!BaseThreadInitThunk+0x1d 000000c7`964ffa30 00000000`00000000 ntdll!RtlUserThreadStart+0x28 3 Id: 61c.3308 Suspend: 1 Teb: 000000c7`96053000 Unfrozen Child-SP RetAddr Call Site 000000c7`965ff6a8 00007ffa`bb145a0e ntdll!ZwWaitForWorkViaWorkerFactory+0x14 000000c7`965ff6b0 00007ffa`ba25244d ntdll!TppWorkerThread+0x2ee 000000c7`965ff990 00007ffa`bb16df78 kernel32!BaseThreadInitThunk+0x1d 000000c7`965ff9c0 00000000`00000000 ntdll!RtlUserThreadStart+0x28 4 Id: 61c.2af0 Suspend: 1 Teb: 000000c7`96055000 Unfrozen Child-SP RetAddr Call Site 000000c7`966ffad8 00007ffa`bb145a0e ntdll!ZwWaitForWorkViaWorkerFactory+0x14 000000c7`966ffae0 00007ffa`ba25244d ntdll!TppWorkerThread+0x2ee 000000c7`966ffdc0 00007ffa`bb16df78 kernel32!BaseThreadInitThunk+0x1d 000000c7`966ffdf0 00000000`00000000 ntdll!RtlUserThreadStart+0x28 5 Id: 61c.2054 Suspend: 1 Teb: 000000c7`96059000 Unfrozen Child-SP RetAddr Call Site 000000c7`968ffcb8 00007ffa`bb165833 ntdll!ZwDelayExecution+0x14 000000c7`968ffcc0 00007ffa`b88f9fcd ntdll!RtlDelayExecution+0x43 000000c7`968ffcf0 00000293`420a1efd KERNELBASE!SleepEx+0x7d 000000c7`968ffd70 00000000`00000000 0x00000293`420a1efd
スレッド 1 は、アクセス違反を犯した疑わしいスレッドです。
別の疑わしいスレッド、スレッド 5 があります。 SleepEx 同じ不審な発信元からの通話 0x00000293`420xxxxx。この他のスレッドはおそらく何かが起こるのを待っているので、それを見てみましょう。
まず、どのような種類のメモリから実行しているかを見てみましょう。
0:001> !address 00000293`420a1ee0 Usage:Base Address: 00000293`420a0000 End Address: 00000293`420ca000 Region Size: 00000000`0002a000 ( 168.000 kB) State: 00001000 MEM_COMMIT Protect: 00000040 PAGE_EXECUTE_READWRITE Type: 00020000 MEM_PRIVATE Allocation Base: 00000293`420a0000 Allocation Protect: 00000040 PAGE_EXECUTE_READWRITE
やあ、 PAGE_。それは良い兆候ではありません。通常のコードが読み書き可能であることは非常に珍しいため、これは悪意のあるコード挿入のような匂いがします。しかし、おそらくこれらすべてに正当な説明があるかもしれないという希望を持ちましょう。それはそれを見つけるだけの問題です。
どのようなコードを実行しているかを見てみましょう。
00000293`420a1ed9 add rsp,30h
00000293`420a1edd pop rdi
00000293`420a1ede ret
00000293`420a1edf int 3
00000293`420a1ee0 push rbx
00000293`420a1ee2 sub rsp,20h
00000293`420a1ee6 call 00000293`420a13e0
00000293`420a1eeb mov qword ptr [00000293`420c0c78],rax
00000293`420a1ef2 mov ecx,3E8h
00000293`420a1ef7 call qword ptr [00000293`420b4028]
^^^^^^^^ YOU ARE HERE
00000293`420a1efd call 00000293`420a13e0 // do it again
00000293`420a1f02 mov rdx,rax
00000293`420a1f05 mov rbx,rax
00000293`420a1f08 call 00000293`420a19d0
00000293`420a1f0d test eax,eax
00000293`420a1f0f jne 00000293`420a1f22
00000293`420a1f11 mov rax,qword ptr [00000293`420c0c78]
00000293`420a1f18 mov qword ptr [00000293`420c0c78],rbx
00000293`420a1f1f mov rbx,rax
00000293`420a1f22 mov rcx,rbx
00000293`420a1f25 call 00000293`420a17f0
00000293`420a1f2a jmp 00000293`420a1ef2
最初のいくつかの指示から、 int 3 前の関数の終わりのようですので、分析を開始できます。 push rbx。
push rbx ; preserve register
sub rsp, 20h ; stack frame
call 00000293`420a13e0 ; mystery function 1
mov [00000293`420c0c78],rax ; save answer in global
00000293`420a1ef2:
mov ecx, 3E8h ; decimal 1000
call [00000293`420b4028] ; mystery function 2
^^^^^^^^ YOU ARE HERE
call 00000293`420a13e0 ; mystery function 1
mov rdx, rax ; return value becomes param1
mov rbx, rax ; save return value in rbx
call 00000293`420a19d0 ; mystery function 3
test eax,eax ; Q: did it succeed?
jne 00000293`420a1f22 ; N: Skip
mov rax, [00000293`420c0c78] ; get previous value
mov [00000293`420c0c78], rbx ; replace with new value
mov rbx, rax ; save previous value in rbx
00000293`420a1f22:
mov rcx, rbx ; rcx = updated value in rbx
call 00000293`420a17f0 ; mystery function 3
jmp 00000293`420a1ef2 ; loop back forever
ここで明らかなことの 1 つは、このスレッドは決して終了しないということです。無限ループですよ。
まず、謎の関数を特定できるかどうかを見てみましょう。
最も簡単なのは、インポートされた関数の呼び出しのように見えるため、おそらく謎の関数 2 です。
0:001> dps 00000293`420b4028 L1 00000293`420b4028 00007ffa`ba258370 kernel32!SleepStub
ああ、謎関数2は Sleep、そしてその呼び出しは Sleep(1000)。これはスタック トレースからある程度わかっていましたが、確認できてうれしいです。
しかし、その住所の近くを見回してみましょう。 関数ポインタの大きなテーブルの一部。
00000293`420b4000 00007ffa`baa59810 advapi32!RegCloseKeyStub 00000293`420b4008 00007ffa`baa596e0 advapi32!RegQueryInfoKeyWStub 00000293`420b4010 00007ffa`baa595a0 advapi32!RegOpenKeyExWStub 00000293`420b4018 00007ffa`baa5ab30 advapi32!RegEnumValueWStub 00000293`420b4020 00000000`00000000 00000293`420b4028 00007ffa`ba258370 kernel32!SleepStub 00000293`420b4030 00007ffa`ba250cc0 kernel32!GetLastErrorStub 00000293`420b4038 00007ffa`ba266b60 kernel32!lstrcatW 00000293`420b4040 00007ffa`ba25ff00 kernel32!CloseHandle 00000293`420b4048 00007ffa`ba254380 kernel32!CreateThreadStub
ビンゴ、これはインポートされた関数ポインターのテーブルのようです。
謎の関数 1 は、物事を開始するために呼び出され、その後ループで再度呼び出されるように見えるので、ある意味重要なようです。それが何なのか見てみましょう。
00000293`420a13e0 mov qword ptr [rsp+8],rbx 00000293`420a13e5 mov qword ptr [rsp+10h],rsi 00000293`420a13ea mov qword ptr [rsp+18h],rdi 00000293`420a13ef push rbp 00000293`420a13f0 mov rbp,rsp 00000293`420a13f3 sub rsp,80h 00000293`420a13fa mov rax,qword ptr [00000293`420bf010] 00000293`420a1401 xor rax,rsp 00000293`420a1404 mov qword ptr [rbp-8],rax 00000293`420a1408 mov ecx,40h 00000293`420a140d call 00000293`420a8478 // mystery function 3
これは、手動でコーディングされたアセンブリではなく、典型的な C 関数のように見えます。不揮発性レジスタを保存した後、スタック フレームを構築し、 mov rax, [global] 続いて xor rax, rsp によく似ています /GS スタックカナリア。
したがって、少なくとも、この不正なコードがスタック バッファ オーバーフロー保護を使用してコンパイルされていることは素晴らしいことです。用心しすぎることはできません。
謎関数3を見てみましょう。
00000293`420a8478
push rbx
sub rsp, 20h
mov rbx, rcx
jmp 00000293`420a8492
00000293`420a8483
mov rcx, rbx
call 00000293`420aad50
test eax, eax
je 00000293`420a84a2
mov rcx, rbx
00000293`420a8492
call 00000293`420aadb4
test rax, rax
je 00000293`420a8483
add rsp, 20h
pop rbx
ret
00000293`420a84a2
cmp rbx, 0FFFFFFFFFFFFFFFFh
je 00000293`420a84ae
call 00000293`420a8c80
int 3
00000293`420a84ae
call 00000293`420a8ca0
int 3
00000293`420a84b4
jmp 00000293`420a8478
これを逆コンパイルすると、
uint64_t something(uint64_t value)
{
uint64_t p;
while (uint64_t p = func00000293420aadb4(value); !p) {
if (!func00000293420aad50(value)) {
if (value == ~0ULL) {
func00000293420a8c80();
} else {
func00000293420a8c80();
}
// NOTREACHED
}
}
return p;
}
これは関数を呼び出すようです func00000293420aadb4 繰り返し。
00000293`420aadb4 jmp 00000293`420acf8c
これは増分リンク サンクであるようです。したがって、これが何であれ、デバッグモードでコンパイルされたように見えます。
00000293`420acf8c
push rbx
sub rsp, 20h
mov rbx,rcx
cmp rcx, 0FFFFFFFFFFFFFFE0h
ja 00000293`420acfd7
test rcx, rcx
mov eax, 1
cmove rbx, rax
jmp 00000293`420acfbe
00000293`420acfa9
call 00000293`420b02c0
test eax, eax
je 00000293`420acfd7
mov rcx, rbx
call 00000293`420aad50
test eax, eax
je 00000293`420acfd7
00000293`420acfbe
mov rcx, [00000293`420c07f8]
mov r8, rbx
xor edx, edx
call [00000293`420b4298]
test rax, rax
je 00000293`420acfa9
jmp 00000293`420acfe4
00000293`420acfd7
call 00000293`420ac71c
mov [rax], 0Ch
xor eax, eax
add rsp, 20h
pop rbx
ret
最初の比較 0xFFFFFFFF`FFFFFFFE これではないかと疑ってしまう malloc() または operator new これらの関数は、整数のオーバーフローを避けるために、過剰な割り当てサイズをチェックすることから始まるためです。
そして確かに、間接的な関数呼び出しで明らかなように、基本的にはこの関数が何であるかがわかります。
0:005> dps 00000293`420b4298 L1 00000293`420b4298 00007ffa`bb14cca0 ntdll!RtlAllocateHeap
さて、見つかりました malloc() または operator new。
これは、ミステリー関数 1 をよりよく理解するのに役立ちます。
00000293`420a13e0
mov [rsp+8], rbx
mov [rsp+10h], rsi
mov [rsp+18h], rdi
push rbp
mov rbp, rsp
sub rsp, 80h
mov rax, [00000293`420bf010]
xor rax, rsp
mov [rbp-8], rax ; /GS canary
mov ecx, 40h
call 00000293`420a8478 ; allocate 64 bytes
xorps xmm0, xmm0
mov ecx, 18h
mov rdi,rax ; save first allocation
movups [rax],xmm0 ; zero out first allocation
movups [rax+10h],xmm0
movups [rax+20h],xmm0
movups [rax+30h],xmm0
call 00000293`420a8478 ; allocate 24 bytes
xor esi,esi
mov ecx, 80h
mov rbx,rax ; save second allocation
mov [rax+0Ch], rsi ; zero out second allocation
mov [rax+14h], esi
mov [rax], esi
mov [rax+4], 10h
mov [rax+8], 1
call 00000293`420a84b4 ; mystery function 4
mov [rbx+10h], rax ; save result
lea ecx, [rsi+10h] ; ecx = 0x10
mov [rdi], rbx
call 00000293`420a8478 ; third allocation
lea ecx, [rsi+40h] ; ecx = 0x40
mov rbx, rax
mov [rax+8], rsi ; initialize third allocation
mov [rax], esi
mov [rax+4], 10h
call 00000293`420a84b4 ; mystery function 4
mov [rbx+8], rax
lea ecx, [rsi+18h] ; ecx = 0x18
この関数は、多くのメモリ ブロックを割り当てて初期化することから始まります。
最後に興味深いことを行うところまで飛ばしてみましょう。
lea rdx, [00000293`420bba90] ; LR"(SOFTWAREsystemconfig)"
lea rax, [rbp-50h]
mov [rdi+38h], rbx
mov r9d, 20119h ; KEY_READ
mov [rsp+20h], rax
xor r8d, r8d
mov rcx,0FFFFFFFF80000002h ; HKEY_LOCAL_MACHINE
call qword ptr [00000293`420b4010] ; RegOpenKeyExW
test eax, eax
あ dps 00000293`420b4010 関数ポインタが RegOpenKeyExWしたがって、関数呼び出し全体は次のようになっているはずです。
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\systemconfig", 0, KEY_READ, &key);
さらに逆アセンブリすると、コードがキーを正常に開くと、そこからいくつかの値を読み取ろうとすることがわかります。私の推測ではそれは systemconfig コードがその状態を保存する場所です。
そうですね、文字列をダンプして、このコードの正体に関する手がかりが何かあるかどうかを確認することで、作業をスピードアップできるかもしれません。思い出してください。 !address コマンドはメモリブロックが
0:001> !address 00000293`420a1ee0 Base Address: 00000293`420a0000 End Address: 00000293`420ca000
伺います の !メックス デバッガ拡張機能 メモリブロック内の文字列を検索します。
0:005> !mex.strings 00000293`420a0000 00000293`420ca000 ... 00000293420bbd10 system 00000293420bc1d4 H:rootkitr77-rootkit-mastervsx64Releaser77-x64.pdb
そうですね、これはマルウェアであるか、少なくともルートキットとして認識されていると思います。そして、このルートキット名をインターネットで検索すると、そのソース コードが公開されていることがわかります。
開発者にとって良いニュースは、問題が開発者のせいではないということです。悪いニュースは、クラッシュ ダンプは匿名で送信されるため、ユーザーに連絡してマルウェアに感染していることを伝える方法がないことです。
#最初の命令でプログラムがクラッシュした場合