在学习 libc 之前,我们要先懂得 32 位和 64 位的传参模式。
- 32位:是栈里传参,属于后传参
- 64位:是先传参,其参数从左到右依次放入寄存器:
rdi,rsi,rdx,rcx,r8,r9中
接着,我们就要开始学习 PLT 和 GOT 的关系。
参考文章:深入理解GOT表和PLT表 - 知乎
由此文章我们大概可以发现我们是由 GOT 表转到 PLT 表来获得函数真实地址,所以如何发现这些函数的地址呢,这时候我们就想到了两个函数:put 和 write 函数。
第一步:泄露地址
所以下面我们来讲解第一个函数:put 函数。
Put 函数
Put 函数的传参是 libc 中较为简单的,其只要一个参数即可,所以很明显,那个参数就是某个函数的 GOT 地址。可以是 read 或者 put。
32位:其传参方式大概是
p32(puts_plt) + p32(main_addr) + p32(puts_got) # 也可以是其他函数的got
32位传参按顺序看即可,先是调用了 puts 函数,注意需要返回主函数地址才能保证 ROP 链正常运行。最后加上 GOT 函数将其地址表达出来。
64位:其传参方式大概是
p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
# puts_got 也可以是其他函数的 got
64位传参是需要寄存器的,所以按上文所述,先将他放进 rdi 寄存器中。大概原理与 32 位一致。
查找控制寄存器的指令:
ROPgadget --binary xxx --only 'pop|ret' | grep 'rdi'
这是查找寄存器的方法。
Write 函数
接下来就是第二个函数:write 函数。
Write 函数就需要注意,他需要传送三个参数。
参考文章:C语言中write函数 - CSDN博客
大概模式就是如此。Write 函数第一个参数(0代表标准输入,1代表标准输出,2代表标准错误。)write 函数第三个参数是输出的字节数(32位就是4,64位就是8)。
32位:其传参方式大概是
p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
64位:其传参方式大概是
p64(rdi_addr) + p64(1) + p64(rsi_addr) + p64(write_got) + p64(rdx) + p64(8) + p64(write_plt) + p64(main_addr)
所需要注意的是,传参有时可以直接利用其本来就有的参数,而且第三个参数可以不用使用寄存器,有时还会由 rsi 与其他寄存器一起出现,这时第三个参数便可以随便输入了。
接收函数地址
最后就是接受函数地址。
32位目前只看见过:
xxx_addr = u32(r.recv(4))
64位则有:
- 无数据干扰
- 有数据干扰
第二步:计算基址与溢出
第一步已经接收到了,接下来 print 即可,然后我们看他函数后三位,即可调查到他的真实地址,我们可以使用 LibcSearcher 或者 libc database search (blukat.me) 在线网站。
需要注意 LibcSearcher 查找方法:
binsh_addr = next(elf.search(b"/bin/sh"))
然后就可以开始计算地址偏移量了:
libc_base = xxx_addr - 找出的对应函数的offset
最后就是最普通的栈溢出了,至此 libc 就结束了。
总结
上述就是我对于 libc 的部分理解,本人纯小白,若有部分理解不到位请多多指教。
部分例题以后有时间,我会整理......