使用GDB修改二进制

46 阅读4分钟

背景

最近工作中用到了gdb修改二进制指令,浅浅记录一下,分享给大家。

原理

golang例子:

package main

func main() {
   go loop()
   for {
      x = make([]byte, 10000)
   }
}

var x []byte
var a [1000]*int
var b [1000]*int
var c [1000]*int

func loop() {
   for {
      assign()
   }
}

//go:noinline
func assign() {
   a = [1000]*int{}
   b = [1000]*int{}
   c = [1000]*int{}
}

缺省情况下,gdb是以只读方式加载程序的。需先指定加载方式为可写, 再通过file命令加载二进制文件:

(gdb) set write on
(gdb) show write
Writing into executable and core files is on.
(gdb) file /usr1/src/main
...
(gdb) b main.main
Breakpoint 1 at 0x459c80: file /usr1/src/main.go, line 3.
(gdb) disassemble /mr main.assign
Dump of assembler code for function main.assign:
25      func assign() {
   0x0000000000459d40 <+0>:     49 3b 66 10     cmp    0x10(%r14),%rsp
   0x0000000000459d44 <+4>:     0f 86 8c 00 00 00       jbe    0x459dd6 <main.assign+150>
   0x0000000000459d4a <+10>:    48 83 ec 18     sub    $0x18,%rsp
   0x0000000000459d4e <+14>:    48 89 6c 24 10  mov    %rbp,0x10(%rsp)
   0x0000000000459d53 <+19>:    48 8d 6c 24 10  lea    0x10(%rsp),%rbp
   0x0000000000459dd6 <+150>:   44 9d   rex.R popfq
   0x0000000000459dd8 <+152>:   45 00 ff        add    %r15b,%r15b
   0x0000000000459ddb <+155>:   0f 1f 44 00 00  nopl   0x0(%rax,%rax,1)
   0x0000000000459de0 <+160>:   e9 5b ff ff ff  jmpq   0x459d40 <main.assign>

26              a = [1000]*int{}
   0x0000000000459d58 <+24>:    83 3d e1 b4 09 00 00    cmpl   $0x0,0x9b4e1(%rip)        # 0x4f5240 <runtime.writeBarrier>
   0x0000000000459d5f <+31>:    90      nop
   0x0000000000459d60 <+32>:    75 31   jne    0x459d93 <main.assign+83>
   0x0000000000459d62 <+34>:    48 8d 3d 97 8b 06 00    lea    0x68b97(%rip),%rdi        # 0x4c2900 <main.a>
   0x0000000000459d69 <+41>:    b9 e8 03 00 00  mov    $0x3e8,%ecx
   0x0000000000459d6e <+46>:    31 c0   xor    %eax,%eax
   0x0000000000459d70 <+48>:    f3 48 ab        rep stos %rax,%es:(%rdi)
   0x0000000000459d91 <+81>:    eb 39   jmp    0x459dcc <main.assign+140>
   0x0000000000459d93 <+83>:    48 8d 05 c6 46 00 00    lea    0x46c6(%rip),%rax        # 0x45e460
   0x0000000000459d9a <+90>:    48 8d 1d 5f 8b 06 00    lea    0x68b5f(%rip),%rbx        # 0x4c2900 <main.a>
   0x0000000000459da1 <+97>:    e8 fa 61 fb ff  callq  0x40ffa0 <runtime.typedmemclr>

27              b = [1000]*int{}
   0x0000000000459d73 <+51>:    48 8d 3d c6 aa 06 00    lea    0x6aac6(%rip),%rdi        # 0x4c4840 <main.b>
   0x0000000000459d7a <+58>:    b9 e8 03 00 00  mov    $0x3e8,%ecx
   0x0000000000459d7f <+63>:    f3 48 ab        rep stos %rax,%es:(%rdi)
   0x0000000000459da6 <+102>:   48 8d 05 b3 46 00 00    lea    0x46b3(%rip),%rax        # 0x45e460
   0x0000000000459dad <+109>:   48 8d 1d 8c aa 06 00    lea    0x6aa8c(%rip),%rbx        # 0x4c4840 <main.b>
   0x0000000000459db4 <+116>:   e8 e7 61 fb ff  callq  0x40ffa0 <runtime.typedmemclr>

28              c = [1000]*int{}
   0x0000000000459d82 <+66>:    48 8d 3d f7 c9 06 00    lea    0x6c9f7(%rip),%rdi        # 0x4c6780 <main.c>
   0x0000000000459d89 <+73>:    b9 e8 03 00 00  mov    $0x3e8,%ecx
   0x0000000000459d8e <+78>:    f3 48 ab        rep stos %rax,%es:(%rdi)
   0x0000000000459db9 <+121>:   48 8d 05 a0 46 00 00    lea    0x46a0(%rip),%rax        # 0x45e460
   0x0000000000459dc0 <+128>:   48 8d 1d b9 c9 06 00    lea    0x6c9b9(%rip),%rbx        # 0x4c6780 <main.c>
   0x0000000000459dc7 <+135>:   e8 d4 61 fb ff  callq  0x40ffa0 <runtime.typedmemclr>

29      }
   0x0000000000459dcc <+140>:   48 8b 6c 24 10  mov    0x10(%rsp),%rbp
   0x0000000000459dd1 <+145>:   48 83 c4 18     add    $0x18,%rsp
   0x0000000000459dd5 <+149>:   c3      retq

