Objcopy --remove-section 不会留下与 --add-section 之前的原始文件相同的 ELF 二进制文件

问题描述 投票:0回答:1

我正在尝试使用 objcopy 在我的二进制文件中嵌入 GPG 签名,以便我可以在 Linux 系统上更新时验证它。 问题是,在删除添加的签名部分后,objcopy 似乎保留了 ELF 二进制文件的修改。

这就是我正在做的签名

gpg --yes --output sig_before --detach-sign --sign binary_before
objcopy --add-section sigdata=sig_before binary_before binary_signed

我正在做什么来验证它

objcopy --remove-section=sigdata binary_signed binary_after
objcopy --dump-section sigdata=sig_after binary_signed
gpg --verify sig_after binary_after

问题是验证总是失败,因为

binary_after
与文件大小所示的
binary_before
不同。

-rwxr-xr-x 1 root root 4608224 Oct 14 15:46 binary_after
-rwxr-xr-x 1 root root 4608135 Oct 14 14:48 binary_before
-rwxr-xr-x 1 root root 4608416 Oct 14 15:46 binary_signed
-rw-r--r-- 1 root root     119 Oct 14 15:46 sig_after
-rw-r--r-- 1 root root     119 Oct 14 15:46 sig_before

我可以做到

gpg --verify sig_after binary_before
并且它得到验证。

我正在查看章节标题,但这让我有点迷失:

# readelf -S binary_before 
There are 13 section headers, starting at offset 0x158:
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       00000000002130ec  0000000000000000  AX       0     0     32
  [ 2] .rodata           PROGBITS         0000000000615000  00215000
       00000000000e4ea1  0000000000000000   A       0     0     32
  [ 3] .typelink         PROGBITS         00000000006f9ec0  002f9ec0
       000000000000171c  0000000000000000   A       0     0     32
  [ 4] .itablink         PROGBITS         00000000006fb5e0  002fb5e0
       00000000000006f0  0000000000000000   A       0     0     32
  [ 5] .gosymtab         PROGBITS         00000000006fbcd0  002fbcd0
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000006fbce0  002fbce0
       0000000000146ef8  0000000000000000   A       0     0     32
  [ 7] .go.buildinfo     PROGBITS         0000000000843000  00443000
       0000000000000250  0000000000000000  WA       0     0     16
  [ 8] .noptrdata        PROGBITS         0000000000843260  00443260
       0000000000015680  0000000000000000  WA       0     0     32
  [ 9] .data             PROGBITS         00000000008588e0  004588e0
       000000000000c450  0000000000000000  WA       0     0     32
  [10] .bss              NOBITS           0000000000864d40  00464d40
       00000000000213c0  0000000000000000  WA       0     0     32
  [11] .noptrbss         NOBITS           0000000000886100  00486100
       000000000000dc50  0000000000000000  WA       0     0     32
  [12] .shstrtab         STRTAB           0000000000000000  00465000
       0000000000000087  0000000000000000           0     0     1

# readelf -S binary_after
There are 13 section headers, starting at offset 0x464da0:
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       00000000002130ec  0000000000000000  AX       0     0     32
  [ 2] .rodata           PROGBITS         0000000000615000  00215000
       00000000000e4ea1  0000000000000000   A       0     0     32
  [ 3] .typelink         PROGBITS         00000000006f9ec0  002f9ec0
       000000000000171c  0000000000000000   A       0     0     32
  [ 4] .itablink         PROGBITS         00000000006fb5e0  002fb5e0
       00000000000006f0  0000000000000000   A       0     0     32
  [ 5] .gosymtab         PROGBITS         00000000006fbcd0  002fbcd0
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000006fbce0  002fbce0
       0000000000146ef8  0000000000000000   A       0     0     32
  [ 7] .go.buildinfo     PROGBITS         0000000000843000  00443000
       0000000000000250  0000000000000000  WA       0     0     16
  [ 8] .noptrdata        PROGBITS         0000000000843260  00443260
       0000000000015680  0000000000000000  WA       0     0     32
  [ 9] .data             PROGBITS         00000000008588e0  004588e0
       000000000000c450  0000000000000000  WA       0     0     32
  [10] .bss              NOBITS           0000000000864d40  00464d30
       00000000000213c0  0000000000000000  WA       0     0     32
  [11] .noptrbss         NOBITS           0000000000886100  00464d30
       000000000000dc50  0000000000000000  WA       0     0     32
  [12] .shstrtab         STRTAB           0000000000000000  00464d30
       0000000000000070  0000000000000000           0     0     1

