duangsuse::Echo
...因为的确已经熬了一夜了,而且这些的确是首先以学习为主... (主要还是想让程序能运行起来... 成天和 linker、relocatable、shared object、静态链接动态链接杠... 我真的不想熬夜啊... 我决定再重新逆向 BEL 函数,成功了就睡觉...唉
function BEL() {
push ebp; ebp = esp
eax = dword [ebp + 0x8]
ecx = [eax + 2]
edx = 0x55555556
eax = ecx
eax = edx = eax * edx ; imul edx
eax = ecx
eax >>= 0x1f ; sar eax, 0x1f
edx -= eax
eax = edx
eax <<<= 2 ; shl eax, 2
eax += 1 ; add eax, 1
}foo.tar
10 KB
非常失败,我根本不知道为什么要先
mov eax, [ebp + 8] 再 mov ecx, [eax + 2],这种看起来根本对不齐一个字的操作到底有什么意义...
duangsuse::Echo
曾经书本上复杂、晦涩难懂的知识对我来说已经不是黑盒。我曾经购买的十多本书里再也没有一行完全看不懂的内容。 原先根本看不懂的什么位长度、什么半字双字缓冲器已经成为日常。 原先一个原生 .so(SharedObject 共享对象)就是“无法理解”的黑盒,现在 x86 机器码根本不是问题、远程 GDB 调试和高等 GDB 功能也不是问题 原先连 Java 的 OOP 类型系统自动子类自动转型基类都无法理解,现在理解整个 Java 类型系统已经不是问题 第一个黑历史 Android 应用 MinBase64 到现在,我却已经记住了…
不,非常黑箱,情况没有好半点,还是很黑箱。凭什么编译器知道我不知道。
duangsuse::Echo
function BEL() { push ebp; ebp = esp eax = dword [ebp + 0x8] ecx = [eax + 2] edx = 0x55555556 eax = ecx eax = edx = eax * edx ; imul edx eax = ecx eax >>= 0x1f ; sar eax, 0x1f edx -= eax eax = edx eax <<<= 2 ; shl eax, 2 eax…
int BEL(int n) { return (4 * (n - 1) / 3 | 3) + 2; }
使用逆波兰法表示
bel(n) = ((n-1) * 4 / 3) | 3 + 3
ldarg.0
ldint 1
dec ; n - 1
ldint 4
mul ; (n - 1) * 4
ldint 3
div ; (n - 1) * 4 / 3
ldint 3
or.bitwise ; |3
ldint 3
add ; + 3
使用逆波兰法表示
bel(n) = ((n-1) * 4 / 3) | 3 + 3
ldarg.0
ldint 1
dec ; n - 1
ldint 4
mul ; (n - 1) * 4
ldint 3
div ; (n - 1) * 4 / 3
ldint 3
or.bitwise ; |3
ldint 3
add ; + 3
function BEL (edi: int n) {
push rbp; rbp = rsp
dword [local_i32] = edi
eax = dword [local_4h]
eax -= 1 ; (n - 1)
ecx = [rax*4] (n - 1) * 4
; / 3 + 1
edx = 0x55555556
eax = ecx
eax = eax * edx
eax = ecx
eax >>= 0x1f
edx -= eax
eax = edx
; | 3
eax |= 3
; + 2
eax += 2
return 2
}
duangsuse::Echo
int BEL(int n) { return (4 * (n - 1) / 3 | 3) + 2; } 使用逆波兰法表示 bel(n) = ((n-1) * 4 / 3) | 3 + 3 ldarg.0 ldint 1 dec ; n - 1 ldint 4 mul ; (n - 1) * 4 ldint 3 div ; (n - 1) * 4 / 3 ldint 3 or.bitwise ; |3 ldint 3 add ; + 3 function BEL (edi: int n) { push…
#Ruby 中也可以模拟
因为这次实在是太失败了,但我对这种极端干扰生活的情况很绝望,,, 因为足足一晚上没合眼,而且早上也没吃饭
希望以后能做到吧,虽然都还不熟悉呢... 太菜了啊... 现在居然连最基本的都弄不懂... 看不到运行时 esp、ebp 到底做了什么
当然位运算就更不可能看到本质了... 就不能逆向而理解呢
因为这次实在是太失败了,但我对这种极端干扰生活的情况很绝望,,, 因为足足一晚上没合眼,而且早上也没吃饭
希望以后能做到吧,虽然都还不熟悉呢... 太菜了啊... 现在居然连最基本的都弄不懂... 看不到运行时 esp、ebp 到底做了什么
当然位运算就更不可能看到本质了... 就不能逆向而理解呢
class Stack然后看主程序
def initialize(stack = [])
@fifo = stack
end
def ld(o); @fifo << o; end
def pop; @fifo.pop; end
def peek; @fifo.last; end
def size; @fifo.size; end
def to_s; @fifo.to_s; end
def eql?(o); @fifo.eql?(o); end
def <<(o); ld o; end
end
def Stack.mk_binary_op(name, &operator)
define_method(name) do
op2 = pop; op1 = pop
ld operator.call(op1, op2)
end
end
class Stack
mk_binary_op :sub, &:-
mk_binary_op :add, &:+
mk_binary_op :mul, &:*
mk_binary_op :div, &:/
mk_binary_op :bitwise_or, &:|
end
def stack_bel(n)
s = Stack.new
s << 4 << n << 1
s.sub
s.mul
s << 3
s.div
s << 3
s.bitwise_or
s << 2
s.add
return s.pop
end
1000.times { |i| print stack_bel(i); print ' ' }Forwarded from dnaugsuz
如果说开始
x86 里怎么会莫名其妙栈上分配一个字又一个半字的数据呢...
... 而
我拿 NASM 重写了代码然后上 edb 逐 step 调试,结果好像证明它的确需要一个指针(
RetDec 反编译的全是错的,它根本没有注意到这个函数不是无参的,还不如 radare2 提示的类型正确... 看来还是上动态分析可选
mov eax, [ebp - 8] 这种可以理解为分配 retAddress(当然其实不是,我误会了),那后来... 我中间有段时间在想是不是在传指针解引用,但传入的参数分明不是指针...mov ecx, [eax - 2] 这种又是什么鬼啊,这不是 x86 么,32 位啊,根本不应该出现这种一个栈帧占 10 字节的情况么... 它不是 2 的幂啊...x86 里怎么会莫名其妙栈上分配一个字又一个半字的数据呢...
... 而
[eax - 4] 这种我还可以理解,这种毫无逻辑的分配是什么意思啊...我拿 NASM 重写了代码然后上 edb 逐 step 调试,结果好像证明它的确需要一个指针(
ebp - 8 是第一个数值参数,然后它解指针这个参数),可是我当时测试的时候给的真的不是指针啊... 是直接传值调用的啊...RetDec 反编译的全是错的,它根本没有注意到这个函数不是无参的,还不如 radare2 提示的类型正确... 看来还是上动态分析可选
#reveng 所以现在 duangsuse 睡了一觉后重整旗鼓,端正自己的态度,一切为了学习底层知识而不是专门彻底逆向工程还原出一个等价的 liba.so 来
现在 duangsuse 思考了一下,又重新理解了 x86 上 C 语言的各种小 routines 了,知道 stack base 和 stack pointer 怎么管理了,想清楚静态链接、动态链接、可重定位文件、共享库有什么区别可以怎么 hack 了,又会写汇编了,现在写一个 1 + 1 的 NASM x86 32b helloworld 程序看看:
#Learn NASM #backend #code
duangsuse 准备用刚编译的 32 位 Lua 和 GNU Binutils 重新调整测试一下新的 liba.so,解决 ABI 不同 ld 解释器动态链接受到阻碍的问题,并且搞懂 BEL 里的莫名其妙的指令序列到底做什么的,可能会上动态分析
首先准备了解下为什么每次 x86 Alpine 容器里动态链接好了都会 Segfault,对比一下自己实现的 BEL 和酷安原本的有什么区别。
了解 BEL 如何执行
然后就是随便再选一个符号还原代码
现在 duangsuse 思考了一下,又重新理解了 x86 上 C 语言的各种小 routines 了,知道 stack base 和 stack pointer 怎么管理了,想清楚静态链接、动态链接、可重定位文件、共享库有什么区别可以怎么 hack 了,又会写汇编了,现在写一个 1 + 1 的 NASM x86 32b helloworld 程序看看:
#Learn NASM #backend #code
section .rodatanasm -felf 1p1helo.asm; ld -m elf_i386 1p1helo.o -lc -o helloworld -I /usr/lib/ld-2*;./helloworld
fmt db `%s %i\n`, 0x0
section .data
msg db "Hello, world!", 0x0
section .text
extern printf
global _start
global main
global print
_start:
mov ebp, esp
; arguments to main:
; _start(int argc, char **argv)
call main
; exit(eax)
mov ebx, eax
mov eax, 1
int 0x80
print:
push ebp
mov ebp, esp
mov ecx, 100
add ecx, 1
push ecx
push msg
push fmt
call printf
; clean-up for `printf' call
; increase stack pointer by argument size
; to simply ignore them
; sizeof(int) + sizeof(char *) * 2
add esp, 12
leave
ret
main:
push ebp
mov ebp, esp
call print
xor eax, eax
leave
ret
duangsuse 准备用刚编译的 32 位 Lua 和 GNU Binutils 重新调整测试一下新的 liba.so,解决 ABI 不同 ld 解释器动态链接受到阻碍的问题,并且搞懂 BEL 里的莫名其妙的指令序列到底做什么的,可能会上动态分析
首先准备了解下为什么每次 x86 Alpine 容器里动态链接好了都会 Segfault,对比一下自己实现的 BEL 和酷安原本的有什么区别。
了解 BEL 如何执行
然后就是随便再选一个符号还原代码
Forwarded from dnaugsuz
我注意了一下上一个 ebp 和 esp 的变化,的确是以 2 为单位的... 从 ...250 到 ..518 了,16 位一个内存地址,貌似上一次 _start 里 mov ebp, esp 后 esp 又 push 了 call main 调用的 retAddr(32b) 上去,然后又 push 了个 ebp(32b),大概是以 32 位为单位的
This media is not supported in your browser
VIEW IN TELEGRAM
duangsuse::Echo
x86_nasm_helloworld.tar
比较友好,虽然没有用到流程控制分支和循环什么的
也没有 bss 静态未初始化变量
不过倒是有函数调用,非常简洁,看不懂 GNU AS 应该也能看懂
也没有 bss 静态未初始化变量
不过倒是有函数调用,非常简洁,看不懂 GNU AS 应该也能看懂
#reveng duangsuse 终于重拾自信,好耶!是 x86 intel syntax 汇编!
mov ebx, 100#code
mov edi, 0
loop:
push edi
call BEL
call printN
inc edi
cmp edi, ebx
jnz loop
call printLn
xor eax, eax
leave
ret
bel.tar
50 KB
真实实现了
liba.so!BEL 而由我用 NASM x86 汇编器重写的版本(其实就是简单的翻译),马上利用 Compiler Expoler 进行优化计算的分析
duangsuse::Echo
#reveng duangsuse 终于重拾自信,好耶!是 x86 intel syntax 汇编!
This media is not supported in your browser
VIEW IN TELEGRAM
接下来可以分析 BEL 具体是如何优化的(因为已经实现了 BEL,并且代码别无二致)
我们的 BEL 公式 LLVM 优化后是这样的:
选择逆向 BEL 函数符号仅仅是因为它的指令条数很少,只有 35 条,我还能接受
别的控制分支都太复杂
这就可见逆向工程很费力了,一直来都是这样,优化做得太好了,机器也不聪明不能代替人工
人工又要看大量的汇编代码,并且最好了解编译原理写过优化器能擅长流程计算归并组合什么的... 难啊
想看大体,细节漏掉了算法就是错的,白逆向一场
想看局部,局部又太多哪里看得完
不想太费力气归并汇编代码,有些平台又不能用
想写出完全等价的算法,工程量又太大一个人做不了
想静态分析,不是很厉害的话又不容易看出是什么,容易迷惑
想动态分析,不是大佬的话又容易被看什么而迷惑
所以啊... 那些写 Java 会用 jadx 一类工具以至于会看 IL 的,写但是只能写 C/C++ 但不知道运行时发生了什么,只能看见一个个星号定义的指针的程序员们...
真是幸福啊。不需要接触这些有点扭曲的东西,面向愉悦编程,真好。
Rust 的开发者们不能说是不底层系统,但 Rust 编译器团队也还是认为他们是『开心的编程生涯』...
那连二进制是什么都不知道的有些前端们... 算什么呢???
当然,因为以前某件和逆向工程相关的事情再澄明一下,免得你们又说我是老鼠什么的,然后在那边大笑着说我们菜,我们在 C++ 编译生成的 x86 机器码反汇编的结果面前如猴子一般抓耳挠腮动不得你们的代码也无法理解你们一分钱的算法逻辑,自己用一天时间写的 C++ 代码经那群编译器工程师的作品(不知道你们在不在乎他们呢...)后再写出等价的来就要 10 天什么的 #Statement
我是主动给自己选择了地狱难度,想还原整个等价的 liba.so 令牌生成算法,这是十分困难的,我之前举烂了的 RENouveau 例子都不是这种做法,它虽然工程量很大但不致于真的完全还原所有算法,只是数据要的多而已
但是呢,其实如果我只是想快速 hack,玩法还是很多的,比如删除你们的包名签名校验逻辑和一些于密钥生成无关的逻辑、调整编辑共享对象文件的其他属性来移植到 Android 外的平台,甚至可以拿 WebAssembly、JavaScript 重写,或者利用 v86.js 直接在浏览器上执行都可以,只要目的小一些实现起来就是分分钟的事情
不管怎么样我觉得我不得不说... 写 C/C++ 系的原作者和无恶意的逆向工程者本来不应该互相讽刺的,都是同道中人,何苦呢?
当然,写 JavaScript 然后上混淆压缩器之类的就又作别论了... JavaScript 的还是小白多一些。这是事实。
不过这次主要就是玩玩 + 细化之前的知识,可能 trivial 的会多一点
我们的 BEL 公式 LLVM 优化后是这样的:
BEL: # @BEL之前:
lea eax, [4*rdi - 4]
cdqe
imul rax, rax, 1431655766
mov rcx, rax
shr rcx, 63
shr rax, 32
add eax, ecx
or eax, 3
add eax, 2
ret
BEL: # @BEL使用 edb 分析确定每条指令的用途...
push rbp
mov rbp, rsp
mov dword ptr [rbp - 4], edi
mov edi, dword ptr [rbp - 4]
sub edi, 1
shl edi, 2
mov eax, edi
cdq
mov edi, 3
idiv edi
or eax, 3
add eax, 2
pop rbp
ret
选择逆向 BEL 函数符号仅仅是因为它的指令条数很少,只有 35 条,我还能接受
别的控制分支都太复杂
这就可见逆向工程很费力了,一直来都是这样,优化做得太好了,机器也不聪明不能代替人工
人工又要看大量的汇编代码,并且最好了解编译原理写过优化器能擅长流程计算归并组合什么的... 难啊
想看大体,细节漏掉了算法就是错的,白逆向一场
想看局部,局部又太多哪里看得完
不想太费力气归并汇编代码,有些平台又不能用
想写出完全等价的算法,工程量又太大一个人做不了
想静态分析,不是很厉害的话又不容易看出是什么,容易迷惑
想动态分析,不是大佬的话又容易被看什么而迷惑
所以啊... 那些写 Java 会用 jadx 一类工具以至于会看 IL 的,写但是只能写 C/C++ 但不知道运行时发生了什么,只能看见一个个星号定义的指针的程序员们...
真是幸福啊。不需要接触这些有点扭曲的东西,面向愉悦编程,真好。
Rust 的开发者们不能说是不底层系统,但 Rust 编译器团队也还是认为他们是『开心的编程生涯』...
那连二进制是什么都不知道的有些前端们... 算什么呢???
当然,因为以前某件和逆向工程相关的事情再澄明一下,免得你们又说我是老鼠什么的,然后在那边大笑着说我们菜,我们在 C++ 编译生成的 x86 机器码反汇编的结果面前如猴子一般抓耳挠腮动不得你们的代码也无法理解你们一分钱的算法逻辑,自己用一天时间写的 C++ 代码经那群编译器工程师的作品(不知道你们在不在乎他们呢...)后再写出等价的来就要 10 天什么的 #Statement
我是主动给自己选择了地狱难度,想还原整个等价的 liba.so 令牌生成算法,这是十分困难的,我之前举烂了的 RENouveau 例子都不是这种做法,它虽然工程量很大但不致于真的完全还原所有算法,只是数据要的多而已
但是呢,其实如果我只是想快速 hack,玩法还是很多的,比如删除你们的包名签名校验逻辑和一些于密钥生成无关的逻辑、调整编辑共享对象文件的其他属性来移植到 Android 外的平台,甚至可以拿 WebAssembly、JavaScript 重写,或者利用 v86.js 直接在浏览器上执行都可以,只要目的小一些实现起来就是分分钟的事情
不管怎么样我觉得我不得不说... 写 C/C++ 系的原作者和无恶意的逆向工程者本来不应该互相讽刺的,都是同道中人,何苦呢?
当然,写 JavaScript 然后上混淆压缩器之类的就又作别论了... JavaScript 的还是小白多一些。这是事实。
不过这次主要就是玩玩 + 细化之前的知识,可能 trivial 的会多一点