End of assembler dump.

我想修改的指令是这一条:

0x0000000000459d44 <+4>:     0f 86 8c 00 00 00       jbe    0x459dd6 <main.assign+150>

我想让jbe跳转到自己,也就是应该改成

0x0000000000459d44 <+4>:     0f 86 8c 00 00 00       jbe    0x459d44 <main.assign+150>

应该要怎么做呢? 汇编代码每一条指令的本质就是二进制,我们只需改变指令所在地址的二进制的值,就可以实现汇编指令的修改,以得到不同的程序执行结果。

可以看到jbe的指令编码是这样的:0f 86 8c 00 00 00。前面的 0f 86 是操作码,不用动,后面的 8c 00 00 00 是距离 jbe 指令的下一条指令位置的偏移,是一个4字节的按小端序存储的整数。jbe 这条指令的下一条指令的地址是 0x459d4a,加上 8c 正好是 jbe 要跳转的地址 0x459dd6。

如果要做到jbe自己跳自己,那么操作码后面的偏移量应该是-6,以补码的形式存储。-6的补码的十六进制是 FF FF FF FA(4个字节),所以我们应该把原本 8c 00 00 00 改成 FA FF FF FF(注意是小端序)。

(gdb) set {byte}0x459d49=0xff
(gdb) set {byte}0x459d48=0xff
(gdb) set {byte}0x459d47=0xff
(gdb) set {byte}0x459d46=0xfa

改完后的结果:

(gdb) disassemble /mr main.assign
Dump of assembler code for function main.assign:
25      func assign() {
   0x0000000000459d40 <+0>:     49 3b 66 10     cmp    0x10(%r14),%rsp
   0x0000000000459d44 <+4>:     0f 86 fa ff ff ff       jbe    0x459d44 <main.assign+4>   // jbe自己跳转到自己,死循环
   0x0000000000459d4a <+10>:    48 83 ec 18     sub    $0x18,%rsp
   0x0000000000459d4e <+14>:    48 89 6c 24 10  mov    %rbp,0x10(%rsp)
   0x0000000000459d53 <+19>:    48 8d 6c 24 10  lea    0x10(%rsp),%rbp
   0x0000000000459dd6 <+150>:   44 9d   rex.R popfq
   0x0000000000459dd8 <+152>:   45 00 ff        add    %r15b,%r15b
   0x0000000000459ddb <+155>:   0f 1f 44 00 00  nopl   0x0(%rax,%rax,1)
   0x0000000000459de0 <+160>:   e9 5b ff ff ff  jmpq   0x459d40 <main.assign>