谜题:打造极小ELF文件输出文件(在Linux环境中精简ELF64文件)

接前文《谜题:打造极小ELF文件输出文件(使用汇编语言通过系统调用来实现)》

在完成了一个232字节的程序后,发现距离186字节的目标还是有一些距离。接下来就要深入研究ELF文件的细节了。

[root@i-a77ugr2f tmp]# readelf -h open
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4000b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         0
  Section header string table index: 0

先通过readelf命令来看一下程序的ELF文件头信息,这里输出的信息都是从ELF文件头中提取得到的。
需要重点关注一下ELF文件头(64字节)和程序头(56*2=112字节)的大小。在64位ELF文件的定义上,文件头和程序头的大小是有明确定义的。
我们可以计算出实际机器指令的大小:总大小-文件头-程序头=232-64-112=56字节。而这56字节的指令我们前面已经通过各种技巧进行了最简化。
那么,接下里的思路只有两个:

  1. 想办法将代码段中的机器指令填充到ELF头和程序头中
  2. 想办法将程序头与文件头进行合并

如上图所示,这是前面得到的232字节的ELF可执行文件的16进制内容,并对文件头和程序头中的字段进行了标注。(备注参考资料)

通过查阅相关资料,我们尝试判断并验证哪些字段是无用的。可以通过下面的cutelf.sh脚本,在使用0xff覆盖一些字节后,观察程序是否仍能正常运行。

cutelf.sh
 #!/bin/sh
test_byte_ff() {
  # modify Ehdr
  seek_lst=({4..15} {20..23} {40..53} {58..63})
  for sk in ${seek_lst[@]}; do
    echo -ne "\xff" | dd of=open bs=1 count=1 seek=${sk} conv=notrunc > /dev/null 2>&1
  done
  # modify Phdr
  seek_lst=({88..95} {112..119} {144..151} {168..175})
  for sk in ${seek_lst[@]}; do
    echo -ne "\xff" | dd of=open bs=1 count=1 seek=${sk} conv=notrunc > /dev/null 2>&1
  done
}
test_byte_ff

经过一番努力,我们知道了在文件头和程序头中哪些字节可以被篡改,且不影响程序功能。然后按照思路一,尝试将代码段中的机器指令填充到ELF文件头和程序头中。
由于ELF文件头和程序头中可以被篡改的字节不是连续的,可以使用汇编指令jmp对应的机器指令0xeb来进行跳转。0xeb后面可以跟一个字节,表示向后跳转的字节数。

[root@i-a77ugr2f tmp]# objdump -d open.o
open.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
   0:   b0 02                   mov    $0x2,%al            # 1
   2:   48 8b 7c 24 10          mov    0x10(%rsp),%rdi     # 2
   7:   0f 05                   syscall                    # 3
   9:   48 89 c7                mov    %rax,%rdi           # 4
   c:   48 31 c0                xor    %rax,%rax           # 5
   f:   be 00 00 00 00          mov    $0x0,%esi           # 6
  14:   66 ba ff ff             mov    $0xffff,%dx         # 7
  18:   0f 05                   syscall                    # 8
  1a:   48 89 c2                mov    %rax,%rdx           # 9
  1d:   48 31 c0                xor    %rax,%rax           # 10
  20:   b0 01                   mov    $0x1,%al            # 11
  22:   48 31 ff                xor    %rdi,%rdi           # 12
  25:   40 b7 01                mov    $0x1,%dil           # 13
  28:   0f 05                   syscall                    # 14
  2a:   48 31 c0                xor    %rax,%rax           # 15
  2d:   b0 03                   mov    $0x3,%al            # 16
  2f:   0f 05                   syscall                    # 17
  31:   b0 3c                   mov    $0x3c,%al           # 18
  33:   0f 05                   syscall                    # 19

我们通过objdump命令可以从open.o文件中粗略查看,ELF文件的代码段中机器指令对应的汇编指令。
再配合jmp的跳转方案,就可以试着将代码段中的部分机器指令填充到ELF文件头和程序头中了。

