首页
社区
课程
招聘
抛一个很有意思的问题,关于OpenThreadToken
发表于: 2016-3-18 14:11 10343

抛一个很有意思的问题,关于OpenThreadToken

2016-3-18 14:11
10343
最近在学习逆向,碰见有个程序,想要调用OpenThreadToken函数获得当前线程权限。
[ebp+0Ch] = GetCurrentThread()

.text:1002EC16 50                             push    eax             ; TokenHandle
.text:1002EC17 33 F6                          xor     esi, esi
.text:1002EC19 46                             inc     esi
.text:1002EC1A 56                             push    esi             ; OpenAsSelf
.text:1002EC1B 6A 08                          push    8               ; DesiredAccess
.text:1002EC1D FF 75 0C                       push    dword ptr [ebp+0Ch] ; ThreadHandle
.text:1002EC20 FF 15 14 50 05+                call    ds:OpenThreadToken

返回值是0,调用GetlastError(),获得的错误码是 1008:试图引用不存在的令牌。 
然后它一直想要通过这种方式获得当前线程的Token。
我很好奇为什么它一直不成功,就自己写程序试了一试。(请不要在意没有初始化变量)
int _tmain(int argc, _TCHAR* argv[])
{

  HANDLE hThread;
  HANDLE hToken;
  BOOL bSuccess;
  DWORD ThreadID;

        hThread = GetCurrentThread();
  cout << "CurrentThread:"<<hThread << endl;

  bSuccess = OpenThreadToken(hThread, TOKEN_QUERY, TRUE, &hToken);

  cout << bSuccess << endl;

  if (bSuccess)
  {
    cout << hToken << endl;
  }
  else
  {
    cout << GetLastError() << endl;
  }
  return 0;
}

返回值相同,也是同样的错误码,我调用OpenProcessToken(),是成功的。
那会不会是GetCurrentThread获得的伪句柄没有Query权限,所以不成功,于是我进行了下面的尝试(不要在意没有对返回值进行校验):
DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{

  HANDLE hThread;
  HANDLE hToken;
  BOOL bSuccess;
  DWORD ThreadID;
  HANDLE ThreadTemp;
  
        ThreadTemp = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadID);
  Sleep(10);

  hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadID);
  
        cout << "CurrentThread:"<<hThread << endl;

  bSuccess = OpenThreadToken(hThread, TOKEN_QUERY, TRUE, &hToken);

  cout << bSuccess << endl;

  if (bSuccess)
  {
    cout << hToken << endl;
  }
  else
  {
    cout << GetLastError() << endl;
  }
  return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
  cout << "dfdas" <<endl;
  Sleep(10000000);
  return 0;
}
结果还是一样,我有点蒙....有没有大牛过来指导一下...
环境是:windows xp(没有sp),windows sp2 windows 7 x32 windows 10 x64,windows7 windows10 的UAC都关了,不过应该是没关系的吧~

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (9)
雪    币: 18
活跃值: (1059)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
[QUOTE=阳光倾城;1420751]最近在学习逆向,碰见有个程序,想要调用OpenThreadToken函数获得当前线程权限。
[ebp+0Ch] = GetCurrentThread()

.text:1002EC16 50   &am...[/QUOTE]

试试看?貌似能解决问题吧?

