在 Linux 中如何确定 PIE 可执行文件的文本部分的地址? [英] How is the address of the text section of a PIE executable determined in Linux?

查看:38
本文介绍了在 Linux 中如何确定 PIE 可执行文件的文本部分的地址?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先我尝试对其进行逆向工程:

printf '#include int main() {puts("你好世界");}' >主文件gcc -std=c99 -pie -fpie -ggdb3 -o pie main.c回声 2 |须藤三通/proc/sys/kernel/randomize_va_spacereadelf -s ./pie |grep -E 'main$'gdb -batch -nh -ex '设置禁用随机化关闭' -ex '开始' -ex '信息行' -ex '开始' -ex '信息行' -ex '设置禁用随机化' -ex '开始' -ex '信息行' -ex '开始' -ex '信息行' ./饼;

输出:

64: 000000000000063a 23 FUNC GLOBAL DEFAULT 14 main临时断点1,main()在main.c:44 puts("你好世界");main.c"的第 4 行从地址 0x5575f5fd263e  开始.并在 0x5575f5fd264f <main+21> 处结束.0x5575f5fd263e 处的临时断点 2:文件 main.c,第 4 行.临时断点2,main()在main.c:44 puts("你好世界");main.c"的第 4 行从地址 0x55e3fbc9363e  开始.并在 0x55e3fbc9364f <main+21> 处结束.0x55e3fbc9363e 处的临时断点 3:文件 main.c,第 4 行.临时断点3,main()在main.c:44 puts("你好世界");main.c"的第 4 行从地址 0x555555555463e  开始.并在 0x55555555464f <main+21> 处结束.0x55555555463e 处的临时断点 4:文件 main.c,第 4 行.临时断点4,main()在main.c:44 puts("你好世界");main.c"的第 4 行从地址 0x555555555463e  开始.并在 0x55555555464f <main+21> 处结束.

表示它是0x555555554000+随机偏移量+63e.

但后来我尝试为 555555554 搜索 Linux 内核和 glibc 源代码,但没有成功.

哪个代码的哪一部分计算该地址?

我在回答时遇到了这个:gcc 和 ld 中位置无关的可执行文件的 -fPIE 选项是什么?

解决方案

0x555555554000 的一些Internet 搜索 给出了提示:ThreadSanitizer https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual

<块引用>

问:当我运行程序时,它说:致命:ThreadSanitizer 无法映射影子内存(某些内容已映射到 0x555555554000 < 0x7cf000000000).该怎么办?您需要启用 ASLR:

 $ echo 2 >/proc/sys/kernel/randomize_va_space

这可能会在未来的内核中修复,请参阅 https://bugzilla.kernel.org/show_bug.cgi?id=66721...

 $ gdb -ex '设置禁用随机化关闭'--args ./a.out

https://lwn.net/Articles/730120/ 稳定的内核更新."由 hmh(订阅者)于 2017 年 8 月 7 日 20:40 UTC(星期一)发布https://marc.info/?t=150213704600001&r=1&w=2(https://patchwork.kernel.org/patch/9886105/提交 c715b72c1ba4)

<块引用>

将 x86_64 和 arm64 PIE 基础从 0x555555554000 移动到 0x000100000000破坏了 AddressSanitizer.这是对以下内容的部分还原:

恢复的代码是:

<块引用>

b/arch/arm64/include/asm/elf.h/** 这是 PIE(带 INTERP 的 ET_DYN)加载的基本位置.在- * 64 位,这被提升到 4GB 以保留整个 32 位地址+ * 64 位,这是 4GB 以上,以便将整个 32 位地址 * 空间开放给想要使用该区域作为 32 位指针的东西.*/-#define ELF_ET_DYN_BASE 0x100000000UL+#define ELF_ET_DYN_BASE (2 * TASK_SIZE_64/3)+++ b/arch/x86/include/asm/elf.h/** 这是 PIE(带 INTERP 的 ET_DYN)加载的基本位置.在- * 64 位,这被提升到 4GB 以保留整个 32 位地址+ * 64 位,这是 4GB 以上留下整个 32 位地址* 为想要使用 32 位指针的区域开放的空间.*/#define ELF_ET_DYN_BASE (mmap_is_ia32() ? 0x000400000UL : - 0x100000000UL)+ (TASK_SIZE/3 * 2))

