解密 x86 汇编函数
Deciphering x86 assembly function
我目前正在处理二进制炸弹任务的第 2 阶段。我无法准确解读某个函数在被调用时的作用。我已经坚持了好几天了。
函数是:
0000000000400f2a <func2a>:
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
400f2e: b9 cd cc cc cc mov [=10=]xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr [=10=]x3,%edx
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
400f3f: 29 c7 sub %eax,%edi
400f41: 83 04 be 01 addl [=10=]x1,(%rsi,%rdi,4)
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
400f4b: f3 c3 repz retq
它在更大的函数中被调用 "phase_2":
0000000000400f4d <phase_2>:
400f4d: 53 push %rbx
400f4e: 48 83 ec 60 sub [=11=]x60,%rsp
400f52: 48 c7 44 24 30 00 00 movq [=11=]x0,0x30(%rsp)
400f59: 00 00
400f5b: 48 c7 44 24 38 00 00 movq [=11=]x0,0x38(%rsp)
400f62: 00 00
400f64: 48 c7 44 24 40 00 00 movq [=11=]x0,0x40(%rsp)
400f6b: 00 00
400f6d: 48 c7 44 24 48 00 00 movq [=11=]x0,0x48(%rsp)
400f74: 00 00
400f76: 48 c7 44 24 50 00 00 movq [=11=]x0,0x50(%rsp)
400f7d: 00 00
400f7f: 48 c7 04 24 00 00 00 movq [=11=]x0,(%rsp)
400f86: 00
400f87: 48 c7 44 24 08 00 00 movq [=11=]x0,0x8(%rsp)
400f8e: 00 00
400f90: 48 c7 44 24 10 00 00 movq [=11=]x0,0x10(%rsp)
400f97: 00 00
400f99: 48 c7 44 24 18 00 00 movq [=11=]x0,0x18(%rsp)
400fa0: 00 00
400fa2: 48 c7 44 24 20 00 00 movq [=11=]x0,0x20(%rsp)
400fa9: 00 00
400fab: 48 8d 4c 24 58 lea 0x58(%rsp),%rcx
400fb0: 48 8d 54 24 5c lea 0x5c(%rsp),%rdx
400fb5: be 9e 26 40 00 mov [=11=]x40269e,%esi
400fba: b8 00 00 00 00 mov [=11=]x0,%eax
400fbf: e8 6c fc ff ff callq 400c30 <__isoc99_sscanf@plt>
400fc4: 83 f8 02 cmp [=11=]x2,%eax
400fc7: 74 05 je 400fce <phase_2+0x81>
400fc9: e8 c1 06 00 00 callq 40168f <explode_bomb>
400fce: 83 7c 24 5c 64 cmpl [=11=]x64,0x5c(%rsp)
400fd3: 76 07 jbe 400fdc <phase_2+0x8f>
400fd5: 83 7c 24 58 64 cmpl [=11=]x64,0x58(%rsp)
400fda: 77 05 ja 400fe1 <phase_2+0x94>
400fdc: e8 ae 06 00 00 callq 40168f <explode_bomb>
400fe1: 48 8d 74 24 30 lea 0x30(%rsp),%rsi
400fe6: 8b 7c 24 5c mov 0x5c(%rsp),%edi
400fea: e8 3b ff ff ff callq 400f2a <func2a>
400fef: 48 89 e6 mov %rsp,%rsi
400ff2: 8b 7c 24 58 mov 0x58(%rsp),%edi
400ff6: e8 2f ff ff ff callq 400f2a <func2a>
400ffb: bb 00 00 00 00 mov [=11=]x0,%ebx
401000: 8b 04 1c mov (%rsp,%rbx,1),%eax
401003: 39 44 1c 30 cmp %eax,0x30(%rsp,%rbx,1)
401007: 74 05 je 40100e <phase_2+0xc1>
401009: e8 81 06 00 00 callq 40168f <explode_bomb>
40100e: 48 83 c3 04 add [=11=]x4,%rbx
401012: 48 83 fb 28 cmp [=11=]x28,%rbx
401016: 75 e8 jne 401000 <phase_2+0xb3>
401018: 48 83 c4 60 add [=11=]x60,%rsp
40101c: 5b pop %rbx
40101d: c3 retq
我完全理解phase_2在做什么,我只是不明白func2a在做什么以及它如何影响0x30(%rsp)处的值等等。因此我总是在0x401003处找到比较语句,炸弹最终在那里爆炸。
我的问题是我不明白输入(相位解)如何通过 func2a 影响 0x30(%rsp) 处的值。
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
这只是当 edi
为零时的提前退出(je
与 jz
相同)。
400f2e: b9 cd cc cc cc mov [=11=]xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr [=11=]x3,%edx
这是一个经典的优化技巧;它是除以乘以倒数的整数算术等价物(参见 here for details);实际上,这和说 edx = edi / 10
是一样的;
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
这里它利用 lea
进行算术运算(在 Intel 语法中更清晰,它是 lea eax,[rdx+rdx*4]
=> eax = edx*5
),然后将结果与自身相加。这一切都归结为 eax = edx*10
.
400f3f: 29 c7 sub %eax,%edi
然后,将其减回 edi
。
所以,总而言之,这是一种计算 edi
的最后一位小数的复杂(但快速)的方法;到目前为止我们所拥有的是这样的:
void func2a(unsigned edi) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
// ...
}
(label1:
是因为400f33
是后面的跳转目标)
进行中:
400f41: 83 04 be 01 addl [=15=]x1,(%rsi,%rdi,4)
同样,这对我来说在英特尔语法中更清晰 - add dword [rsi+rdi*4],byte +0x1
。它是一个32位int
数组的规则递增(rdi
乘以4);所以,我们可以想象 rsi
指向一个整数数组,索引为 edi
.
刚刚计算的最后一位
void func2a(unsigned edi, int rsi[]) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
rsi[edi]++;
}
然后:
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
把上面计算的除法结果移到edi
,不为零则循环
400f4b: f3 c3 repz retq
Return(使用 an unusual encoding of the instruction that is optimal for certain AMD processors)。
因此,通过使用 while
循环重写跳转并给出一些有意义的名称...
// number is edi, digits_count is rsi, as per regular
// x64 SystemV calling convention
void count_digits(unsigned number, int digits_count[]) {
while(number) {
digits_count[number%10]++;
number/=10;
}
}
也就是说,这是一个函数,给定一个整数,通过递增 digits_count
数组中的相应桶来计算单个小数位的出现次数。
有趣的事实:如果我们将上面的 C 代码提供给 gcc
(几乎所有 -O1
的最新版本)we obtain back exactly the assembly you provided.
我目前正在处理二进制炸弹任务的第 2 阶段。我无法准确解读某个函数在被调用时的作用。我已经坚持了好几天了。
函数是:
0000000000400f2a <func2a>:
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
400f2e: b9 cd cc cc cc mov [=10=]xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr [=10=]x3,%edx
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
400f3f: 29 c7 sub %eax,%edi
400f41: 83 04 be 01 addl [=10=]x1,(%rsi,%rdi,4)
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
400f4b: f3 c3 repz retq
它在更大的函数中被调用 "phase_2":
0000000000400f4d <phase_2>:
400f4d: 53 push %rbx
400f4e: 48 83 ec 60 sub [=11=]x60,%rsp
400f52: 48 c7 44 24 30 00 00 movq [=11=]x0,0x30(%rsp)
400f59: 00 00
400f5b: 48 c7 44 24 38 00 00 movq [=11=]x0,0x38(%rsp)
400f62: 00 00
400f64: 48 c7 44 24 40 00 00 movq [=11=]x0,0x40(%rsp)
400f6b: 00 00
400f6d: 48 c7 44 24 48 00 00 movq [=11=]x0,0x48(%rsp)
400f74: 00 00
400f76: 48 c7 44 24 50 00 00 movq [=11=]x0,0x50(%rsp)
400f7d: 00 00
400f7f: 48 c7 04 24 00 00 00 movq [=11=]x0,(%rsp)
400f86: 00
400f87: 48 c7 44 24 08 00 00 movq [=11=]x0,0x8(%rsp)
400f8e: 00 00
400f90: 48 c7 44 24 10 00 00 movq [=11=]x0,0x10(%rsp)
400f97: 00 00
400f99: 48 c7 44 24 18 00 00 movq [=11=]x0,0x18(%rsp)
400fa0: 00 00
400fa2: 48 c7 44 24 20 00 00 movq [=11=]x0,0x20(%rsp)
400fa9: 00 00
400fab: 48 8d 4c 24 58 lea 0x58(%rsp),%rcx
400fb0: 48 8d 54 24 5c lea 0x5c(%rsp),%rdx
400fb5: be 9e 26 40 00 mov [=11=]x40269e,%esi
400fba: b8 00 00 00 00 mov [=11=]x0,%eax
400fbf: e8 6c fc ff ff callq 400c30 <__isoc99_sscanf@plt>
400fc4: 83 f8 02 cmp [=11=]x2,%eax
400fc7: 74 05 je 400fce <phase_2+0x81>
400fc9: e8 c1 06 00 00 callq 40168f <explode_bomb>
400fce: 83 7c 24 5c 64 cmpl [=11=]x64,0x5c(%rsp)
400fd3: 76 07 jbe 400fdc <phase_2+0x8f>
400fd5: 83 7c 24 58 64 cmpl [=11=]x64,0x58(%rsp)
400fda: 77 05 ja 400fe1 <phase_2+0x94>
400fdc: e8 ae 06 00 00 callq 40168f <explode_bomb>
400fe1: 48 8d 74 24 30 lea 0x30(%rsp),%rsi
400fe6: 8b 7c 24 5c mov 0x5c(%rsp),%edi
400fea: e8 3b ff ff ff callq 400f2a <func2a>
400fef: 48 89 e6 mov %rsp,%rsi
400ff2: 8b 7c 24 58 mov 0x58(%rsp),%edi
400ff6: e8 2f ff ff ff callq 400f2a <func2a>
400ffb: bb 00 00 00 00 mov [=11=]x0,%ebx
401000: 8b 04 1c mov (%rsp,%rbx,1),%eax
401003: 39 44 1c 30 cmp %eax,0x30(%rsp,%rbx,1)
401007: 74 05 je 40100e <phase_2+0xc1>
401009: e8 81 06 00 00 callq 40168f <explode_bomb>
40100e: 48 83 c3 04 add [=11=]x4,%rbx
401012: 48 83 fb 28 cmp [=11=]x28,%rbx
401016: 75 e8 jne 401000 <phase_2+0xb3>
401018: 48 83 c4 60 add [=11=]x60,%rsp
40101c: 5b pop %rbx
40101d: c3 retq
我完全理解phase_2在做什么,我只是不明白func2a在做什么以及它如何影响0x30(%rsp)处的值等等。因此我总是在0x401003处找到比较语句,炸弹最终在那里爆炸。
我的问题是我不明白输入(相位解)如何通过 func2a 影响 0x30(%rsp) 处的值。
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
这只是当 edi
为零时的提前退出(je
与 jz
相同)。
400f2e: b9 cd cc cc cc mov [=11=]xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr [=11=]x3,%edx
这是一个经典的优化技巧;它是除以乘以倒数的整数算术等价物(参见 here for details);实际上,这和说 edx = edi / 10
是一样的;
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
这里它利用 lea
进行算术运算(在 Intel 语法中更清晰,它是 lea eax,[rdx+rdx*4]
=> eax = edx*5
),然后将结果与自身相加。这一切都归结为 eax = edx*10
.
400f3f: 29 c7 sub %eax,%edi
然后,将其减回 edi
。
所以,总而言之,这是一种计算 edi
的最后一位小数的复杂(但快速)的方法;到目前为止我们所拥有的是这样的:
void func2a(unsigned edi) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
// ...
}
(label1:
是因为400f33
是后面的跳转目标)
进行中:
400f41: 83 04 be 01 addl [=15=]x1,(%rsi,%rdi,4)
同样,这对我来说在英特尔语法中更清晰 - add dword [rsi+rdi*4],byte +0x1
。它是一个32位int
数组的规则递增(rdi
乘以4);所以,我们可以想象 rsi
指向一个整数数组,索引为 edi
.
void func2a(unsigned edi, int rsi[]) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
rsi[edi]++;
}
然后:
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
把上面计算的除法结果移到edi
,不为零则循环
400f4b: f3 c3 repz retq
Return(使用 an unusual encoding of the instruction that is optimal for certain AMD processors)。
因此,通过使用 while
循环重写跳转并给出一些有意义的名称...
// number is edi, digits_count is rsi, as per regular
// x64 SystemV calling convention
void count_digits(unsigned number, int digits_count[]) {
while(number) {
digits_count[number%10]++;
number/=10;
}
}
也就是说,这是一个函数,给定一个整数,通过递增 digits_count
数组中的相应桶来计算单个小数位的出现次数。
有趣的事实:如果我们将上面的 C 代码提供给 gcc
(几乎所有 -O1
的最新版本)we obtain back exactly the assembly you provided.