objcopy 在二进制文件中留下了什么?有什么方法可以在不改变输出二进制文件的情况下嵌入签名吗?

go elf objcopy gpg-signature
1个回答
0
投票

从部分名称可以明显看出,您的二进制文件是使用 Google 的 golang 工具链构建的 Go 程序。

有什么问题吗?

看这个:

$ cat binary_before.go 
package main
import "fmt"
func main() {
    fmt.Println("hello world")
}

$ go build binary_before.go

$ ./binary_before 
hello world

binary_before
的大小为:

$ stat -c "%s" binary_before
1893833

这是它的 ELF 标头:

$ readelf -h binary_before
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:               0x463900
  Start of program headers:          64 (bytes into file)
  Start of section headers:          400 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         23
  Section header string table index: 20

请注意,节头从字节 400 (= 0x190) 开始。就在6个56字节程序之后 headers,从字节 64 开始,就在 ELF 标头本身之后。这些部分本身 位于节标题所说的任何位置,但它们都必须在 23 64 字节结束之后 节标题,即字节 400 + (23*64) = 1872 之后。

readelf -S binary_before
将向您显示 确实如此。

让我们用 objcopy 复制程序:

$ objcopy binary_before binary_copy
$ ./binary_copy 
hello world
    

副本尺寸为:

$ stat -c "%s" binary_copy
1893232

即 1893833 - 1893232 = 比

binary_before
小 601 个字节。和 这是
binary_copy

的 ELF 标头
$ readelf -h binary_copy
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:               0x463900
  Start of program headers:          64 (bytes into file)
  Start of section headers:          1891760 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         23
  Section header string table index: 22
  

不一样。值得注意的是,节头现在从字节 1891760 = 0x1cddb0 开始,而不是字节 400。 那不是在程序标题之后。它在哪里?以下是该部分的详细信息:

$ readelf -S binary_copy
There are 23 section headers, starting at offset 0x1cddb0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       000000000007f93a  0000000000000000  AX       0     0     32
  [ 2] .rodata           PROGBITS         0000000000481000  00081000
       000000000003d2da  0000000000000000   A       0     0     32
  [ 3] .typelink         PROGBITS         00000000004be2e0  000be2e0
       0000000000000590  0000000000000000   A       0     0     32
  [ 4] .itablink         PROGBITS         00000000004be880  000be880
       0000000000000058  0000000000000000   A       0     0     32
  [ 5] .gosymtab         PROGBITS         00000000004be8d8  000be8d8
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000004be8e0  000be8e0
       00000000000645d8  0000000000000000   A       0     0     32
  [ 7] .go.buildinfo     PROGBITS         0000000000523000  00123000
       0000000000000130  0000000000000000  WA       0     0     16
  [ 8] .noptrdata        PROGBITS         0000000000523140  00123140
       00000000000054a0  0000000000000000  WA       0     0     32
  [ 9] .data             PROGBITS         00000000005285e0  001285e0
       0000000000004250  0000000000000000  WA       0     0     32
  [10] .bss              NOBITS           000000000052c840  0012c830
       000000000005fb30  0000000000000000  WA       0     0     32
  [11] .noptrbss         NOBITS           000000000058c380  0012c830
       0000000000003a40  0000000000000000  WA       0     0     32
  [12] .debug_abbrev     PROGBITS         0000000000000000  0012c830
       0000000000000135  0000000000000000   C       0     0     1
  [13] .debug_line       PROGBITS         0000000000000000  0012c965
       000000000001f60b  0000000000000000   C       0     0     1
  [14] .debug_frame      PROGBITS         0000000000000000  0014bf70
       0000000000006232  0000000000000000   C       0     0     1
  [15] .debug_gdb_s[...] PROGBITS         0000000000000000  001521a2
       000000000000002d  0000000000000000           0     0     1
  [16] .debug_info       PROGBITS         0000000000000000  001521cf
       000000000003d87e  0000000000000000   C       0     0     1
  [17] .debug_loc        PROGBITS         0000000000000000  0018fa4d
       000000000001c466  0000000000000000   C       0     0     1
  [18] .debug_ranges     PROGBITS         0000000000000000  001abeb3
       000000000000b10f  0000000000000000   C       0     0     1
  [19] .note.go.buildid  NOTE             0000000000400f9c  00000f9c
       0000000000000064  0000000000000000   A       0     0     4
  [20] .symtab           SYMTAB           0000000000000000  001b6fc8
       000000000000baa8  0000000000000018          21    95     8
  [21] .strtab           STRTAB           0000000000000000  001c2a70
       000000000000b24a  0000000000000000           0     0     1
  [22] .shstrtab         STRTAB           0000000000000000  001cdcba
       00000000000000f0  0000000000000000           0     0     1