因此,0x55​​5555554000 与 ELF_ET_DYN_BASE 宏有关(参考 fs/binfmt_elf.c对于 ET_DYN 作为未随机化的 load_bias),对于 x86_64 和 arm64,它就像 TASK_SIZE 的 2/3.当没有 CONFIG_X86_32 时,x86_64 的 TASK_SIZE 为 2^47 - arch/x86/include/asm/processor.h

中的一页

/** 用户空间进程大小.47 位减去一个保护页.守卫* 在 Intel CPU 上需要页面:如果 SYSCALL 指令位于* 可能的最高规范用户空间地址,那么* 系统调用将以非规范返回进入内核* 地址,SYSRET 会爆炸危险.我们避免这种情况*防止任何东西被映射的特殊问题* 在最大规范地址.*/#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)

旧版本:

/** 用户空间进程大小.47 位减去一个保护页.*/#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)

较新的版本还支持 5level__VIRTUAL_MASK_SHIFT 56 位 - v4.17/source/arch/x86/include/asm/processor.h(但之前不想用由用户启用 + commit b569bab78d8d ".. 并非所有用户空间都准备好处理宽地址")).

因此,0x55​​5555554000 被四舍五入(通过 <代码>load_bias = ELF_PAGESTART(load_bias - vaddr);, vaddr 为零)来自公式(2^47-1page)*(2/3)(或2^56 对于更大的系统):

$ echo 'obase=16;(2^47-4096)/3*2'|bc -q555555554AAA$ echo 'obase=16;(2^56-4096)/3*2'|bc -qAAAAAAAAAAA000

2/3 * TASK_SIZE 的一些历史:

<块引用>

几乎所有的拱门都将 ELF_ET_DYN_BASE 定义为 TASK_SIZE 的 2/3.尽管似乎某些架构以错误的方式执行此操作.问题是 2*TASK_SIZE 可能会溢出 32 位,所以真正的 ELF_ET_DYN_BASE变得错误.通过在之前除以 TASK_SIZE 来修复此溢出乘法:(TASK_SIZE/3 * 2)

<块引用>

diff --git a/include/asm-i386/elf.h b/include/asm-i386/elf.h+/* 这是 ET_DYN 程序在执行时加载的位置.典型的+ 使用它来调用./ld.so someprog"来测试新版本的+ 装载机.我们需要确保它不受程序影响+ 它将执行",并且 brk 有足够的空间.*/++#define ELF_ET_DYN_BASE (2 * TASK_SIZE/3)

First I tried to reverse engineer it a bit:

printf '
#include <stdio.h>
int main() {
    puts("hello world");
}
' > main.c
gcc -std=c99 -pie -fpie -ggdb3 -o pie main.c
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
readelf -s ./pie | grep -E 'main$'
gdb -batch -nh 
  -ex 'set disable-randomization off' 
  -ex 'start' -ex 'info line' 
  -ex 'start' -ex 'info line' 
  -ex 'set disable-randomization on' 
  -ex 'start' -ex 'info line' 
  -ex 'start' -ex 'info line' 
  ./pie 
;

Output:

64: 000000000000063a    23 FUNC    GLOBAL DEFAULT   14 main
Temporary breakpoint 1, main () at main.c:4
4           puts("hello world");
Line 4 of "main.c" starts at address 0x5575f5fd263e <main+4> and ends at 0x5575f5fd264f <main+21>.
Temporary breakpoint 2 at 0x5575f5fd263e: file main.c, line 4.

Temporary breakpoint 2, main () at main.c:4
4           puts("hello world");
Line 4 of "main.c" starts at address 0x55e3fbc9363e <main+4> and ends at 0x55e3fbc9364f <main+21>.
Temporary breakpoint 3 at 0x55e3fbc9363e: file main.c, line 4.

Temporary breakpoint 3, main () at main.c:4
4           puts("hello world");
Line 4 of "main.c" starts at address 0x55555555463e <main+4> and ends at 0x55555555464f <main+21>.
Temporary breakpoint 4 at 0x55555555463e: file main.c, line 4.

Temporary breakpoint 4, main () at main.c:4
4           puts("hello world");
Line 4 of "main.c" starts at address 0x55555555463e <main+4> and ends at 0x55555555464f <main+21>.

which indicates that it is 0x555555554000 + random offset + 63e.

But then I tried to grep the Linux kernel and glibc source code for 555555554 and there were no hits.

Which part of which code calculates that address?

I came across this while answering: What is the -fPIE option for position-independent executables in gcc and ld?

解决方案

Some Internet search of 0x555555554000 gives hints: there were problems with ThreadSanitizer https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual

Q: When I run the program, it says: FATAL: ThreadSanitizer can not mmap the shadow memory (something is mapped at 0x555555554000 < 0x7cf000000000). What to do? You need to enable ASLR:

 $ echo 2 >/proc/sys/kernel/randomize_va_space

This may be fixed in future kernels, see https://bugzilla.kernel.org/show_bug.cgi?id=66721 ...

 $ gdb -ex 'set disable-randomization off' --args ./a.out

and https://lwn.net/Articles/730120/ "Stable kernel updates." Posted Aug 7, 2017 20:40 UTC (Mon) by hmh (subscriber) https://marc.info/?t=150213704600001&r=1&w=2 (https://patchwork.kernel.org/patch/9886105/, commit c715b72c1ba4)

Moving the x86_64 and arm64 PIE base from 0x555555554000 to 0x000100000000 broke AddressSanitizer. This is a partial revert of:

Reverted code was:

b/arch/arm64/include/asm/elf.h
 /*
  * This is the base location for PIE (ET_DYN with INTERP) loads. On
- * 64-bit, this is raised to 4GB to leave the entire 32-bit address
+ * 64-bit, this is above 4GB to leave the entire 32-bit address   * space open for things that want to use the area for 32-bit pointers.   */
-#define ELF_ET_DYN_BASE      0x100000000UL
+#define ELF_ET_DYN_BASE      (2 * TASK_SIZE_64 / 3)


+++ b/arch/x86/include/asm/elf.h
 /*
  * This is the base location for PIE (ET_DYN with INTERP) loads. On
- * 64-bit, this is raised to 4GB to leave the entire 32-bit address
+ * 64-bit, this is above 4GB to leave the entire 32-bit address
  * space open for things that want to use the area for 32-bit pointers.
  */
 #define ELF_ET_DYN_BASE      (mmap_is_ia32() ? 0x000400000UL : 
-                       0x100000000UL)
+                       (TASK_SIZE / 3 * 2))

So, 0x555555554000 is related with ELF_ET_DYN_BASE macro (referenced in fs/binfmt_elf.c for ET_DYN as not randomized load_bias) and for x86_64 and arm64 it is like 2/3 of TASK_SIZE. When there is no CONFIG_X86_32, x86_64 has TASK_SIZE of 2^47 - one page in arch/x86/include/asm/processor.h

/*
 * User space process size. 47bits minus one guard page.  The guard
 * page is necessary on Intel CPUs: if a SYSCALL instruction is at
 * the highest possible canonical userspace address, then that
 * syscall will enter the kernel with a non-canonical return
 * address, and SYSRET will explode dangerously.  We avoid this
 * particular problem by preventing anything from being mapped
 * at the maximum canonical address.
 */
#define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE)

Older versions:

/*
 * User space process size. 47bits minus one guard page.
 */
#define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE)

Newer versions also have support of 5level with __VIRTUAL_MASK_SHIFT of 56 bit - v4.17/source/arch/x86/include/asm/processor.h (but don't want to use it before enabled by user + commit b569bab78d8d ".. Not all user space is ready to handle wide addresses")).

So, 0x555555554000 is rounded down (by load_bias = ELF_PAGESTART(load_bias - vaddr);, vaddr is zero) from the formula (2^47-1page)*(2/3) (or 2^56 for larger systems):

$ echo 'obase=16; (2^47-4096)/3*2'| bc -q
555555554AAA
$ echo 'obase=16; (2^56-4096)/3*2'| bc -q
AAAAAAAAAAA000

Some history of 2/3 * TASK_SIZE:

Almost all arches define ELF_ET_DYN_BASE as 2/3 of TASK_SIZE. Though it seems that some architectures do this in a wrong way. The problem is that 2*TASK_SIZE may overflow 32-bits so the real ELF_ET_DYN_BASE becomes wrong. Fix this overflow by dividing TASK_SIZE prior to multiplying: (TASK_SIZE / 3 * 2)

diff --git a/include/asm-i386/elf.h b/include/asm-i386/elf.h
+/* This is the location that an ET_DYN program is loaded if exec'ed.  Typical
+   use of this is to invoke "./ld.so someprog" to test out a new version of
+   the loader.  We need to make sure that it is out of the way of the program
+   that it will "exec", and that there is sufficient room for the brk.  */
+
+#define ELF_ET_DYN_BASE         (2 * TASK_SIZE / 3)

这篇关于在 Linux 中如何确定 PIE 可执行文件的文本部分的地址?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