cutelf.sh
#!/bin/sh
mv_bytes() {
  # 覆盖 e_ident[7~13],将0xb0(176)开始的7个字节(第1、2条指令),复制到0x07(7)开始的位置
  dd if=open of=open bs=1 skip=176 count=7 seek=7 conv=notrunc

  # 覆盖 e_ident[14~15],将0x0e(14)开始的两个字节覆盖为(eb 04),表示从下一个字节算起,向后跳转0x04(4)个字节
  echo -ne "\xeb\x04" | dd of=open bs=1 count=2 seek=14 conv=notrunc

  # 覆盖 e_version[0~1],将0xb7(183)开始的2个字节(第3条指令),复制到0x14(20)开始的位置
  dd if=open of=open bs=1 skip=183 count=2 seek=20 conv=notrunc

  # 覆盖 e_version[2~3],将0x16(22)开始的两个字节覆盖为(eb 10),表示向后跳转0x10(16)个字节
  echo -ne "\xeb\x10" | dd of=open bs=1 count=2 seek=22 conv=notrunc

  # 覆盖 e_shoff[0-7], e_flags[0-2],将0xb9(185)开始的11个字节(第4、5、6条指令),复制到0x28(40)开始的位置
  dd if=open of=open bs=1 skip=185 count=11 seek=40 conv=notrunc

  # 覆盖 e_flags[3], e_ehsize[0],将0x33(51)开始的两个字节覆盖为(eb 05),表示向后跳转0x05(5)个字节
  echo -ne "\xeb\x05" | dd of=open bs=1 count=2 seek=51 conv=notrunc

  # 覆盖 e_shentsize, e_shnum,将0xc4(196)开始的4个字节(第7条指令),复制到0x3a(58)开始的位置
  dd if=open of=open bs=1 skip=196 count=4 seek=58 conv=notrunc

  # 覆盖 e_shstrndx,将0x3e(62)开始的两个字节覆盖为(eb 18),表示向后跳转0x18(24)个字节
  echo -ne "\xeb\x18" | dd of=open bs=1 count=2 seek=62 conv=notrunc

  # 覆盖 p_paddr[0~4],将0xc8(200)开始的5个字节(第8、9条指令),复制到0x58(88)开始的位置
  dd if=open of=open bs=1 skip=200 count=5 seek=88 conv=notrunc

  # 覆盖 p_paddr[5~6],将0x5d(93)开始的两个字节覆盖为(eb 11),表示向后跳转0x11(17)个字节
  echo -ne "\xeb\x11" | dd of=open bs=1 count=2 seek=93 conv=notrunc

  # 覆盖 p_align[0~4],将0xcd(205)开始的5个字节(第10、11条指令),复制到0x70(112)开始的位置
  dd if=open of=open bs=1 skip=205 count=5 seek=112 conv=notrunc

  # 覆盖 p_align[5~6],将0x75(117)开始的两个字节覆盖为(eb 19),表示向后跳转0x19(25)个字节
  echo -ne "\xeb\x19" | dd of=open bs=1 count=2 seek=117 conv=notrunc

  # 覆盖 p_paddr[0~5],将0xd2(210)开始的6个字节(第12、13条指令),复制到0x90(144)开始的位置
  dd if=open of=open bs=1 skip=210 count=6 seek=144 conv=notrunc

  # 覆盖 p_paddr[6~7],将0x96(150)开始的两个字节覆盖为(eb 10),表示向后跳转0x10(16)个字节
  echo -ne "\xeb\x10" | dd of=open bs=1 count=2 seek=150 conv=notrunc

  # 覆盖 p_align[0~7] 和后面的代码段,将0xd8(216)开始的13个字节(第14-19条指令),复制到0xa8(168)开始的位置
  dd if=open of=open bs=1 skip=216 count=48 seek=168 conv=notrunc

  # 从0xb5(181)开始截去之后的字节
  dd if=open of=open bs=1 count=1 skip=181 seek=181

  # 覆盖 e_entry[0~1],调整程序入口地址 0x4000b0->0x400007,将0x18(24)的字节修改为0x07
  echo -ne "\x07" | dd of=open bs=1 count=1 seek=24 conv=notrunc
}
mv_bytes > /dev/null 2>&1

以上代码建议就着上文的图片一起看,再配合注释更容易理解。

[root@i-a77ugr2f tmp]# bash cutelf.sh
[root@i-a77ugr2f tmp]# ./open /etc/passwd
# 可以正常输出
[root@i-a77ugr2f tmp]# ll open
-rwxr-xr-x 1 root root 181 Oct 31 00:15 open

执行cutelf.sh脚本后,就获得了仅181字节的极小ELF文件,符合谜题要求。

posted @ 2022-11-08 00:52  Cathon  阅读(167)  评论(0编辑  收藏  举报