二进制安全之栈溢出(八)

admin 2024年4月20日01:36:46评论1 views字数 3053阅读10分10秒阅读模式

阅读本文前需要阅读 https://www.zhihu.com/question/21249496 和回答后面的四个链接。

plt && got 表

之前提到有些函数实现的代码是在 libc 中,但是编译时和运行时,libc 可能是不同的版本,函数的偏移位置也不一样,这种情况下,操作系统是怎么找到对应的函数的呢?

以下面的代码为例

#include <stdio.h>
#include <stdlib.h>

void world() {
    printf("World");
}

int main(int argc, char* argv[]) {
    printf("Hello ");
    world();
    return 0;
}

main 函数的汇编代码可以看到

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x000000000040053c <+0>:push   rbp
   0x000000000040053d <+1>:mov    rbp,rsp
   0x0000000000400540 <+4>:sub    rsp,0x10
   0x0000000000400544 <+8>:mov    DWORD PTR [rbp-0x4],edi
   0x0000000000400547 <+11>:mov    QWORD PTR [rbp-0x10],rsi
   0x000000000040054b <+15>:mov    edi,0x4005fa
   0x0000000000400550 <+20>:mov    eax,0x0
   0x0000000000400555 <+25>:call   0x400400 <printf@plt>
   0x000000000040055a <+30>:mov    eax,0x0
   0x000000000040055f <+35>:call   0x400526 <world>
   0x0000000000400564 <+40>:mov    eax,0x0
   0x0000000000400569 <+45>:leave
   0x000000000040056a <+46>:ret

在两个 printf 那里下断点,然后 si 跟进第一个 printf@plt 看下。

=> 0x400400 <printf@plt>:jmp    QWORD PTR [rip+0x200c12]        # 0x601018
 | 0x400406 <printf@plt+6>:push   0x0
 | 0x40040b <printf@plt+11>:jmp    0x4003f0
 | 0x400410 <__libc_start_main@plt>:jmp    QWORD PTR [rip+0x200c0a]        # 0x601020
 | 0x400416 <__libc_start_main@plt+6>:push   0x1
 |->   0x400406 <printf@plt+6>:push   0x0
       0x40040b <printf@plt+11>:jmp    0x4003f0
       0x400410 <__libc_start_main@plt>:jmp    QWORD PTR [rip+0x200c0a]        # 0x601020
       0x400416 <__libc_start_main@plt+6>:push   0x1

这时候 0x601018 存储的地址就是 0x400406,对应的是 printf@plt+6,跳转过去之后,接下来又是 jmp 0x4003f0,之后就是 _dl_runtime_resolve_avx 函数了。

   0x4003f0:push   QWORD PTR [rip+0x200c12]        # 0x601008
=> 0x4003f6:jmp    QWORD PTR [rip+0x200c14]        # 0x601010
 | 0x4003fc:nop    DWORD PTR [rax+0x0]
 | 0x400400 <printf@plt>:jmp    QWORD PTR [rip+0x200c12]        # 0x601018
 | 0x400406 <printf@plt+6>:push   0x0
 | 0x40040b <printf@plt+11>:jmp    0x4003f0
 |->   0x7ffff7dee870 <_dl_runtime_resolve_avx>:push   rbx
       0x7ffff7dee871 <_dl_runtime_resolve_avx+1>:mov    rbx,rsp
       0x7ffff7dee874 <_dl_runtime_resolve_avx+4>:and    rsp,0xffffffffffffffe0
       0x7ffff7dee878 <_dl_runtime_resolve_avx+8>:sub    rsp,0x180

接下来就是一大堆指令,没耐心看了,直接按 c 到了第二个 printf@plt 那里,这时候发现第一个 jmp 的行为已经不一样了。

=> 0x400400 <printf@plt>:jmp    QWORD PTR [rip+0x200c12]        # 0x601018
 | 0x400406 <printf@plt+6>:push   0x0
 | 0x40040b <printf@plt+11>:jmp    0x4003f0
 | 0x400410 <__libc_start_main@plt>:jmp    QWORD PTR [rip+0x200c0a]        # 0x601020
 | 0x400416 <__libc_start_main@plt+6>:push   0x1
 |->   0x7ffff7a62800 <__printf>:sub    rsp,0xd8
       0x7ffff7a62807 <__printf+7>:test   al,al
       0x7ffff7a62809 <__printf+9>:mov    QWORD PTR [rsp+0x28],rsi
       0x7ffff7a6280e <__printf+14>:mov    QWORD PTR [rsp+0x30],rdx

这时候 0x601018 指向的地址已经是 0x7ffff7a62800,在 __printf 中,而之前是在 printf@plt 中。

上面的流程可以看出,对于动态库来说,可执行文件在运行前并不知道它的真实地址,而是需要运行时动态的去解析才能知道,专业的说法是运行时重定位。

上面已经有 plt 的概念了

PLT(Procedure Linkage Table)作用是将位置无关的符号转移到绝对地址。当一个外部符号被调用时,PLT 去引用 GOT 中的其符号对应的绝对地址,然后转入并执行。

GOT(Global Offset Table)用于记录在 ELF 文件中所用到的共享库中符号的绝对地址。在程序刚开始运行时,GOT 表项是空的,当符号第一次被调用时会动态解析符号的绝对地址然后转去执行,并将被解析符号的绝对地址记录在 GOT 中,第二次调用同一符号时,由于 GOT 中已经记录了其绝对地址,直接转去执行即可(不用重新解析)。

plt 表的信息也可以通过 readelf -r exe_file 看到

Relocation section '.rela.plt' at offset 0x398 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

利用

  • 如果代码存在任意写,那就可以把某些函数的 got 表改写为 shellcode 的地址,和覆盖返回地址的原理是一样的。
  • 如果代码存在任意读,那就可以读取某些函数的 got 表中的地址,然后结合 libc 推算出 libc 的加载基址。

## 练习题

- source:strcpy.me

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月20日01:36:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   二进制安全之栈溢出(八)http://cn-sec.com/archives/2631658.html

发表评论

匿名网友 填写信息