int
set_se_debug() {
    HANDLE hToken;
    if (! OpenThreadToken(GetCurrentThread(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                          FALSE,
                          &hToken)
       ) {
        if (GetLastError() == ERROR_NO_TOKEN) {
            if (!ImpersonateSelf(SecurityImpersonation)) {
                CloseHandle(hToken);
                return 0;
            }
            if (!OpenThreadToken(GetCurrentThread(),
                                 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                                 FALSE,
                                 &hToken)
               ) {
                RevertToSelf();
                CloseHandle(hToken);
                return 0;
            }
        }
    }

    RevertToSelf();
    CloseHandle(hToken);
    return 1;
}
2016-3-18 16:12
0
雪    币: 18
活跃值: (1059)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
3
我测试了没问题!
2016-3-18 16:15
0
雪    币: 31
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
嗯,可以的,再补充一些,要是在主线程里面起一个A线程,然后在主线程中OpenThread获得A线程句柄,查询A线程的Token的话,就需要在A线程里面调用这个函数,而主线程不需要。

现在有个疑惑,为什么需要这样做....
再具体一点就是线程为什么不能像进程那样可以直接获取Token,是因为CPU优先级的问题么?
能解释一下么?

谢谢了~
2016-3-18 17:49
0
雪    币: 18
活跃值: (1059)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
5
其实我也不太懂,不过根据http://stackoverflow.com/questions/26107470/openthreadtoken-error-1008-error-no-token以及ImpersonateSelf的相关说明,大概意思可以认为每个线程只有自己的token,想要获得其它线程的token必须调用ImpersonateSelf函数冒充call进程的安全上下文环境(security context)获得一个访问令牌。具体细节ImpersonateSelf函数有说明;这是来自google groups对这个函数的讨论。OpenThreadToken fail

其实我是半灌水啊。
2016-3-18 21:10
0
雪    币: 31
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
能帮忙解决问题那就不叫灌水啊~谢谢了~等下我把ntosknl.exe的汇编贴出来..我想我找到一些原因了
2016-3-19 13:47
0
雪    币: 31
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
loc_49695E:                             ; CODE XREF: NtOpenThreadTokenEx+3Bj
PAGE:0049695E                 lea     eax, [ebp+var_28]
PAGE:00496961                 push    eax             ; int
PAGE:00496962                 lea     eax, [ebp+var_2C]
PAGE:00496965                 push    eax             ; int
PAGE:00496966                 lea     eax, [ebp+var_19]
PAGE:00496969                 push    eax             ; int
PAGE:0049696A                 lea     eax, [ebp+var_38]
PAGE:0049696D                 push    eax             ; int
PAGE:0049696E                 lea     eax, [ebp+var_34]
PAGE:00496971                 push    eax             ; int
PAGE:00496972                 push    [ebp+arg_8]     ; int
PAGE:00496975                 push    [ebp+Handle]    ; Handle
PAGE:00496978                 call    sub_49688F      ; 完成功能的函数
PAGE:0049697D                 cmp     eax, ebx
PAGE:0049697F                 jge     loc_4A211D

 mov     [ebp+AccessMode], al
PAGE:004968A6                 xor     ebx, ebx
PAGE:004968A8                 push    ebx             ; HandleInformation
PAGE:004968A9                 lea     eax, [ebp+Handle]
PAGE:004968AC                 push    eax             ; Object
PAGE:004968AD                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:004968B0                 push    PsThreadType    ; ObjectType
PAGE:004968B6                 push    40h             ; DesiredAccess
PAGE:004968B8                 push    [ebp+Handle]    ; Handle
PAGE:004968BB                 call    ObReferenceObjectByHandle ; 对线程句柄引用一次
PAGE:004968C0                 cmp     eax, ebx
PAGE:004968C2                 mov     ecx, [ebp+Handle]
PAGE:004968C5                 mov     esi, [ebp+arg_C]
PAGE:004968C8                 mov     [esi], ecx
PAGE:004968CA                 jl      short loc_4968F8
PAGE:004968CC                 push    edi
PAGE:004968CD                 push    [ebp+arg_18]    ; ImpersonationLevel
PAGE:004968D0                 push    [ebp+arg_14]    ; PBOOLEAN EffectiveOnly
PAGE:004968D3                 push    [ebp+arg_10]    ; PBOOLEAN CopyOnOpen
PAGE:004968D6                 push    ecx             ; hThread
PAGE:004968D7                 call    PsReferenceImpersonationToken ; 关键函数,这里就是获取线程Token的函数
PAGE:004968DC                 cmp     eax, ebx        ; ebx=0
PAGE:004968DE                 mov     edi, [ebp+arg_8]
PAGE:004968E1                 mov     [edi], eax
PAGE:004968E3                 jnz     loc_4A2268      ; 这里跳走

到PsReferenceImpersonationToken 函数这里,可以看到ImpersonationLevel参数被传了进去,是个指针。进入这个函数,我挑出来赋值的一段:
test    byte ptr [ebx+248h], 8
PAGE:004A19B3                 mov     esi, [ebx+20Ch]
PAGE:004A19B9                 jz      short loc_4A1A22
PAGE:004A19BB                 mov     ecx, [esi]
PAGE:004A19BD                 mov     [ebp+arg_0], ecx
PAGE:004A19C0                 call    ObfReferenceObject
PAGE:004A19C5                 mov     eax, [esi+8]
PAGE:004A19C8                 mov     ecx, [ebp+arg_C]
PAGE:004A19CB                 mov     [ecx], eax
PAGE:004A19CD                 mov     al, [esi+4]
PAGE:004A19D0                 mov     ecx, [ebp+arg_4]
PAGE:004A19D3                 mov     [ecx], al
PAGE:004A19D5                 mov     al, [esi+5]
PAGE:004A19D8                 mov     ecx, [ebp+arg_8]
PAGE:004A19DB                 mov     [ecx], al
PAGE:004A19DD
PAGE:004A19DD loc_4A19DD:                             ; CODE XREF: PsReferenceImpersonationToken+14F41j
PAGE:004A19DD                 mov     ebx, [ebp+var_C]
PAGE:004A19E0                 mov     esi, [ebx]
PAGE:004A19E2                 and     esi, 0FFFFFFFEh
PAGE:004A19E5                 lea     eax, [esi-4]
PAGE:004A19E8                 mov     [ebp+arg_4], ebx  ;CopyOnOpen
PAGE:004A19EB                 mov     [ebp+arg_8], esi  ;EffectiveOnly
PAGE:004A19EE                 mov     [ebp+arg_C], eax  ;ImpersonationLevel 
PAGE:004A19F1                 mov     eax, [ebp+arg_8]
PAGE:004A19F4                 mov     ecx, [ebp+arg_4]
PAGE:004A19F7                 mov     edx, [ebp+arg_C]

这里找到了ImpersonationLevel 这个值的出处
贴上MSDN
PsReferenceImpersonationToken returns a pointer to the impersonation token for the given thread. If the thread is not currently impersonating a client, a NULL pointer is returned.
如果说ImpersonationLevel 这个值小于等于SecurityAnonymous等级的话,就直接返回空。从A线程打开B线程的Token时,就是这个SecurityAnonymous等级,所以每次都返回空...
2016-3-19 14:52
0
雪    币: 10
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢lz的分析,这两天也在看windows security的相关机制,发现是一个很浩大的工程,不过无论如何也要啃下来。

关于Impersonation Level的内容我还没有看到,但OpenThreadToken失败的原因可能跟这个没有关系(或者我没找到)。

在MSDN 关于access token的解释当中有这么一段话
(链接:https://msdn.microsoft.com/en-us/library/windows/desktop/aa374909(v=vs.85).aspx)

“Every process has a primary token that describes the security context of the user account associated with the process. By default, the system uses the primary token when a thread of the process interacts with a securable object. Moreover, a thread can impersonate a client account. Impersonation allows the thread to interact with securable objects using the client's security context. A thread that is impersonating a client has both a primary token and an impersonation token.
Use the OpenProcessToken function to retrieve a handle to the primary token of a process. Use the OpenThreadToken function to retrieve a handle to the impersonation token of a thread. For more information, see Impersonation.”
大概意思是说,如果没有impersonation的话,thread在访问安全对象的时候,只能使用process token。
如果impersonation的话,那么thread就有两个token,一个primary token(从process继承来的),一个是impersonation token。
我还是不太理解,觉得即使没有impersonation,那么openThreadToken应该也获得primary token吧。

在stackoverflow上发现了一个帖子,下面有一个没被采用的回答
(链接:http://stackoverflow.com/questions/6746113/does-calling-impersonateself-cancel-all-security-token-adjustments-made-to-the)
大家可以看帖子具体内容,有一个叫Damien_The_Unbeliever的网友,大概解释了一下,线程本身没有impersonation或者人工赋予token的话,默认是不带token的,只是windows有一套机制,线程在访问安全对象的时候,可以使用process的token。

所以当openThreadToken返回1008错误的时候(这时候thread本身确实没有token),一般有两种处理方式:
1.获得process token, openprocesstoken;
2.给线程赋予一个token,可以是impersonation,能够调用impersonation的API有很多个(链接:https://msdn.microsoft.com/en-us/library/windows/desktop/aa379317(v=vs.85).aspx  中就列举了可以使用的),也可以获取一个token,修改一下。

我理解的大体流程就是这样,这还是只是windows security机制的一个非常不起眼的小问题,觉得要研究整套机制,鸭梨山大。

PS:很不幸,stackoverflow上的回答,所给的微软官网链接已经失效了。。。,我在google上也没有找到准确匹配的内容,所以目前来看,真实性也是存疑的,但我目前只能选择相信了,实在耗不起了,一个问题研究了两天。
如果不当之处,敬请指正。
2016-8-31 17:28
0
雪    币: 31
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
就我个人理解就是,在线程创建的时候默认继承的是ProcessToken,除非为线程分配一个单独ThreadToken,如果没有单独分配ThreadToken那么调用OpenThreadToken就是会返回无Token的错误信息。ProcessToken需要通过OpenProcessToken函数来获取。ImpersonationToken是一个模拟令牌,相当于假装有一个Client进程的安全令牌,这样就有一个访问Client进程的权限了。
请指正
2016-9-7 11:58
0
雪    币: 10
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
应该就是这样的,只能通过impersonation的形式来给线程分配令牌,如果有需要的话,再对impersonation token进行修改了。
2016-9-8 12:42
0
游客
登录 | 注册 方可回帖
返回
//