<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/binary/7430

            65章 線程局部存儲


            TLS是每個線程特有的數據區域,每個線程可以把自己需要的數據存儲在這里。一個著名的例子是C標準的全局變量errno。多個線程可以同時使用errno獲取返回的錯誤碼,如果是全局變量它是無法在多線程環境下正常工作的。因此errno必須保存在TLS。

            C++11標準里面新添加了一個thread_local修飾符,標明每個線程都屬于自己版本的變量。它可以被初始化并位于TLS中。

            Listing 65.1: C++11

            #!c
            #include <iostream>
            #include <thread>
            thread_local int tmp=3;
            int main()
            {
                std::cout << tmp << std::endl;
            };
            

            使用MinGW GCC 4.8.1而不是MSVC2012編譯。

            如果我們查看它的PE文件,可以看到tmp變量被放到TLS section。

            65.1 線性同余發生器


            前面第20章的純隨機數生成器有一個缺陷:它不是線程安全的,因為它的內部狀態變量可以被不同的線程同時讀取或修改。

            65.1.1 Win32

            未初始化的TLS數據

            一個全局變量如果添加了_declspec(thread)修飾符,那么它會被分配在TLS。

            #!c
            #include <stdint.h>
            #include <windows.h>
            #include <winnt.h>
            
            // from the Numerical Recipes book
            #define RNG_a 1664525
            #define RNG_c 1013904223
            
            __declspec( thread ) uint32_t rand_state;
            
            void my_srand (uint32_t init)
            {
                rand_state=init;
            }
            
            int my_rand ()
            {
                rand_state=rand_state*RNG_a;
                rand_state=rand_state+RNG_c;
                return rand_state & 0x7fff;
            }
            
            int main()
            {
                my_srand(0x12345678);
                printf ("%d\n", my_rand());
            };
            

            使用Hiew可以看到PE文件多了一個section:.tls。

            Listing 65.2: Optimizing MSVC 2013 x86

            _TLS SEGMENT
                _rand_state DD 01H DUP (?)
            _TLS ENDS
            
            _DATA SEGMENT
                $SG84851 DB '%d', 0aH, 00H
            _DATA ENDS
            
            _TEXT SEGMENT
            
            _init$ = 8  ; size = 4
            
            _my_srand PROC
            ; FS:0=address of TIB
                mov eax, DWORD PTR fs:__tls_array ; displayed in IDA as FS:2Ch
            ; EAX=address of TLS of process
                mov ecx, DWORD PTR __tls_index
                mov ecx, DWORD PTR [eax+ecx*4]
            ; ECX=current TLS segment
                mov eax, DWORD PTR _init$[esp-4]
                mov DWORD PTR _rand_state[ecx], eax
                ret 0
            _my_srand ENDP
            
            _my_rand PROC
            ; FS:0=address of TIB
                mov eax, DWORD PTR fs:__tls_array ; displayed in IDA as FS:2Ch
            ; EAX=address of TLS of process
                mov ecx, DWORD PTR __tls_index
                mov ecx, DWORD PTR [eax+ecx*4]
            ; ECX=current TLS segment
                imul eax, DWORD PTR _rand_state[ecx], 1664525
                add eax, 1013904223 ; 3c6ef35fH
                mov DWORD PTR _rand_state[ecx], eax
                and eax, 32767 ; 00007fffH
                ret 0
            _my_rand ENDP
            
            _TEXT ENDS
            

            rand_state現在處于TLS段,而且這個變量每個線程都擁有屬于自己版本。它是這么訪問的:從FS:2Ch加載TIB(Thread Information Block)的地址,然后添加一個額外的索引(如果需要的話),接著計算出在TLS段的地址。

            最后可以通過ECX寄存器來訪問rand_state變量,它指向每個線程特定的數據區域。

            FS:這是每個逆向工程師都很熟悉的選擇子了。它專門用于指向TIB,因此訪問線程特定數據可以很快完成。

            GS: 該選擇子用于Win64,0x58的地址是TLS。

            Listing 65.3: Optimizing MSVC 2013 x64

            _TLS SEGMENT
                rand_state DD 01H DUP (?)
            _TLS ENDS
            
            _DATA SEGMENT
                $SG85451 DB '%d', 0aH, 00H
            _DATA ENDS
            
            _TEXT SEGMENT
            init$ = 8
            
            my_srand PROC
                mov edx, DWORD PTR _tls_index
                mov rax, QWORD PTR gs:88 ; 58h
                mov r8d, OFFSET FLAT:rand_state
                mov rax, QWORD PTR [rax+rdx*8]
                mov DWORD PTR [r8+rax], ecx
                ret 0
            my_srand ENDP
            
            my_rand PROC
                mov rax, QWORD PTR gs:88 ; 58h
                mov ecx, DWORD PTR _tls_index
                mov edx, OFFSET FLAT:rand_state
                mov rcx, QWORD PTR [rax+rcx*8]
                imul eax, DWORD PTR [rcx+rdx], 1664525 ;0019660dH
                add eax, 1013904223 ; 3c6ef35fH
                mov DWORD PTR [rcx+rdx], eax
                and eax, 32767 ; 00007fffH
                ret 0
            my_rand ENDP
            
            _TEXT ENDS
            

            初始化TLS數據

            比方說,我們想為rand_state設置一些固定的值以避免程序員忘記初始化。

            #!c
            #include <stdint.h>
            #include <windows.h>
            #include <winnt.h>
            
            // from the Numerical Recipes book
            #define RNG_a 1664525
            #define RNG_c 1013904223
            
            __declspec( thread ) uint32_t rand_state=1234;
            
            void my_srand (uint32_t init)
            {
               rand_state=init;
            }
            
            int my_rand ()
            {
               rand_state=rand_state*RNG_a;
               rand_state=rand_state+RNG_c;
               return rand_state & 0x7fff;
            }
            
            int main()
            {
                printf ("%d\n", my_rand());
            };
            

            代碼除了給rand_state設定初始值外與之前的并沒有什么不同,但在IDA我們看到:

            .tls:00404000 ; Segment type: Pure data
            .tls:00404000 ; Segment permissions: Read/Write
            .tls:00404000 _tls segment para public 'DATA' use32
            .tls:00404000 assume cs:_tls
            .tls:00404000 ;org 404000h
            .tls:00404000 TlsStart db 0 ; DATA XREF: .rdata:TlsDirectory
            .tls:00404001 db 0
            .tls:00404002 db 0
            .tls:00404003 db 0
            .tls:00404004 dd 1234
            .tls:00404008 TlsEnd db 0 ; DATA XREF: .rdata:TlsEnd_pt
            ...
            

            每次一個新的線程運行的時候,會分配新的TLS給它,然后包括1234所有數據將被拷貝過去。

            這是一個典型的場景:

            TLS callbacks

            如果我們想給TLS賦一個變量值呢?比方說:程序員忘記調用my_srand()函數來初始化PRNG,但是隨機數生成器在開始的時候必須使用一個真正的隨機數值而不是1234。這種情況下則可以使用TLS callbaks。

            下面的代碼的可移植性很差,原因你應該明白。我們定義了一個函數(tls_callback()),它在進程/線程開始執行前調用,該函數使用GetTickCount()函數的返回值來初始化PRNG。

            #!c
            #include <stdint.h>
            #include <windows.h>
            #include <winnt.h>
            
            // from the Numerical Recipes book
            #define RNG_a 1664525
            #define RNG_c 1013904223
            
            __declspec( thread ) uint32_t rand_state;
            
            void my_srand (uint32_t init)
            {
                rand_state=init;
            }
            
            void NTAPI tls_callback(PVOID a, DWORD dwReason, PVOID b)
            {
                my_srand (GetTickCount());
            }
            
            #pragma data_seg(".CRT$XLB")
            PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;
            #pragma data_seg()
            
            int my_rand ()
            {
                rand_state=rand_state*RNG_a;
                rand_state=rand_state+RNG_c;
                return rand_state & 0x7fff;
            }
            int main()
            {
                // rand_state is already initialized at the moment (using GetTickCount())
                printf ("%d\n", my_rand());
            };
            

            用IDA看一下:

            Listing 65.4: Optimizing MSVC 2013

            .text:00401020 TlsCallback_0 proc near ; DATA XREF: .rdata:TlsCallbacks
            .text:00401020     call ds:GetTickCount
            .text:00401026     push eax
            .text:00401027     call my_srand
            .text:0040102C     pop ecx
            .text:0040102D     retn 0Ch
            .text:0040102D TlsCallback_0 endp
            ...
            .rdata:004020C0 TlsCallbacks dd offset TlsCallback_0 ; DATA XREF: .rdata:TlsCallbacks_ptr
            ...
            .rdata:00402118 TlsDirectory dd offset TlsStart
            .rdata:0040211C TlsEnd_ptr dd offset TlsEnd
            .rdata:00402120 TlsIndex_ptr dd offset TlsIndex
            .rdata:00402124 TlsCallbacks_ptr dd offset TlsCallbacks
            .rdata:00402128 TlsSizeOfZeroFill dd 0
            .rdata:0040212C TlsCharacteristics dd 300000h
            

            TLS callbacks函數時常用于隱藏解包處理過程。為此有些人可能會困惑,為什么一些代碼可以偷偷地在OEP(Original Entry Point)之前執行。

            65.1.2 Linux

            下面是GCC聲明線程局部存儲的方式:

            #!c
            __thread uint32_t rand_state=1234;
            

            這不是標準C/C++的修飾符,但是是GCC的一個擴展特性。

            GS:該選擇子同樣用于訪問TLS,但稍微有點區別:

            Listing 65.5: Optimizing GCC 4.8.1 x86

            .text:08048460 my_srand proc near
            .text:08048460
            .text:08048460 arg_0 = dword ptr 4
            .text:08048460
            .text:08048460     mov eax, [esp+arg_0]
            .text:08048464     mov gs:0FFFFFFFCh, eax
            .text:0804846A     retn
            .text:0804846A my_srand endp
            .text:08048470 my_rand proc near
            .text:08048470     imul eax, gs:0FFFFFFFCh, 19660Dh
            .text:0804847B     add eax, 3C6EF35Fh
            .text:08048480     mov gs:0FFFFFFFCh, eax
            .text:08048486     and eax, 7FFFh
            .text:0804848B     retn
            .text:0804848B my_rand endp
            

            更多例子:ELF Handling For Thread-Local Storage

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线