duangsuse::Echo
715 subscribers
4.25K photos
127 videos
583 files
6.46K links
import this:
美而不丑、明而不暗、短而不凡、长而不乱,扁平不宽,读而后码,行之天下,勿托地上天国。
异常勿吞,难过勿过,叹一真理。效率是很重要,盲目最是低效。
简明是可靠的先验,不是可靠的祭品。
知其变,守其恒,为天下式;穷其变,知不穷,得地上势。知变守恒却穷变知新,我认真理,我不认真。

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
duangsuse::Echo
像这种反汇编对编译器学习者就很有用(
教大家个招:

一段简单的 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 :)
找了半天反汇编库不少,但有很多库根本没有能用的前端,这个也一样,得自己写代码来用他们,真的很难受

而且好像反汇编都比较难... 因为的确是有引用的问题存在,我再想想...
Radare2 反汇编出来的东西 size 居然是错的... emmmm 害得我又熬夜了这么久
duangsuse::Echo
Radare2 反汇编出来的东西 size 居然是错的... emmmm 害得我又熬夜了这么久
那个 ecx 偏移量运行时会取一个双字(long int)的(本地变量 [ebp-0xe])数据到寄存器,然而其实只给了它(这个本地变量) 4 字节的空间!结果就是它会把 0x0008 后面的 0x0064 也取到一起,加到 char *s 上面就是个无效偏移量 unmapped memory,直接段错误
duangsuse::Echo
Radare2 反汇编出来的东西 size 居然是错的... emmmm 害得我又熬夜了这么久
根本不是一个双字吧!就是一个字的数据而已
cooltok.zip
24.6 KB
第二次,明天就上学了 emmmm #life #school #GeekAPk #reveng #CoolApk
🤔 r2 明明知道一个 int 只有 word 那么长,为什么还要上 dword address 呢...
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 *
duangsuse::Echo
🤔 r2 明明知道一个 int 只有 word 那么长,为什么还要上 dword address 呢...
我没想到的是 i686 里寄存器看起来也有一个双字么... 🤔
我以为 32 位的意思是寄存器都是 32 位的,可是 edb 里却有 8 个 16 进制数位...
duangsuse::Echo
我没想到的是 i686 里寄存器看起来也有一个双字么... 🤔 我以为 32 位的意思是寄存器都是 32 位的,可是 edb 里却有 8 个 16 进制数位...
用 radare2 的其他反汇编模式看看,发现 64 位模式下只是 ebp 改成了 rbp,这... 🤔

我只是想上动态分析看汇编啊...
这显然是错误的,明明只分配了四个字的空间,却全用的是 dword,这根本不可能可以正常运行,存储位置全是冲突的!反汇编只是为了能让你看得懂机器代码而不可能简单地再重新汇编上吗?
这就是一个正确的栗子,它给栈帧分配的是两个 dword 的空间,而上面那个是没有完成分配的任务,居然只给 64 位的东西分配 32 位的空间,读写都会冲突
duangsuse::Echo
这就是一个正确的栗子,它给栈帧分配的是两个 dword 的空间,而上面那个是没有完成分配的任务,居然只给 64 位的东西分配 32 位的空间,读写都会冲突
执行时是怎么样的呢? 🤔(当然这里可能不严谨,不过也够了,反正大家很多人连 CDEF 系统栈是怎么维护的都不知道,也算是科普一下)

我们的 caller 叫做 main,它执行如下代码以调用我们的子程序 bd

sub esp, (2*4)
<sp> [....] [....] |*****
mov ecx, ; buffer
; ecx = (char *) buffer
mov [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 的调用就完成了(迫真)
lea esp, [esp - framesz] 有什么用呢? 🤔
duangsuse::Echo
lea esp, [esp - framesz] 有什么用呢? 🤔
注意:

1. 是 lea esp, [esp-framesz] 不是 mov esp, [esp-framesz]
1.5. 0x10 是 16 的意思,16 / 4 等于 4
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. 所以现在看得懂汇编了吗?