1、2017-0ctf-EasiestPrintf
checkseck
1 | Arch: i386-32-little |
got 表不可改
程序逻辑很简单,先给你一个真实地址printf("%#x\n", *v1);
,之后有一次printf(s)格式化字符串的机会,就调用exit退出 ,所以我们需要在这次printf(s)后就控制 EIP
需要注意的是,程序在leave前进行了一次随机抬栈v3 = alloca(16 * ((buf + 30) / 0x10u));
所以想直接通过printf控制栈是不太可能的,因为偏移每次都不一样。而且got表不可改,也不存在将exit@got改为main。
这里需要知道一个点,printf 是会调用malloc和free函数的,在它需要输入一串比较长的字符时,需要临时申请空间来存放这些字符,输出后再调用free释放这部分空间。
所以这题的思路就是,将malloc_hook利用printf 修改为onegadget,然后通过%100000c
来触发malloc调用onegadget
exp:
1 | from pwn import * |
2、easy_printf
这道题来自星盟pwn平台,出自EX师傅的手,同时欢迎大家来我们的pwn平台刷题,题目质量绝对杠杠的
保护全开,环境是libc2.27,程序两个功能,1、调用printf_chk;2、调用printf且存在格式化字符串漏洞的
1 | .text:0000000000000ADD call _printf |
可以看到调用完printf用程序也是直接退出了,所以利用方式还是跟0ctf一样通过调用malloc来get shell,但是首先要拿到libc_base
回头看printf_chk函数,printf_chk函数可以有效地阻拦格式化字符串的攻击,无法直接使用%x$p
,也无法使用%n
%a leak libc
以下面代码为例
1 |
|
64位下的__printf_chk
编译 gcc -o test -t -O1 test.c
1 | ► 0x400517 <main> sub rsp, 8 |
第一句,printf("%d,%d,%d,%d,%d,%d\n",1,2,3,4,5,6);
遵守一般的调用约定,前6个参数通过寄存器rdi
、rsi
、rdx
、rcx
、rcx
、r8
、r9
,剩余的参数通过栈传递
1 | 0x40054b <main+52> movsd xmm5, qword ptr [rip + 0x19d] |
第二句printf("%f,%f,%f,%f,%f,%f\n",1.0,2.0,3.0,4.0,5.0,6.0);
浮点数通过浮点数寄存器xmm0
到xmm7
,剩下的参数通过栈来传递
1 | 0x400591 <main+122> movsd xmm2, qword ptr [rip + 0x157] |
第三句printf("%d,%f,%d,%f,%d,%f\n",1,2.0,3,4.0,5,6.0);
和预想一样,整数存入整数寄存器,浮点数存入浮点数寄存器
1 | 0x4005cf <main+184> mov r8d, 6 |
第四句printf("%d,%d,%d,%f,%f,%f\n",1.0,2.0,3.0,4,5,6);1
编译器在编译时会根据格式化字符串的值选择寄存器的类型而不是通过格式化字符串选择
那么,%a leak 的值是哪的呢?
第一句printf("%d,%d,%d,%d,%d,%d\n",1,2,3,4,5,6);
跟进__printf_chk
函数,发现这里进行了堆栈操作,将之前寄存器的值压入栈中,之后一个条件跳转,接下来就是正常的执行流程了
1 | 0x7ffff7b161e1 <__printf_chk+17> test al, al |
第二句printf("%f,%f,%f,%f,%f,%f\n",1.0,2.0,3.0,4.0,5.0,6.0);
跟__printf_chk
函数,发现这里虽然 没有使用整数寄存器,但是同样将整数寄存器压入栈中,之后 由于未符合条件未发生跳转,接下来将浮点数寄存器压入栈中
1 | 0x7ffff7b161e1 <__printf_chk+17> test al, al |
那么,这个寄存器al
是什么呢,在调用__printf_chk
之前 会对eax
赋值,其值为浮点数的个数,如果没有浮点数,其值为0,0x400587 <main+112> mov eax, 6
到这里我们可以推测栈的布局如下
1 | +--------------------------+ |
对于格式化字符串,在栈中有一段shadow space
,用于将寄存器中的值压栈,这样有利于va_start (ap, format);
的实现
shadow space
中有一段栈空间用于保存整数型变量,一段栈空间用于保存浮点数类型变量,两者互不影响
在开始的时候会将所有用于传递整数类型的寄存器rdx
、rcx
、r8
、r9
压入栈用
之后会判断al
的值是否为0,al
为参数列表中使用xmm寄存器的个数,当al
不为0时,会将所有的浮点数寄存器压入栈中
然后根据va_start (ap, format);
读取相应栈上的值
所以,由于程序编译时参数列表并没有值,所以不会将浮点数寄存器压栈,但是在根据va_start(ap,format);
读取的时候仍然会从shadow space
中对应浮点数部分读取,因此我们leak出的是栈上本就存在的垃圾值
接下来的操作就跟上一题差不多了
exp:
1 | #coding:utf-8 |