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

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
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],这种看起来根本对不齐一个字的操作到底有什么意义...
#sysadmin #Assembly #reveng #failure #life

算是体验了一下 NASM 和 x86_64 交叉 x86,此外,没有别的。
弄出等价的后可以看看是如何优化的
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

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 到底做了什么
当然位运算就更不可能看到本质了... 就不能逆向而理解呢

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
如果说开始 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

section .rodata
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

nasm -felf 1p1helo.asm; ld -m elf_i386 1p1helo.o -lc -o helloworld -I /usr/lib/ld-2*;./helloworld

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
x86_nasm_helloworld.tar
20 KB
#code #backend #dev #asm 整理了一下构建工具什么的
duangsuse::Echo
x86_nasm_helloworld.tar
比较友好,虽然没有用到流程控制分支和循环什么的

也没有 bss 静态未初始化变量

不过倒是有函数调用,非常简洁,看不懂 GNU AS 应该也能看懂
#reveng duangsuse 终于重拾自信,好耶!是 x86 intel syntax 汇编!
  mov ebx, 100
mov edi, 0

loop:

push edi
call BEL
call printN

inc edi
cmp edi, ebx
jnz loop

call printLn

xor eax, eax
leave
ret
#code
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:                                    # @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
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

使用 edb 分析确定每条指令的用途...

选择逆向 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 的会多一点