LAB5
3.56
题目描述
consider the following assembly code
x at %ebp+8, n at %ebp+12
movl 8(%ebp), %esi
movl 12(%ebp), %ebx
movl $1431655765, %edi
movl $-2147483648, %edx
.L2:
movl %edx, %eax
andl %esi, %eax
xorl %eax, %edi
movl %ebx, %ecx
shrl %cl, %edx
testl %edx, %edx
jne .L2
movl %edi, %eax
the preceding code was generated by compiling C code that had the following overall from:
int loop(int x,int n){
int result = 0x55555555;
int mask;
for(mask = -0x80000000;mask!=0;mask = mask>>n){
result ^= (mask&x);
}
return result;
}
思考问题
-
Which registers hold program values x,n,result, and mask?
%esi : x %ebx : n
%edi : result %edx : mask -
What are the initial values of result and mask?
result = 1431655765
mask = -2147483648 -
What are the test condition for mask?
test condition == ~ZF & SF when the loop continue
-
How does mask get updated?
shrl %cl, %edx
-
How does result get updated?
xorl %eax, %edi
-
Fill in all missing parts of the C code?
as show in the C code
知识要点分析
- testl命令的应用
testl指令是将两个操作数做与来设置零标志位和负数标志(状态码),常用方法(也是本题的方法)为<code>testl %eax, %eax</code>
这种方法用来检查%eax
是正数、负数还是零。
另外,该命令不改变%%eax
的值,而是设置状态码
- 数据寄存器
数据寄存器主要用来保存操作数和运算结果等信息。
32位CPU有4个32位的通用寄存器,%eax、%ebx、%ecx、%edx,对于低16位的存取,不会影响高16位数据的存取,这几个低16位的寄存器分别命名为%ax、%bx、%cx、%dx,而4个16位寄存器又可分割为8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。
八位寄存器的功能
- %ax、%al 累加器(Accumulator) high using rate
- %bx 基地址寄存器(Base Register) pointer to memory
- %cx 计数寄存器(Count Register) control the times of the loop
- %cl declare the bits of shifting 在位运算中声明移位的位数,%cl取%ecx的低十六位
3.56(2)
题目描述
_Change the assembly code in 3.56 with the following code, then fill in all the missing parts of the C code
x at %ebp+8, n at %ebp+12
movl 8(%ebp), %esi
movl 12(%ebp), %ebx
movl $-1, %edi
movl $1, %edx
.L2:
movl %edx, %eax
andl %esi, %eax
xorl %eax, %edi
movl %ebx, %ecx
sall %cl, %edx
testl %edx, %edx
jne .L2
movl %edi, %eax
the complete C code are:
int loop(int x,int n){
int result = -1;
int mask;
for(mask = 1;mask!=0;mask = mask<<n){
result ^= (mask&x);
}
return result;
3.59
题目描述
this problem will give you a chance to reverse engineer a switch statement from machine code. In the following procedure, the body of the switch statement has been removed, please use the assembly code and gdb fill the missing C code.
all the info of disassemble code and gdb(x) has been omited.
题目分析
- (gdb)x/6w 0x80485d0 就是switch的跳转表
- 根据题目反汇编代码按0-5的顺序查找对应的跳转位置即可
C code
int switch_prob(int x,int n){
int result = x;
switch(n){
case 32:
case 34:
result = result<<2;
break;
case 33:
result = result+12;
break;
case 35:
result = result>>2;
break;
case 36:
result = result*3;
result = result*result;
result = result + 12;
break;
case 37:
result = result*result;
result = result+12;
break;
default:
result+=12;
}
return result;
}
知识要点分析
-
跳转表
在gcc编译器中,如果switch语句中case的种数相对较多且值的变化范围较小,那么在汇编中将使用跳转表
我们可以将跳转表理解为指针数组,数组的索引值与case值相关,指针指向要跳转的代码位置。
拓展题
problem one
题目
下面的代码片段常常出现在库函数的编译版本中:
call next
next:
popl %eax
A.寄存器%eax被设置成了什么值?
%eax被设置为了popl指令的地址
执行call next之前,PC(程序计数器) 为popl指令的地址(PC始终未下条指令地址);
执行call next之后,%esp被设置为popl指令的地址(call指令的顺序下一条,即调用者返回的地址)
popl指令使得%eax的值设置为M[R[%esp]],也就是将popl指令的地址放入了%eax中
B.解释为什么这个调用没有与之匹配的ret指令
这不是一个真正地过程调用,因为控制是按照与指令相同的顺序进行的,而返回值是从栈中弹出的,所以没有ret指令。
C.这段代码完成了什么功能?
将程序计数器的值放入整数寄存器
D.call指令的伪汇编代码表示
pushl CP
jmp <address>
preblem two
题目描述
运行程序a,给出输出结果
运行结果
Hello World!
401353 PointTo?
problem three & four
题目描述
反汇编main函数的汇编代码,观察main函数
Dump of assembler code for function main:
0x000005a0 <+0>: lea 0x4(%esp),%ecx
0x000005a4 <+4>: and $0xfffffff0,%esp
0x000005a7 <+7>: pushl -0x4(%ecx)
0x000005aa <+10>: push %ebp
0x000005ab <+11>: mov %esp,%ebp
0x000005ad <+13>: push %ebx
0x000005ae <+14>: push %ecx
0x000005af <+15>: call 0x5ff <x86.get_pc_thunk.ax>
0x000005b4 <+20>: add $0x1a4c,%eax
0x000005b9 <+25>: sub $0x8,%esp
0x000005bc <+28>: call 0x5c7 <main+39>
0x000005c1 <+33>: bound %ebp,0x6e(%ecx)
0x000005c4 <+36>: outsl %ds:(%si),(%dx)
0x000005c6 <+38>: add %ch,%al
0x000005c8 <+40>: sbb (%eax),%al
0x000005ca <+42>: add %al,(%eax)
0x000005cc <+44>: dec %eax
0x000005cd <+45>: gs insb (%dx),%es:(%edi)
0x000005cf <+47>: insb (%dx),%es:(%edi)
0x000005d0 <+48>: outsl %ds:(%esi),(%dx)
0x000005d1 <+49>: and %dl,0x6f(%edi)
0x000005d4 <+52>: jb 0x642 <libc_csu_init+50>
0x000005d6 <+54>: and %ecx,%fs:(%edx)
0x000005d9 <+57>: and $0x6f502058,%eax
0x000005de <+62>: imul $0xa3f6f54,0x74(%esi),%ebp
0x000005e5 <+69>: add %cl,-0x1ec173d(%ecx)
0x000005eb <+75>: (bad)
0x000005ec <+76>: incl 0xb810c4(%ebx)
0x000005f2 <+82>: add %al,(%eax)
0x000005f4 <+84>: add %cl,0x5b59f865(%ebp)
0x000005fa <+90>: pop %ebp
0x000005fb <+91>: lea -0x4(%ecx),%esp
0x000005fe <+94>: ret
- 找到输出的地址指向的是什么
- 找到printf中的格式串的地址
- 解释为什么没有printf却有字符串打印出来了
- 请用合理的语言/图示来表示main函数的实际逻辑
题目分析
1. 使用gdb查看gdb运行结果中的地址
(由于gdb禁用ASLR,所以可以这么做)
Starting program: /home/vermouthdky/code_test/LAB4/a
Hello World!
565555C1 PointTo?
(gdb) x/s 0x565555C1
0x565555c1 <main+33>: "bingo"
发现运行结果中的地址指向一个字符串“bingo”
2. 使用(gdb) x/ns 查看地址call 0x565555c7 <main+39>的字符串
(gdb) x/94s main
打印部分输出结果
0x565555c1 <main+33>: "bingo"
0x565555c7 <main+39>: "\350\032"
0x565555ca <main+42>: ""
0x565555cb <main+43>: ""
0x565555cc <main+44>: "Hello World!\n%X PointTo?\n"
0x565555e6 <main+70>: "\211\303\350\023\376\377\377\203\304\020\270"
得到printf格式串的地址 0x565555cc <main+44>
3.使用(gdb)x/i 查看地址的命令
- 查看格式字符串地址<main+44>上的一条指令
call 0x5c7 <main+39>
中的地址(gdb) x/4i main+39
,得到输出:
0x565555c7 <main+39>: call 0x565555e6 <main+70>
0x565555cc <main+44>: dec %eax
0x565555cd <main+45>: gs insb (%dx),%es:(%edi)
0x565555cf <main+47>: insb (%dx),%es:(%edi)
- 查看<main+70>的指令,得到输出:
0x565555e6 <main+70>: mov %eax,%ebx
0x565555e8 <main+72>: call 0x56555400 <printf@plt>
0x565555ed <main+77>: add $0x10,%esp
0x565555f0 <main+80>: mov $0x0,%eax
由此我们知道,main函数在<main+70>处调用了<printf@plt>
所以main函数实际上是调用了printf函数的,这就解释了为什么会有字符串打印出来
4.说明main函数的运行逻辑
-
下面是main的call指令与字符串的地址分布
address | content |
---|---|
main ~ main+27 | initialize |
main+28 | goto main + 39 |
main+33 ~ main+38 | "bingo"(there is a '\0') |
main+44 ~ main+69 | "Hello World!\n%X PointTo?\n" |
main+72 | call <printf@plt> |
需要注意的是main+28处的call指令实际上起到了jmp的作用
-
main函数的运行逻辑
main函数的运行逻辑实际上就是顺序的,只不过由于字符串的储存使得在顺序执行的时候用到了call跳转地址的情况。
push "bingo"
push "Hello World!\n%X PointTo?\n"
call printf
ret
网友评论