Microsoft MVP성태의 닷넷 이야기
VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드 [링크 복사], [링크+제목 복사],
조회: 21454
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 6개 있습니다.)
VC++: 64. x64 Visual C++에서 TEB 주소 구하는 방법
; https://www.sysnet.pe.kr/2/0/1387

.NET Framework: 348. .NET x64 응용 프로그램에서 Teb 주소를 구하는 방법
; https://www.sysnet.pe.kr/2/0/1388

.NET Framework: 349. .NET Thread 인스턴스로부터 COM Apartment 유형 확인하는 방법
; https://www.sysnet.pe.kr/2/0/1389

VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드
; https://www.sysnet.pe.kr/2/0/10797

디버깅 기술: 150. windbg - Wow64, x86, x64에서의 커널 구조체(예: TEB) 구조체 확인
; https://www.sysnet.pe.kr/2/0/12097

디버깅 기술: 205. Windbg - KPCR, KPRCB
; https://www.sysnet.pe.kr/2/0/13842




자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드

"Windows Internals" 책을 보다가,

Windows Internals 제6판 Vol. 1: 마이크로소프트 윈도우 커널 공식 가이드 6판 
; http://www.yes24.com/24/goods/7877539

469페이지에 실린 "서비스 태그"라는 내용에서 SubProcessTag 필드의 존재를 처음 알게 되었습니다.

내용인 즉, 마이크로소프트 측에서 Vista부터 제공하고 있던 기능인데 이 필드의 내용은 하위 스레드에 자동으로 전달된다는 것입니다. 오호~~~ 재미있는 기능이군요. ^^

일단, windbg로 x64의 windows 8.1에서 _TEB 구조체를 살펴보면 다음과 같이 0x1720 옵셋에 위치한 것을 확인할 수 있습니다.

0: kd> dt _TEB 
nt!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : Ptr64 Void
   ...[생략]...
   +0x1720 SubProcessTag    : Ptr64 Void
   +0x1818 ReservedForWdf   : Ptr64 Void

실제로 이 값을 확인하려면 다음과 같은 식의 코드를 작성하면 됩니다.

// 첨부 파일에 Visual Studio 프로젝트로 포함됨

#include "stdafx.h"
#include <winternl.h>
#include <intrin.h> 

void showSbt()
{
#if defined WIN32
#define OFFSET_SUBPROCESSTAG 0x0f60
#elif defined _WIN64
#define OFFSET_SUBPROCESSTAG 0x1720
#else
#endif // _WIN32

    unsigned long subProcessTag = 0;

    HANDLE hProcess = GetCurrentProcess();

#if defined(_WIN64) // x64
    auto pTeb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#elif defined(WIN32) // x86
    auto pTeb = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif
    
    BOOL result = ReadProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
        , reinterpret_cast<void *>(&subProcessTag)
        , sizeof(subProcessTag),
        nullptr);

    if (result == TRUE)
    {
        printf("[TID: %d] %d (0x%x)\n", GetCurrentThreadId(), subProcessTag, subProcessTag);
        // 출력결과: [TID: 10216] 0 (0x0)
    }

    CloseHandle(hProcess);
}

int _tmain(int argc, _TCHAR* argv[])
{
    showSbt();
    return 0;
}

기본값은 0입니다. 그렇다면 정말 스레드 간에 자동 상속되는지 확인을 해볼까요? ^^

#include "stdafx.h"
#include <winternl.h>
#include <intrin.h> 

void showSbt(bool setSbt, DWORD newId)
{
#if defined WIN32
#define OFFSET_SUBPROCESSTAG 0x0f60
#elif defined _WIN64
#define OFFSET_SUBPROCESSTAG 0x1720
#else
#endif // _WIN32

    unsigned long subProcessTag = 0;

    HANDLE hProcess = GetCurrentProcess();

#if defined(_WIN64) // x64
    auto pTeb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#elif defined(WIN32) // x86
    auto pTeb = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif
    
    DWORD written = 0;

    if (setSbt == true)
    {
        subProcessTag = newId;

        WriteProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
            , reinterpret_cast<void *>(&subProcessTag)
            , sizeof(subProcessTag), &written);
    }

    subProcessTag = 0;

    BOOL result = ReadProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
        , reinterpret_cast<void *>(&subProcessTag)
        , sizeof(subProcessTag),
        nullptr);

    if (result == TRUE)
    {
        printf("[TID: %d] %d (0x%x)\n", GetCurrentThreadId(), subProcessTag, subProcessTag);
    }

    CloseHandle(hProcess);
}

DWORD WINAPI newThreadProc2(PVOID pVoid)
{
    showSbt(false, 0);
    return 0;
}

DWORD WINAPI newThreadProc(PVOID pVoid)
{
    showSbt(false, 0);

    HANDLE newThread = ::CreateThread(nullptr, 0, newThreadProc2, nullptr, 0, nullptr);
    ::WaitForSingleObject(newThread, INFINITE);
    ::CloseHandle(newThread);

    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    showSbt(false, 0x100);

    HANDLE newThread = ::CreateThread(nullptr, 0, newThreadProc, nullptr, 0, nullptr);
    ::WaitForSingleObject(newThread, INFINITE);
    ::CloseHandle(newThread);
    
    return 0;
}

