日本語版
最新ニュース
科学&テクノロジー

最初の命令でプログラムがクラッシュした場合

ある顧客は、プログラムが最初の命令で失敗したことを示すクラッシュ レポートに困惑していました。 クラッシュ ダンプの 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…

最初の命令でプログラムがクラッシュした場合

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_READ­ONLY)、これが書き込みアクセス違反を考慮する理由です。

実際、私たちは画像ヘッダーに書き込みを行っていますが、これは通常誰も行うことではありません。これはかなり疑わしいようです。

スタックを要求すると、次のようになります。

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_EXECUTE_READ­WRITE。それは良い兆候ではありません。通常のコードが読み書き可能であることは非常に珍しいため、これは悪意のあるコード挿入のような匂いがします。しかし、おそらくこれらすべてに正当な説明があるかもしれないという希望を持ちましょう。それはそれを見つけるだけの問題です。

どのようなコードを実行しているかを見てみましょう。

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 関数ポインタが Reg­Open­Key­ExWしたがって、関数呼び出し全体は次のようになっているはずです。

RegOpenKeyExW(HKEY_LOCAL_MACHINE,
    L"SOFTWARE\systemconfig", 0, KEY_READ, &key);

さらに逆アセンブリすると、コードがキーを正常に開くと、そこからいくつかの値を読み取ろうとすることがわかります。私の推測ではそれは system­config コードがその状態を保存する場所です。

そうですね、文字列をダンプして、このコードの正体に関する手がかりが何かあるかどうかを確認することで、作業をスピードアップできるかもしれません。思い出してください。 !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

そうですね、これはマルウェアであるか、少なくともルートキットとして認識されていると思います。そして、このルートキット名をインターネットで検索すると、そのソース コードが公開されていることがわかります。

開発者にとって良いニュースは、問題が開発者のせいではないということです。悪いニュースは、クラッシュ ダンプは匿名で送信されるため、ユーザーに連絡してマルウェアに感染していることを伝える方法がないことです。

#最初の命令でプログラムがクラッシュした場合

執筆者について: nipponese

Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。