2018年5月17日星期四

CVE-2018-8120 Analysis and Exploit

5月15日ESET发文其在3月份捕获了一个 pdf远程代码执行(cve-2018-4990)+windows本地权限提升(cve-2018-8120)的样本。ESET发文后,我从vt上下载了这样一份样本(https://www.virustotal.com/#/file/6cfbebe9c562d9cdfc540ce45d09c8a00d227421349b12847c421ba6f71f4284/detection)。初步逆向,大致明确如外界所传,该漏洞处于开发测试阶段,不慎被上传到了公网样本检测的网上,由ESET捕获并提交微软和adobe修补。测试特征字符串如下

定位样本中关键的代码并调试分析

可以知道漏洞产生于系统调用号为0x1226的内核函数NtUserSetImeInfoEx中,该函数调用SetImeInfoEx,在SetImeInfoEx内对参数1校验疏忽,产生了空指针解引用漏洞,相关触发代码逻辑如下:



相较于目前较为主流的gdi提权技术,该样本利用了安装系统调用门来实现内核权限提升。
首先,通过指令sgdt指令获取全局描述符表

申请0x400 bytes内存,构造调用门描述符

调用门描述符结构如下

调用门及mapping null page构造完毕后,开始触发漏洞安装调用门

此时寄存器数据如下

源数据如下

目的地址数据如下

可以看到安装了自身callgate及Ring0Function。安装完毕后(支持3环调用的CallGate),ring3程序调用调用门

找到对应的GDT表项

按照GDT表项的结构,分析样本安装的调用门描述符:
段选择子cs的值为0x1a8;
对应的Ring0Function的offset低地址为0x51b4;
对应的Ring0Function的offset高地址为0x80b9;
DPL为3  &  Gate Valid位为1
段选择子cs对应的结构如下,RPL级别为0,特权级别

根据上述结构定位gdt段描述符项

段描述符结构如下

3,4,5,8个字节得到段基址为0x0,结合上面的Ring0Func,得到Ring0Func的物理地址
Ring0Function很简单,直接ret,但此时ring3代码已具有ring0权限,因为这里没有恢复cs:

整个Far Pointer to Call Gate流程如下图:

中断在call far pointer,此时cs的值为0x1b

单步进入后,cs变为0x1a8(此时中断在我双机调试的windbg上)

如此替换本进程的token为system的token后,完成权限提升,最后恢复cs,并平衡堆栈后,再执行更多的ring3代码,否则容易BSOD。

分析过程中,我近95%的按照样本的思路还原了提权代码。



Source code

https://github.com/bigric3/cve-2018-8120



Thanks:

https://www.f-secure.com/weblog/archives/kasslin_AVAR2006_KernelMalware_paper.pdf
http://vexillium.org/dl.php?call_gate_exploitation.pdf


阅读更多博文 »

2018年5月14日星期一

CAJViewer Local Privilege Escape Exploit

源码面前,了无秘密:

https://github.com/bigric3/CAJViewer-LPE-Exploit.git


Key Point:



  1. PagedPool Overflow ;
  2. Heap Spray Private Namespace Object .




阅读更多博文 »

CVE-2017-11885 Analysis

早上看到cve-2017-11885公开了exploit(https://www.exploit-db.com/exploits/44616/),遂跟进分析下。

通过修改如下位置,触发RRAS服务崩溃
stub += "\xff\xff\xff\xff" #dwVarID ECX (CALL off_64389048[ECX*4]) -> p2p JMP EAX #dwVarID (_MIB_OPAQUE_QUERY)

也正如exp中注释"#0x1d MIBEntryGetFirst (other RPC calls are also affected)",定位崩溃点即iprtemgr.dll!RtrMgrMIBEntryGetFirst :
(通过xref可以验证作者所述,其他rpc调用同样存在类似漏洞)

在+6C位置下断,重新运行exp,通过wireshark抓取RRAS的数据包如下:

 同时od中断,下图的包括ECX在内的数据和原始exp有些差异,是我修改exp后的值

看到,eax指向的数据中,除了exp里的buf数据外,开头还包含了部分stub数据。回到作者的exp来看
stub += "\xad\x0b\x2d\x06" #dwVarID ECX (CALL off_64389048[ECX*4]) -> p2p JMP EAX #dwVarID (_MIB_OPAQUE_QUERY)
i think he want to call address,the address contains 'jmp eax' .

Here have a question,you should to search modules(e.g shell32.dll)for 'jmp eax' address,we call it address1,and then search an address contains the address1,because of ' call dword ptr ds:[] ',and we should consider that after 'jmp eax', there are some garbage code before shellcode execute,we should ensure these code can not cause DoS;

Besides,I think windows2003 sp2 have opened DEP,and eax contains data don't have execute privilege.

Last, I closed DEP, and find esp+0x8 contains value equal eax,just 4 test,Get one Shell

阅读更多博文 »

2018年3月20日星期二

bad rabbit磁盘加解密分析

之前分析的磁盘加解密部分的,水平有限如有错误,还请指正:P


阅读更多博文 »

2018年3月13日星期二

Hash Length Extension Attack -- WooYunCase

再贴一篇以前的doc:)

Q:前提知道md5(secret)的值,secret的长度,在str可控的情况下,能否推算md5(secret.str)的值?


Hash Length Extension Attack


例如md5长度扩展攻击,曾经了解这个攻击的时候并不懂md5的算法实现,所以先补习了一下md5的算法。下面是我从一份md5的实现中扣出的大致流程的代码(实现细节扣除了,有兴趣可以自行搜索md5的实现代码)。
# -*- coding: cp936 -*-

#对消息填充0
def message_fill(bin_message):
    #略
#附加消息的长度
def fillLength(bin_message,length):
    #略
def F(b,c,d):
    return (b&c)|((~b)&d)
def G(b,c,d):
    return (b&d)|(c&~d)
def H(b,c,d):
    return b^c^d
def I(b,c,d):
    return c^(b|~d)
#略部分细节处理代码
#第一轮
def RF_F(messageYq_512bits,CVq,q):
    a=CVq[0]
    b=CVq[1]
    c=CVq[2]
    d=CVq[3]
    messageGroup_32bits=divide_32bits(messageYq_512bits)
    for i in range(16):
        ## 略
        b=a
        a=d
        d=c
        c=tmpb
    ABCD_group=[a,b,c,d]
    return ABCD_group
#第二轮
def RF_G(messageYq_512bits,CVq,q):
    a=CVq[0]
    b=CVq[1]
    c=CVq[2]
    d=CVq[3]
    messageGroup_32bits=divide_32bits(messageYq_512bits)
    for i in range(16):
        ## 略
        b=a
        a=d
        d=c
        c=tmpb
    ABCD_group=[a,b,c,d]
    return ABCD_group
#第三轮
def RF_H(messageYq_512bits,CVq,q):
    a=CVq[0]
    b=CVq[1]
    c=CVq[2]
    d=CVq[3]
    messageGroup_32bits=divide_32bits(messageYq_512bits)
    for i in range(16):
       ## 略
        b=a
        a=d
        d=c
        c=tmpb
    ABCD_group=[a,b,c,d]
    return ABCD_group
#第四轮
def RF_I(messageYq_512bits,CVq,q):
    a=CVq[0]
    b=CVq[1]
    c=CVq[2]
    d=CVq[3]
    messageGroup_32bits=divide_32bits(messageYq_512bits)
    for i in range(16):
        ## 略
        b=a
        a=d
        d=c
        c=tmpb
    ABCD_group=[a,b,c,d]
    return ABCD_group

def Hmd5(messageYq,CVq,L):
    for i in range(L):
        ABCD_group=RF_F(messageYq[i],CVq,i)
        ABCD_group=RF_G(messageYq[i],ABCD_group,i)
        ABCD_group=RF_H(messageYq[i],ABCD_group,i)
        ABCD_group=RF_I(messageYq[i],ABCD_group,i)
        CV_q_1=add_mod32(CVq,ABCD_group)
        CVq=CV_q_1
    return CVq

def MD5encrypt():
    message=raw_input("please input your message:")
    messageLength=len(message)
    bin_messageFilled=message_fill(bin_message)
    print "msg Fill result:",bin_messageFilled
    message=fillLength(bin_messageFilled,messageLength)
    print "msg Fill length:",message
    A=0x67452301
    B=0xefcdab89
    C=0x98badcfe
    D=0x10325476
    CVq=[A,B,C,D]
    #L为512的倍数
    L=len(message)/512
    #按512分组
    messageYq=[]

    for i in xrange(L):
        messageYq.append(message[i*512:i*512+512])
    result=Hmd5(messageYq,CVq,L)  ###########压缩函数Hmd5,为MD5算法的核心
    
if __name__=="__main__":
    MD5encrypt()


  1. md5处理消息,传入的消息必须是512bit的整数倍,若不足则先进行padding,padding的方式是,第一个字节1填充,后面全0,填到留64bit为止,最后64bit用于存储消息长度。如上代码中message_fill,以及fillLength函数。

  2. md5初始化如下4个值,对padding完毕的消息每512bit分为一组进行计算,每组进行4轮处理,每轮有16次的循环:
    •     A=0x67452301
    •     B=0xefcdab89
    •     C=0x98badcfe
    •     D=0x10325476

  3. 4轮处理后得到的4个int元素的数组,处理后继续作为CVq数组,代入下一组512bit消息的处理,直到所有512bit处理结束,最后的32个字节的hash即为32位的md5值。

如上基本概述了md5算法,那么回到文章开头的问题,知道md5(secret)的值,secret的长度,在str可控的情况下,为什么能推算md5(secret.str)的值,绕过一些验证?

这又要回到md5的算法,假如secret正好是448bit的整数倍(最后64个bit用于padding消息长度),那么secret和str必然是作为两组512bit代入大循环的4轮计算,当处理到第二组512bit,即str,此时的CVq必然是md5(secret)处理得到的数组,那么我们就可以将得到的CVq数组代入512bit分组计算的循环,继续“未完成”的哈希;

假如secret不是448bit的整数倍,也不要紧,因为str是可控的,那么我们就可以自行对secret进行padding,padding到512bit,相当于帮md5算法完成了message_fill,以及fillLength函数功能,下面继续上面的分析,又可以推算出md5(secret.str)的值。



如上的分析即是哈希长度扩展攻击,也是常见于ctf比赛应用中。其实在现实中也有一些应用,可以搜到一些case,下面看一个代码审计的实例。

phpwind前台getshell(当年乌云差不多这个标题)


wooyun一哥刚爆出这个漏洞的时候,即给了闪电3刀,p牛先知先觉发到了群里,详情未出的情况下m牛瞬间就意识到secretKey的加密上出了问题,并贴出了道哥早年的一篇文章Understanding MD5 Length Extension Attack

p牛当晚也就完成了漏洞的复现分析《phpwind 利用哈希长度扩展攻击进行getshell》。
作为websecurity群里一只bindog,也尽力在第二时间参考道哥和p牛的文章完成了学习。

读了下phpwind的代码,遵循MVC开发模式,以首页上板块为例

m对应module,c对应控制器,a对应function,模版文件位于template/bss/,定位到控制器目录src/Application,run即相当于构造函数


若类下有beforeAction函数,则这个函数先于run执行。(我只跟了遍登录流程整理,如有错误还请斧正)。例如构造如下url获取数据http://127.0.0.4:88/windid/index.php?m=api&c=app&a=list

AppController继承于OpenBaseController,而这个OpenBaseController类里有个beforeAction函数

class OpenBaseController extends PwBaseController {
 
 public $app = array();
 public $appid = 0;
 
 public  function beforeAction($handlerAdapter) {
  parent::beforeAction($handlerAdapter);
  
  $charset = 'utf-8';
  $_windidkey = $this->getInput('windidkey', 'get');
  $_time = (int)$this->getInput('time', 'get');
  $_clientid = (int)$this->getInput('clientid', 'get');
  if (!$_time || !$_clientid) $this->output(WindidError::FAIL);
  $clent = $this->_getAppDs()->getApp($_clientid);
  if (!$clent) $this->output(WindidError::FAIL);
  if (WindidUtility::appKey($clent['id'], $_time, $clent['secretkey'], $this->getRequest()->getGet(null), $this->getRequest()->getPost()) != $_windidkey)
                    $this->output(WindidError::FAIL);
  
  $time = Pw::getTime();
  if ($time - $_time > 1200) $this->output(WindidError::TIMEOUT);
  $this->appid = $_clientid;
 }
可以看到这是一个验证的过程,要绕过这个验证继续看WindidUtility::appKey的实现

如上代码,可以看到$str是post或get来处理后的,假如我们能找到一处调用这个appKey函数,并将得到的hash返回前端的,那么就可以进行哈希长度扩展攻击。搜索到如下一处

前台功能如下

有返回windidkey(即appKey函数结果),调整代码,echo下, 哪些变量参与了计算

至此,还有一个问题要解决,在进行一些越权操作时,构造的url很可能因为appKey中的ksort而导致无法利用,所以在利用时注意将一些会改变排序的字段放到post变量中,在appKey中会添加到$str结尾,满足条件攻击。如padding前面这个http://127.0.0.4:88/windid/index.php?m=api&c=app&a=list

最终构造

http://127.0.0.4:88/windid/index.php?adoAvatarcavatarmapitypeflashuid2uidundefined=%80%B8%02&windidkey=4a12ee2fcb1cad2fe03fb79ae70ead8a&time=1472109820&clientid=1

Success!

水平有限,欢迎交流:)
阅读更多博文 »

2018年3月11日星期日

Conficker Virus Analysis


目录

一. 关于壳

二. Patch MS08-067 && DNS API

三. 本地创建WebServer,用作传播本体

四. 爆破内网中的IPC$

五. 感染网络磁盘&可移动磁盘

六. 端口映射

七. 对C段和公网随机IP发起攻击

八. Shellcode 分析

九. 域名生存部分

十. ROOTKIT



阅读更多博文 »

2018年3月8日星期四

MS16-098 Analysis(CVE-2016-3309)

以前分析的,搬到blog上:P

漏洞成因和heap Fengshui没做记录,网上都有相关的文章,经典paper《Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects》,补充点文章里没记录的鸡肋:P

1. 关于heap的构造,这个是SensePost paper的中心,非常喜欢他们的配图,So列一点















2. 利用

这是一个整型溢出,造成非分页池的分配远小于所需的大小,最终BSOD。池风水之后,申请的非分页池过小,使用过程中越界修改了构造在内存中的SURFOBJ64.sizlBitmap.cy变量为0xffffffff,此被破坏的对象做ManagerBitmap,下一页的bitmap作为worker对象,Manager越界覆写worker对象,覆盖worker->pvScan0对象,获取任意地址读写能力,最终完成提权。在没有SensePost代码的前提下,要完成这个exp还需要分析一个函数AddEdgeToGET,为精确覆写SURFOBJ64.sizlBitmap.cy为-1,也就是exp中的如下代码:
       for (int l = 0; l < 0x3FE00; l++) {
points[l].x = 0x5a1f;
points[l].y = 0x5a1f;
}
points[2].y = 20;
points[0x3FE00].x = 0x4a1f;
points[0x3FE00].y = 0x6a1f;
if (!BeginPath(hMemDC)) {
fprintf(stderr, "[!] BeginPath() Failed: %x\r\n", GetLastError());
}
for (int j = 0; j < 0x156; j++) {
if (j > 0x1F && points[2].y != 0x5a1f) {
points[2].y = 0x5a1f;
}
if (!PolylineTo(hMemDC, points, 0x3FE01)) {
fprintf(stderr, "[!] PolylineTo() Failed: %x\r\n", GetLastError());
}
}

AddEdgeToGET函数对point进行处理,v10和v11循环遍历point数组。V8为rect结构,根据v10 v11的x,y及v8的top,bottom,确定是否向v9申请的空间写标志,如1,-1









在AddEdgeToGET函数中,v10 v11相同的point,基本不做处理,v9的空间也不递增。注意到代码中point[2].y = 0x14,占了157个大数组的0x20个,这个点只是为了控制v9空间的0x30不断递增。在AddEdgeToGET函数中,有3个return分支,只有最后一个return时才会递增申请池0x30,那么我们需要覆盖下一页的bitmap.size.y=-1作为manager,下下页的bitmap作为worker,下页的manager起始偏移为0x1bc0,申请池的起始为0x0fb0。那么0x20个y==0x14的point即可递增pool至manager的相应偏移?答案是确定的。

因为0x14作为pre_point.y或者now_point.y的时候会进入如下处理


















无论如何,v6_pre_y都是保存了两点中小的y值,继续向下看













申请池大小不变的返回的情况只在这两个分支满足时,而top是始终0,bottom的值为0x1f,所以一个y==20的情况即可使pool递增0x60的空间(分别作为pre和now的y),并且这个情况下的内存值的修改是达不到要求(修改sizeBitmap.y无限大),因为now<pre时修改后,后续now>pre时会改回1,所以真正修改目标地址值的point是数组中最后一个元素完成的,这个y不存在<0x1f的情况,所以会直接从前两个return返回








阅读更多博文 »