주 스레드가 newThreadProc를 가진 2차 스레드를 생성하고, 다시 newThreadProc 내부에서는 newThreadProc2를 가진 3차 스레드를 생성했는데, 결과는 예상한 대로 상속된 것으로 나옵니다.

[TID: 12536] 256 (0x100)
[TID: 13384] 256 (0x100)
[TID: 14884] 256 (0x100)

이 기능의 아쉬운 점이 있다면? 스레드 풀에는 적용되지 않는다고 합니다. 즉, 현재 스레드에서 스레드 풀의 스레드에 작업을 던진 경우, 그 동작 간에는 SubProcessTag 값의 전달이 없습니다. 아마도, 스레드 생성 시에 부모 스레드의 SubProcessTag값을 전달하는 식으로 구현된 것이 아닌가 생각됩니다. 그렇기 때문에 이미 생성된 스레드 풀의 스레드에는 영향을 미치지 못하는 것이고!




이 기능을, 마이크로소프트 측에서는 다중 서비스를 호스팅하는 프로세스 내의 스레드에 적용한다고 합니다. 이에 대해서는 다음의 글을 참고하세요. ^^

ScTagQuery: Mapping Service Hosting Threads With Their Owner Service
; http://www.alex-ionescu.com/?p=52

제가 구현한 예제 코드에서는 단순히 DWORD 값을 넣었지만, 마이크로소프트가 설정한 정보의 구조는 좀 다릅니다. 문서화되지 않은(undocumented) 기능이지만, 다음과 같이 잘 공개(?)되어 있습니다. ^^

HOWTO: Use I_QueryTagInformation
; http://wj32.org/wp/2010/03/30/howto-use-i_querytaginformation/




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/23/2015]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13793정성태10/28/20245149C/C++: 183. C++ - 윈도우에서 한글(및 유니코드)을 포함한 콘솔 프로그램을 컴파일 및 실행하는 방법
13792정성태10/27/20244643Linux: 99. Linux - 프로세스의 실행 파일 경로 확인
13791정성태10/27/20244902Windows: 267. Win32 API의 A(ANSI) 버전은 DBCS를 사용할까요?파일 다운로드1
13790정성태10/27/20244621Linux: 98. Ubuntu 22.04 - 리눅스 커널 빌드 및 업그레이드
13789정성태10/27/20244916Linux: 97. menuconfig에 CONFIG_DEBUG_INFO_BTF, CONFIG_DEBUG_INFO_BTF_MODULES 옵션이 없는 경우
13788정성태10/26/20244459Linux: 96. eBPF (bpf2go) - fentry, fexit를 이용한 트레이스
13787정성태10/26/20244980개발 환경 구성: 730. github - Linux 커널 repo를 윈도우 환경에서 git clone하는 방법 [1]
13786정성태10/26/20245219Windows: 266. Windows - 대소문자 구분이 가능한 파일 시스템
13785정성태10/23/20244985C/C++: 182. 윈도우가 운영하는 2개의 Code Page파일 다운로드1
13784정성태10/23/20245256Linux: 95. eBPF - kprobe를 이용한 트레이스
13783정성태10/23/20244863Linux: 94. eBPF - vmlinux.h 헤더 포함하는 방법 (bpf2go에서 사용)
13782정성태10/23/20244622Linux: 93. Ubuntu 22.04 - 커널 이미지로부터 커널 함수 역어셈블
13781정성태10/22/20244802오류 유형: 930. WSL + eBPF: modprobe: FATAL: Module kheaders not found in directory
13780정성태10/22/20245554Linux: 92. WSL 2 - 커널 이미지로부터 커널 함수 역어셈블
13779정성태10/22/20244847개발 환경 구성: 729. WSL 2 - Mariner VM 커널 이미지 업데이트 방법
13778정성태10/21/20245673C/C++: 181. C/C++ - 소스코드 파일의 인코딩, 바이너리 모듈 상태의 인코딩
13777정성태10/20/20244954Windows: 265. Win32 API의 W(유니코드) 버전은 UCS-2일까요? UTF-16 인코딩일까요?
13776정성태10/19/20245272C/C++: 180. C++ - 고수준 FILE I/O 함수에서의 Unicode stream 모드(_O_WTEXT, _O_U16TEXT, _O_U8TEXT)파일 다운로드1
13775정성태10/19/20245492개발 환경 구성: 728. 윈도우 환경의 개발자를 위한 UTF-8 환경 설정
13774정성태10/18/20245197Linux: 91. Container 환경에서 출력하는 eBPF bpf_get_current_pid_tgid의 pid가 존재하지 않는 이유
13773정성태10/18/20244883Linux: 90. pid 네임스페이스 구성으로 본 WSL 2 + docker-desktop
13772정성태10/17/20245162Linux: 89. pid 네임스페이스 구성으로 본 WSL 2 배포본의 계층 관계
13771정성태10/17/20245066Linux: 88. WSL 2 리눅스 배포본 내에서의 pid 네임스페이스 구성
13770정성태10/17/20245341Linux: 87. ps + grep 조합에서 grep 명령어를 사용한 프로세스를 출력에서 제거하는 방법
13769정성태10/15/20246120Linux: 86. Golang + bpf2go를 사용한 eBPF 기본 예제파일 다운로드1
13768정성태10/15/20245397C/C++: 179. C++ - _O_WTEXT, _O_U16TEXT, _O_U8TEXT의 Unicode stream 모드파일 다운로드2
1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...