GUN/Linux通用Glibc库是如何操控.Net 7的CLR
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GUN/Linux通用Glibc库是如何操控.Net 7的CLR相关的知识,希望对你有一定的参考价值。
楔子
今天是北方小年,祝北方的朋友小年快乐。
最新的.Net 7 是如何被Linux(Ubuntu22.04)加载的,运行微软程序的呢?本篇看下它的Glibc库里面的运作模式。
Glibc
Glibc是套C语言运行库,GUN/Linux系统最底层的库,几乎所有的Linux函数运行都需要靠它---来自百科。
本篇主要用到,libc_start_call_main.h和libc-start.c两个库文件。
原理
CLR在Ubuntu上运行的最重要的一点就是通过Glibc来加载./corerun命令的参数abc.dll操纵的。它通过调用Glibc的入口_start函数,一系列运作调用CLR的main函数开始进入运行时。
调看
先上lldb,同样在PreStubWorker处打打个断点
运行到此函数
查看下堆栈
注意12,11,10三个栈,此三栈是调用CLR的前奏函数
12帧:_start
11帧:__libc_start_main_impl(libc-start.c里面的函数)
10帧:__libc_start_call_main(libc_start_call_main.h里的函数)
9帧及其以后都是CLR范畴了。
问题
b在_start上下个断点
把PreStubWorker断点删掉,因为会干扰后面的调试
c让整个程序运行完成
然后r再把程序运行一遍,断点会停在最首先调用CLR的地方,也就是上面的_start函数
可以看到它是一个汇编代码写成的函数,查看下它的函数体
在地址0x55555556bc8f处call了一个地址,在此处下一个断点。C运行到此处。
查看下call到底调用了哪个地址
可以看到call的地址是0x0000555555583f1a,查看下这个地址的内存和汇编
可以看到内存不是一个地址,而汇编则是未被编译的机器码。这到底咋回事呢?
分析
si单步进入上面的call指令
发现call指令跳转的地址是0x00007ffff7a7bdc0。
而上面0x0000555555583f1a地址内存如下
对照下可以发现需要把0x0000555555583f1a+6=0x0000555555583f20这个地址才能完整的跳转到0x00007ffff7a7bdc0这个地址,才能准确的调到堆栈的__libc_start_main_impl函数。
那么这个多余的6个字节哪里来的呢?
这里需要注意下__libc_start_main_impl这个函数有7个参数,所以在Ubuntu22.04上面传参的顺序是:rdi, rsi, rdx, rcx, r8, r9。
这里可以一一对照下。
rdi就是__libc_start_main_impl函数的一个参数,rsi就是第二个argc=2以此类推。
返回问题所在,重新运行到call指令处,查看下这个_start函数原型
call的机器码是ff 15,后面的01828b是相对位置,call的下一条指令地址是0x55555556bc95两者相加,0x55555556bc95+0x01828b=0x555555583F20,查看下这个地址内存
刚好是0x00007ffff7a7bdc0,也就是__libc_start_main_impl函数的地址。
最后看下另外两个函数调用,在PreStubWorker下断点。重新运行,查看堆栈。
当运行到了Glibc里面的__libc_start_main_impl函数之后,反汇编__libc_start_main_impl函数
查看当前堆栈
可以看到帧11也就是__libc_start_main_impl函数所在的帧,的地址是0x00007ffff7a7be40,转到这个地址
这个地址的上一个地址里面所包含的指令调用了一个call,这个call跳转的刚好是__libc_start_call_main。继续看下__libc_start_call_main函数
由于__libc_start_call_main在帧10,它的地址是0x00007ffff7a7bd90
此处看下它的地址处汇编
可以看到帧10地址的上一条地址调用了call rax,那么rax应该就是corerun.main也就是CLR的入口地址。如何证明呢?
在帧10的上一条地址0x7ffff7a7bd8e处下个断点。然后删掉其它断点避免干扰,r命令再次运行到0x7ffff7a7bd8e处。
读取下rax的值
可以看到它是corerun.main函数的地址无疑了
那么至此为止,Glibc操控CLR的所有关节已打通。如果说缺点什么,那么就是如ELF这个文件结构了。
总结
一:_start函数
看下_start的第一条指令
0x55555556bc70 <+0>: f3 0f 1e fa endbr64
这条指令是预测指令,防止恶意修改。
_start最后一条指令
0x55555556bc95 <+37>: f4 hlt
这条指令是当前机器静默,也就是啥都不做,等待苏醒。
那么问题其实很清楚了,call的跳转不是后面带的立即数的地址,而是相对地址加上下一条指令的地址。才是跳转的真正地址。所以才造成了差6个字节的假象。
二frame帧
它的地址主要存储了call指令下一地址。帧上附带有参数,以及函数调用所在的行列。
三:
注意命令:
di -f -mb 反汇编当前断点所在的函数
di -bn __libc_start_main_impl 通过函数名称反汇编
di -s 0x0001 -c 24 通过指定的地址-x0001反汇编
di -l 反汇编当前行
结尾
作者:江湖评谈。
以上是关于GUN/Linux通用Glibc库是如何操控.Net 7的CLR的主要内容,如果未能解决你的问题,请参考以下文章