首页 编程 软件学院 查看内容

汇编调试技巧及简单破解

2019-12-15 12:44 |来自: 互联网 1289 0

摘要: 为研究竞品,学习了下如何破解,同时也可以更好地防御自己的产品。本文适合对汇编调试感兴趣或想入门破解的同学。以下以arm linux(android)为例。elf简介linux系统的可执行文件(也包括obj,.a,.so)采用的格式是elf。在网上找到的一张图,很好地诠释了elf的格式大致内容、布局,以及如何被加载和运行 ...
关键词: 代码 寄存器 gdb 指令 字符串 hello world 盗版 源码


为研究竞品,学习了下如何破解,同时也可以更好地防御自己的产品。

本文适合对汇编调试感兴趣或想入门破解的同学。

以下以arm linux(android)为例。

elf简介

linux系统的可执行文件(也包括obj,.a,.so)采用的格式是elf。

在网上找到的一张图,很好地诠释了elf的格式大致内容、布局,以及如何被加载和运行。

对于我们接下来要作的破解而言,最关键的信息是知道elf主要由头、表、段组成。

一些常用的工具是objdump和readelf。当然gdb更是必不可少的。

要查看各个段的分布:

readelf -S a.out

要(16进制)输出一个段(这里输出.rodata):

readelf -x .rodata a.out

要反汇编代码段(.text),可以:

objdump -d a.out > a.out.dump

汇编指令

由于要破解的库一般都是strip的,看不到源码信息,所以,要很经常和汇编指令打交道。

不同的架构有不同的指令,如x86、arm。但基本不变的是汇编原理,也就是寄存器、PC(程序指针)、SP(栈指针)、常量/栈/内存读写。只有了解了这些基本原理,再看指令和了解架构差异,才能游刃有余。

这里我要破解一个arm的库,所以提前了解了一些arm指令,可以参考arm infocenter.

gdb

直接从objdump解析的汇编代码去推断源码是一件很费脑的事情,要强迫自己像机器一样工作,在大脑中想象各个寄存器、指针的状态。(而且往往汇编代码是开了-O优化过的)

所以借助gdb在运行态对目标文件进行“调试”,可以实时打印寄存器的值,跟踪流程调用顺序,快速理清代码原理。

由于要调试的是汇编指令,所以和常规的基于源码的调试略有不同。

这里介绍几个常用的gdb命令。

显示反汇编代码:

layout asm

汇编代码单步进入:

si

汇编代码单步跳过:

ni

显示寄存器信息:

info registers

打印寄存器值:

p /x $r0

打印内存值(假设r0放着内存地址):

x $r0

我喜欢这样调试:

左边是objdump的代码,右边是gdb环境,gdb执行layout asm后也可以看到代码,然后左右对照,边调试,边在左边作笔记。

实战HelloWorld

HelloWorld的好处是简单,干扰元素少。可以基于HelloWorld验证各种猜想,或理解编译器行为。

比如,这里我们可以用HelloWorld看看如何找到输出"hello world"的代码片段。

源码:

#include int main(int argc, char* argv[]) {
printf("hello world");
return 0;
}

反汇编:

arm-linux-androideabi-objdump -d a.out > a.out.dump
56 00000428
:
57 428: e59f0010 ldr r0, [pc, #16] ; 440
58 42c: e92d4008 push {r3, lr}
59 430: e08f0000 add r0, pc, r0
60 434: ebffffe9 bl 3e0
61 438: e3a00000 mov r0, #0
62 43c: e8bd8008 pop {r3, pc}
63 440: 00001730 .word 0x00001730

从汇编代码中看不到"hello world"字符串,因为,字符串常量被放在了.rodata段:

> arm-linux-androideabi-readelf -x .rodata a.out

Hex dump of section '.rodata':
0x00001b68 68656c6c 6f20776f 726c6400 hello world.

那0x00001b68地址是如何在代码段中引用呢?偏移!

不妨调试下,跟到printf那一行,我们知道函数调用的第一个参数,放在寄存器r0,也就是printf的format参数,即要输出的hello world,验证下:

(gdb) p /x $r0
$2 = 0xb6f1fb68
(gdb) x /s $r0
0xb6f1fb68: "hello world"

再看r0的计算过程,分两步,取pc+16给r0,以及对r0作加pc操作,我们从main开始再调试一次,这次观察整个计算过程:

B+ │0xb6f90428 
ldr r0, [pc, #16] ; 0xb6f90440 <│
│0xb6f9042c push {r3, lr} │
│0xb6f90430 add r0, pc, r0 │
>│0xb6f90434 bl 0xb6f903e0

(gdb) ni
0xb6f90430 in main ()
(gdb) p /x $r0
$2 = 0x1730
(gdb) p $pc
$3 = (void (*)()) 0xb6f90430
(gdb) ni
0xb6f90434 in main ()
(gdb) p $pc
$4 = (void (*)()) 0xb6f90434
(gdb) p /x $r0
$5 = 0xb6f91b68

可以看到ldr r0, [pc, #16]之后,r0的值是$2 = 0x1730,也就是这行63 440: 00001730 .word 0x00001730中的0x00001730。

注意gdb的时候是装载后的地址,比如0xb6f90428对应的是objdump中的428(偏移了0xb6f90000,这个值不是固定值)

接着在0xb6f90434对r0加pc,r0 = 0x1730+0xb6f90430=0x0xb6f91b60。明明gdb打印的是0xb6f91b68,怎么差了8?不对是吗?

需要注意的是arm的pc超前2条指令:https://blog.csdn.net/lee244868149/article/details/49488575...

所以应该是r0 = 0x1730+0xb6f90438=0x0xb6f91b68,而0xb6f91b68就是hello world.

以上为读者演示了如何用汇编调试的技巧去探索一个字符串如何在代码中被引用。当目标程序在被盗版情况下有输出调试信息时,这个调试信息就会成为一个突破口。

类似地,读者可以用"Hello World"去尝试其他想法。

实战简易破解

上面学习了如何定位字符串在代码中如何被使用,下面给一个简单的程序破解。

为模拟破解,这里不给源码。先观察结果:

授权模式下运行:

# ./a.out                                             
hello world

盗版模式下运行:

# ./a.out                                             
thief!!!

字符串thief是一个切入点,打印这个字符串的附近很可能有进行盗版检查的代码。

反汇编:

61 00000454 
:
62 454: e59f0044 ldr r0, [pc, #68] ; 4a0
63 458: e92d4010 push {r4, lr}
64 45c: e24dd068 sub sp, sp, #104 ; 0x68
65 460: e08f0000 add r0, pc, r0
66 464: e1a0100d mov r1, sp
67 468: ebffffe4 bl 400
68 46c: e2504000 subs r4, r0, #0
69 470: 1a000005 bne 48c
70 474: e59f0028 ldr r0, [pc, #40] ; 4a4
71 478: e08f0000 add r0, pc, r0
72 47c: ebffffe2 bl 40c
73 480: e1a00004 mov r0, r4
74 484: e28dd068 add sp, sp, #104 ; 0x68
75 488: e8bd8010 pop {r4, pc}
76 48c: e59f0014 ldr r0, [pc, #20] ; 4a8
77 490: e08f0000 add r0, pc, r0
78 494: ebffffdc bl 40c
79 498: e3e00000 mvn r0, #0
80 49c: eafffff8 b 484
81 4a0: 0000175c .word 0x0000175c
82 4a4: 0000175c .word 0x0000175c

这个程序比较简单,这里有2处puts,可以从.rodata结合代码很容易推断出代码意图,找到防盗版原理。

结合gdb,也容易发现,代码运行路径: 454一直运行到470,分支跳转到48c,加载thief!!!到r0并打印。

那么要破解,可以简单地反转470的判断条件(可以用ghex修改),即使盗版,也跳到正常代码(当然还有更好的破解方案,留给读者发挥了):

470:   1a000005    bne 48c 
改为:
470: 0a000005 bne 48c

到此为止,基本的汇编调试技巧就介绍完了,对于简(hen)易(shui)的防盗版,也可以破解了。

最后一节并没有详细展开,留给读者思考。读者可以参者上一节的方法自行调试,如果有疑问,或需要源码,请留言告诉我~

本文出处: https://www.toutiao.com/a6770474995719078407/
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手
1

鲜花

鸡蛋

刚表态过的朋友 (1 人)

最新评论

返回顶部