最后一节,

,shstrtab
(节标题字符串表)从 0x1cdcba 开始, 长度为 0xf0 字节,因此以 0x1cddaa = 字节 1891754 结束。这意味着该部分 字节 1891760 = 0x1cddb0 处的标头从紧随其后的下一个 16 字节边界开始 这些部分本身。

仅通过“复制”

binary_before
binary_copy
- 没有添加或删除任何部分 -
obcopy
已重新排列输出 ELF 数据布局,从程序头、节头、节程序头、节、节头。这样做节省了 601 字节 节/段对齐填充。从
binary_before
中提取的数字签名是 已经注定与
binary_copy
不同。

objcopy
可以这样做。
ELF
格式不指定顺序 程序标题、节标题和节。程序标题和章节分隔符 是 ELF 标头所说的位置,而节是节标头所说的位置 他们是。

但是如果你

objcopy
编辑了一个已经被修改过的程序,你就不会看到这种重新排列。 使用 GNU 工具链进行编译和链接。那是因为 ELF 布局 Program Headers、Sections、Section Headers,虽然不是强制性的,但 GNU ELF 工具遵循的默认布局,包括 GNU 链接器 它的库存配置和 GNU
objcopy

出于我不知道的原因,golang工具链更喜欢程序头、节头、节 ELF 布局,而

objcopy
恢复了传统的 GNU 布局,这会挫败您使用
objcopy

验证数字签名的尝试

解决方案1

不要使用 Google 的 Go 工具链来构建程序,而是使用 GCC(如果已打包) 对于您的发行版1,或者如果您愿意从源代码构建它。 如果您无法做到其中一项,请使用解决方案 2

GCC Go 坚持传统的 GNU ELF 布局。参见:

$ gccgo -o binary_before_gcc binary_before.go
$ ./binary_before_gcc 
hello world

$ objcopy binary_before_gcc binary_before_gcc_copy
$ ./binary_before_gcc_copy 
hello world

objcopy
ed GCC 二进制文件与原始版本相同:

$ cmp binary_before_gcc binary_before_gcc_copy ; echo Done
Done

因此它将产生相同的数字签名。

解决方案2

在您的构建中,使用

obcopy
binary_before
“复制”到(比如说)
binary_copy
,就像我们已经完成的那样。那 为您提供一个功能等效的程序(如
binary_before_gcc
),它具有传统的 GNU ELF 布局。将
binary_before
视为一次性中间文件;将
binary_before_copy
视为您的 程序。

然后再次使用

objcopy
,就像您已经完成的那样,对
binary_copy
进行数字签名并验证数字 签名。

像这样。

制作签名程序:

$ gpg --yes --output sig_before --detach-sign --sign binary_copy
$ objcopy --add-section sigdata=sig_before binary_copy binary_copy_signed

验证签名:

$ objcopy --remove-section=sigdata binary_copy_signed binary_copy_after
$ objcopy --dump-section sigdata=sig_after binary_copy_signed
$ gpg --verify sig_after binary_copy_after
gpg: Signature made Tue 15 Oct 2024 18:04:09 BST
gpg:                using EDDSA key 64F0C44FABCDA322C7977E6BE602681EEE59BA4E
gpg: Good signature from "Mike Kinghan (Throwaway key) <[email protected]>" [ultimate]

  1. 它是为最近的 Debian 和 Ubuntu 版本打包的:
    sudo apt install gccgo
© www.soinside.com 2019 - 2024. All rights reserved.