duangsuse::Echo
我们来对照一下这些代码和他们的汇编形式,让我们自己也来当一回 GCC 4.9: 🤔 🐔❓为什么要手动『编译』一些源代码呢? #Compiler #CS #PL #backend #reveng 为什么要手动分析一些反汇编代码呢? 人不是生来就是 native 编译器,所以我们要写汇编,要了解各种实体机,X86、ARM、MIPS、AVR,看各种反汇编让自己能成为编译器(虽然依然不支持向量化和某些高等编译优化诸如逃逸分析指针别名还有更高级的寄存器分配算法,哭哭)。 有时候或许做梦能梦到自己变成编译器,正在拿着一张…
算了算了不胡闹了(跑
我还是快点逆向 liba 好了,免得又熬几次夜.... 🤔
我还是快点逆向 liba 好了,免得又熬几次夜.... 🤔
duangsuse::Echo
像这种反汇编对编译器学习者就很有用(
教大家个招:
一段简单的 C 代码反汇编可能就是四五条指令(甚至更多!)所以完全看完,很多时候不现实(除了本身业务代码很少,而且依赖的外部库代码又可以找到源码的情况)
所以除了有调试行号的情况之外,还可以这么做:
+ 我知道一点关于参数的信息,给参数起名字
+ 利用 r2 的自动分析寻找引用,给所有参数的引用传递性地起名字
+ 检查标记所有 call 的参数
+ 注意 r2 的控制流程图分析,注意 jump* 指令
+ 分析调用参数的时候熟悉一些模式,诸如哪些地址常放第几个参数,这个编译器经常怎么初始化参数,是 push 还是偏移量
+ 经常用鼠标划,找引用、重命名
+ 善于忽视:一些可能无关业务逻辑的代码,打个 flag 然后直接无视(比如 GCC 4.x 的动态栈帧大小
+ 善于猜测,比如 printf* 函数的格式化字符串类型标记,比如某些函数原来可能是做什么的,可以少走不少弯路
+ 善于计算,诸如 struct 的 member 偏移量在尤其是 JNI 分析里有很大作用,有些清奇的编译器会用某些寄存器存储其他程序段的基址,知道一个对象的偏移要能根据 static dump 的结果算出另外一个的
+ 善于反查:数据反查逻辑是个好方法,很多时候文本搜索就够了
+ 善于寻找资料和工具:GItHub 是个好地方,很多时候被逆向的程序使用的库代码都能在上面 code search 出来
+ 善于模式识别:诸如
+ 记忆类型:能记住本地变量和参数的类型
+ 看见其他层的东西:逆向工程必须要有基本的编程功底,能从高级代码里看到底层的逻辑、数据结构和更高层的东西,比如类型、组件、服务
不能像老谭的 C 教程一样,不要只知道『函数要先定义才能使用』、数组索引不能是负的,要看到里面的数据类型、数据长度、栈帧、分配、低地址高低址
这是逆向工程的基本,也只有有这种知识才在逆向工程中有进步的空间(因为很多时候,栈帧本地可能分配一些静态大小的数组,偏移量可能是要你自己去算的)
+ Skip 掉一些编译器生成的无关代码,比如 stack protector 的 prolog - epilog 始末码
现在逆向工程根本不赚什么钱,而且由于它的特殊性(比如对一些泛向 CS 知识的依赖),会逆向的基本都是些真正的爱好者(和计算机科学的也一样)
Happy reversing, have a good night :)
一段简单的 C 代码反汇编可能就是四五条指令(甚至更多!)所以完全看完,很多时候不现实(除了本身业务代码很少,而且依赖的外部库代码又可以找到源码的情况)
所以除了有调试行号的情况之外,还可以这么做:
+ 我知道一点关于参数的信息,给参数起名字
+ 利用 r2 的自动分析寻找引用,给所有参数的引用传递性地起名字
+ 检查标记所有 call 的参数
+ 注意 r2 的控制流程图分析,注意 jump* 指令
+ 分析调用参数的时候熟悉一些模式,诸如哪些地址常放第几个参数,这个编译器经常怎么初始化参数,是 push 还是偏移量
+ 经常用鼠标划,找引用、重命名
+ 善于忽视:一些可能无关业务逻辑的代码,打个 flag 然后直接无视(比如 GCC 4.x 的动态栈帧大小
sub esp, * 分配,flexible array size)+ 善于猜测,比如 printf* 函数的格式化字符串类型标记,比如某些函数原来可能是做什么的,可以少走不少弯路
+ 善于计算,诸如 struct 的 member 偏移量在尤其是 JNI 分析里有很大作用,有些清奇的编译器会用某些寄存器存储其他程序段的基址,知道一个对象的偏移要能根据 static dump 的结果算出另外一个的
+ 善于反查:数据反查逻辑是个好方法,很多时候文本搜索就够了
+ 善于寻找资料和工具:GItHub 是个好地方,很多时候被逆向的程序使用的库代码都能在上面 code search 出来
+ 善于模式识别:诸如
ary[i] 这种被翻译成 *(ary + i) 的表达式要能快速模式识别出来+ 记忆类型:能记住本地变量和参数的类型
+ 看见其他层的东西:逆向工程必须要有基本的编程功底,能从高级代码里看到底层的逻辑、数据结构和更高层的东西,比如类型、组件、服务
不能像老谭的 C 教程一样,不要只知道『函数要先定义才能使用』、数组索引不能是负的,要看到里面的数据类型、数据长度、栈帧、分配、低地址高低址
这是逆向工程的基本,也只有有这种知识才在逆向工程中有进步的空间(因为很多时候,栈帧本地可能分配一些静态大小的数组,偏移量可能是要你自己去算的)
+ Skip 掉一些编译器生成的无关代码,比如 stack protector 的 prolog - epilog 始末码
现在逆向工程根本不赚什么钱,而且由于它的特殊性(比如对一些泛向 CS 知识的依赖),会逆向的基本都是些真正的爱好者(和计算机科学的也一样)
Happy reversing, have a good night :)
#recommended #reveng #tools https://github.com/0xd4d/iced
果然还是 de4dot 的东西好啊... 现在不缺反汇编器,就却不需要我写代码的能反汇编而且能重新汇编的反汇编器
果然还是 de4dot 的东西好啊... 现在不缺反汇编器,就却不需要我写代码的能反汇编而且能重新汇编的反汇编器
GitHub
GitHub - icedland/iced: Blazing fast and correct x86/x64 disassembler, assembler, decoder, encoder for .NET, Rust, Python, JavaScript
Blazing fast and correct x86/x64 disassembler, assembler, decoder, encoder for .NET, Rust, Python, JavaScript - GitHub - icedland/iced: Blazing fast and correct x86/x64 disassembler, assembler, dec...
duangsuse::Echo
#recommended #reveng #tools https://github.com/0xd4d/iced 果然还是 de4dot 的东西好啊... 现在不缺反汇编器,就却不需要我写代码的能反汇编而且能重新汇编的反汇编器
This media is not supported in your browser
VIEW IN TELEGRAM
找了半天反汇编库不少,但有很多库根本没有能用的前端,这个也一样,得自己写代码来用他们,真的很难受
而且好像反汇编都比较难... 因为的确是有引用的问题存在,我再想想...
而且好像反汇编都比较难... 因为的确是有引用的问题存在,我再想想...
duangsuse::Echo
Radare2 反汇编出来的东西 size 居然是错的... emmmm 害得我又熬夜了这么久
那个 ecx 偏移量运行时会取一个双字(long int)的(本地变量 [ebp-0xe])数据到寄存器,然而其实只给了它(这个本地变量) 4 字节的空间!结果就是它会把 0x0008 后面的 0x0064 也取到一起,加到
char *s 上面就是个无效偏移量 unmapped memory,直接段错误
duangsuse::Echo
Radare2 反汇编出来的东西 size 居然是错的... emmmm 害得我又熬夜了这么久
根本不是一个双字吧!就是一个字的数据而已
duangsuse::Echo
cooltok.zip
byte = 8
halfword = 16
word = 32
size = 32
dword = 64
byte char
size char *
dword double
word float
word int
halfword int16_t
word int32_t
dword int64_t
byte int8_t
dowrd long
dword long long
halfword short
size size_t
halfword uint16_t
word uint32_t
dword uint64_t
byte uint8_t
byte unsigned char
word unsigned int
halfword unsigned short
size void *
halfword = 16
word = 32
size = 32
dword = 64
byte char
size char *
dword double
word float
word int
halfword int16_t
word int32_t
dword int64_t
byte int8_t
dowrd long
dword long long
halfword short
size size_t
halfword uint16_t
word uint32_t
dword uint64_t
byte uint8_t
byte unsigned char
word unsigned int
halfword unsigned short
size void *
duangsuse::Echo
🤔 r2 明明知道一个 int 只有 word 那么长,为什么还要上 dword address 呢...
我没想到的是 i686 里寄存器看起来也有一个双字么... 🤔
我以为 32 位的意思是寄存器都是 32 位的,可是 edb 里却有 8 个 16 进制数位...
我以为 32 位的意思是寄存器都是 32 位的,可是 edb 里却有 8 个 16 进制数位...
duangsuse::Echo
我没想到的是 i686 里寄存器看起来也有一个双字么... 🤔 我以为 32 位的意思是寄存器都是 32 位的,可是 edb 里却有 8 个 16 进制数位...
用 radare2 的其他反汇编模式看看,发现 64 位模式下只是 ebp 改成了 rbp,这... 🤔
我只是想上动态分析看汇编啊...
我只是想上动态分析看汇编啊...
duangsuse::Echo
这就是一个正确的栗子,它给栈帧分配的是两个 dword 的空间,而上面那个是没有完成分配的任务,居然只给 64 位的东西分配 32 位的空间,读写都会冲突
执行时是怎么样的呢? 🤔(当然这里可能不严谨,不过也够了,反正大家很多人连 CDEF 系统栈是怎么维护的都不知道,也算是科普一下)
我们的 caller 叫做 main,它执行如下代码以调用我们的子程序 bd:
然后机器开始解释执行我们的程序逻辑:
==
; eip = *__x86_get_pc_thunk_bx
(其实就是拿到位于 .text 段的 ip 指针,也就是
(我们要重新用汇编重写的时候必须也重写这种 ebx 偏移量,把对它们的使用替换成 .rodata 段实际的偏移地址,NASM 可以帮我们做这件事,直接
; eip = *bd
; eax = *bd+5i
== 然后我们又回到了自己的程序
; GCC(GNU Compiler Collection) 4.9 是相当老的编译器了,不要忘记现在 GCC 都出 8 了
eax = my_arg2
eax = my_arg1
🤔所以,我们为什么要多分配这么多无用的东西?浪费了一个双字的空间(虽然 mov subarg2 的时候可能溢出到别的存储单元)
🤔
一瞬返回
最后 main 收拾调用现场
== main+???
对 bd 的调用就完成了(迫真)
我们的 caller 叫做 main,它执行如下代码以调用我们的子程序 bd:
sub esp, (2*4)<sp> [....] [....] |*****
mov ecx, ; buffer
; ecx = (char *) buffermov [esp+4], "SGVsbG8K"
<sp> [....] [*"SGV....] |*****mov [esp], ecx
<sp> [*buffer] [*"SGV....] |*****call bd
; eip = *bd然后机器开始解释执行我们的程序逻辑:
push ebp<sp> [*bp@main] [return@main] [*buffer] [*"SGV....] |*****
mov ebp, esp
<sp><bp> [*bp@main] [return@main] [*buffer] [*"SGV....] |*****push ebx
<sp> [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....] |*****lea esp, [esp-(4*2 * 2)]
<sp> [........] [........] [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....] |*****==
call __x86_get_pc_thunk_bx
<sp> [return@bd] [........] [........] [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....]; eip = *__x86_get_pc_thunk_bx
mov ebx, dword [esp]
; ebx = (long) *<sp>(其实就是拿到位于 .text 段的 ip 指针,也就是
.text+bd+???,GCC 4.9 拿这个 bx (就是当前函数位于的 call __x86_get_pc_thunk_bx 时的 eip)指针去算 .rodata 段的静态只读数据地址)(我们要重新用汇编重写的时候必须也重写这种 ebx 偏移量,把对它们的使用替换成 .rodata 段实际的偏移地址,NASM 可以帮我们做这件事,直接
section .rodata 然后定义 byte/dword 静态数据指针就可以了)ret
<sp> [........] [........] [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....]; eip = *bd
; eax = *bd+5i
== 然后我们又回到了自己的程序
add ebx, 0x23fb
; 现在 ebx 指向了 .rodata (ds)段的某个地址,不过,本函数是不用; GCC(GNU Compiler Collection) 4.9 是相当老的编译器了,不要忘记现在 GCC 都出 8 了
mov eax, bd_arg2 (ebp+0xc)eax = my_arg2
mov bd_suba2(esp+0x4), eax
mov eax, bd_arg1 (ebp+0x8)eax = my_arg1
mov bd_suba1(esp), eax
<sp> [my_arg1|my_arg2] [........] [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....]🤔所以,我们为什么要多分配这么多无用的东西?浪费了一个双字的空间(虽然 mov subarg2 的时候可能溢出到别的存储单元)
call BD
; BD(my_arg1, my_arg2)🤔
lea esp, [esp+framesz]
[my_arg1|my_arg2] [........] <sp> [bx@main] <bp> [*bp@main] [return@main] [*buffer] [*"SGV....]一瞬返回
pop ebx
<sp> <bp> [*bp@main] [return@main] [*buffer] [*"SGV....]pop ebp<sp> [return@main] [*buffer] [*"SGV....]
ret一瞬重置代码指针返回 main+???
最后 main 收拾调用现场
== main+???
add esp, (2*4)
<sp> |*****对 bd 的调用就完成了(迫真)
duangsuse::Echo
lea esp, [esp - framesz] 有什么用呢? 🤔
注意:
1. 是
1.6. 我们要分配 4 个本地变量(4 * 4),当然也和我们自己调用子程序的部分有重叠
2. framesz 是 frame size 的意思,有时候你们看到的 sz 表示的 handle 们只是『碰巧』使用 size_t 机器字大小存储而已,所以叫 size,和这个无关
3. 我也不知道为什么 X86 的寄存器就会有 64 位了,因为这个是反汇编出来的程序又是 -m32 multlib 编译肯定有点奇怪啊
4. 我的机器很高级,支持 Debug Registers、FPU 还有 MMX、SSE、AVX 这些 SIMD 处理特性,可以快快的解码视频(迫真
6. 上面那些高等 Vectorize 处理特性,我们要分析的程序都不会用到(悲)
7. 这条指令执行前,esp 指向 0xb4f8,执行后,esp 指向 0xb4e4,下移了 20 个字节(合 5 个字)
8. 这啥 🐔 玩意,我也不知道它要干什么
9. 好吧,告诉你们,就是下移了 4 个字(4 * 4)而已,所谓 20 字节是我瞎 🐔 猜的
10. 所以现在看得懂汇编了吗?
1. 是
lea esp, [esp-framesz] 不是 mov esp, [esp-framesz]
1.5. 0x10 是 16 的意思,16 / 4 等于 41.6. 我们要分配 4 个本地变量(4 * 4),当然也和我们自己调用子程序的部分有重叠
2. framesz 是 frame size 的意思,有时候你们看到的 sz 表示的 handle 们只是『碰巧』使用 size_t 机器字大小存储而已,所以叫 size,和这个无关
3. 我也不知道为什么 X86 的寄存器就会有 64 位了,因为这个是反汇编出来的程序又是 -m32 multlib 编译肯定有点奇怪啊
4. 我的机器很高级,支持 Debug Registers、FPU 还有 MMX、SSE、AVX 这些 SIMD 处理特性,可以快快的解码视频(迫真
6. 上面那些高等 Vectorize 处理特性,我们要分析的程序都不会用到(悲)
7. 这条指令执行前,esp 指向 0xb4f8,执行后,esp 指向 0xb4e4,下移了 20 个字节(合 5 个字)
8. 这啥 🐔 玩意,我也不知道它要干什么
9. 好吧,告诉你们,就是下移了 4 个字(4 * 4)而已,所谓 20 字节是我瞎 🐔 猜的
10. 所以现在看得懂汇编了吗?
duangsuse::Echo
执行时是怎么样的呢? 🤔(当然这里可能不严谨,不过也够了,反正大家很多人连 CDEF 系统栈是怎么维护的都不知道,也算是科普一下) 我们的 caller 叫做 main,它执行如下代码以调用我们的子程序 bd: sub esp, (2*4) <sp> [....] [....] |***** mov ecx, ; buffer ; ecx = (char *) buffer mov [esp+4], "SGVsbG8K" <sp> [....] [*"SGV....] |***** mov [esp]…
🤔 实际上还是得重写 liba.so 真正自己的逻辑,不能直接反汇编然后啥都 OK 了...