SSD6 Exercise1 深入分析
来源:互联网 发布:访客网络需要开启吗 编辑:程序博客网 时间:2024/05/29 00:33
题目
Take Assessment: Exercise 1: Decoding Lab
Decoding Lab: Understanding a Secret Message
You have just intercepted an encoded message. The message is a sequence of bits which reads as follows in hexadecimal:
6363636363636363724646636F6D6F72
466D203A65693A7243646E206F54540A
5920453A54756F0A6F6F470A21643A6F
594E2020206F776F797275744563200A
6F786F686E6963736C206765796C656B
2C3365737420346E20216F74726F5966
7565636F202061206C61676374206C6F
20206F74747865656561727632727463
6E617920680A64746F69766120646E69
21687467630020656C6C786178742078
6578206F727478787863617800783174
You have no idea how to decode it, but you know that your grade depends on it, so you are willing to do anything to extract the message. Fortunately, one of your many agents on the field has stolen the source code for the decoder. This agent (007) has put the code and the message in the file secret.cpp, which you can download from the laboratory of your technical staff (Q).
Q has noticed that the decoder takes four integers as arguments. Executing the decoder with various arguments seems to either crash the program or produce unintelligible output. It seems that the correct four integers have to be chosen in order for the program to produce the decoded message. These four integers are the “secret keys.”
007 has been unable to find the keys, but from the desk of the encrypting personnel he was able to cunningly retrieve the first five characters of the unencoded message. These characters are:
From:
Assignment
Your assignment is to decode the message, and find the keys.
Reminders
This exercise is not extremely difficult. However, the strategy of trying things until something works will be ineffective. Try to understand the material in the course, particularly the following:
•Memory contains nothing but bits. Bits are interpreted as integers, characters, or instructions by the compiler, but they have no intrinsic type in memory.
•The compiler can be strong-armed into interpreting integers as characters, or even as instructions, and vice versa.
•Every group of 8 bits (a byte) has an address.
•A pointer in C is merely a stored memory address.
•The activation records for each function call are all together in memory, and they are organized in a stack that grows downwards and shrinks upwards on function calls and returns respectively.
•The return address of one function as well as the addresses of all of its local variables are allocated within one activation record.
Strategy
The designers of this decoder weren’t very good. They made it possible for us to attack the keys in two independent parts. Try to break the first two keys first, and do not try to break the third and fourth keys until you have succeeded with the first two.
You can do the first part by specifying only two integer arguments when you execute the decoder. If you get the first and second keys right, a message that starts with From: will appear. This message is not the true message, but a decoy. It is useful, however, to let you know that you have indeed broken the first two keys.
In breaking the first two keys, realize that the function process_keys12 must be somehow changing the value of the dummy variable. This must be so, because the variables start and stride control the extraction of the message, and they are calculated from the value of dummy.
In breaking the third and fourth keys, try to get the code to invoke extract_message2 instead of extract_message1. This modification must somehow be controlled from within the function process_keys34.
Files
When you are done, write a brief report that includes at least the following:
1.The secret message.
2.The secret keys.
3.One paragraph describing, in your own prose, what process_keys12 does. For example, you might say that it modifies a specific program variable.
4.The meaning of the first two keys in terms of variables and addresses in the decoder program. For example, you might describe key2 by saying that its X-Y bits contain the value to which variable start is set. Or you might describe key1 by saying, for example, that it must be set equal to the number of memory addresses separating the address of two specific variables. These are only examples.
5.One paragraph describing, in your own prose, what process_keys34 does.
6.One paragraph describing the line of source code that is executed when the first call to process_keys34 returns.
7.The meaning of the third and fourth keys in terms of variables and addresses in the decoder program.
Be precise, clear, and brief in each of the points above. Your report should not, in any case, be
longer than one page. Do not get frustrated if this takes a little longer than you expected: brief and clear text often requires more time to write than rambling prose.
Your teacher can tell you what word processors you may use to write your report. Chances are that you can write your report in a number of formats, and for simplicity’s sake, you might even want to write it using Notepad.
Enjoy!
解答思路
阅读完题目后的到如下提示及信息:
- 先解出key1和key2,再去解key3和key4
- key1和key2正解以后会有From:开头的输出
- 函数process_keys12会改变变量dummy的值( function process_keys12 must be somehow changing the value of the dummy variable. )
- 解key3和key4时,尝试让代码调用extract_message2而不调用extract_message1(In breaking the third and fourth keys, try to get the code to invoke extract_message2 instead of extract_message1.)
- 函数 process_keys34(控制代码的调用转变 This modification must somehow be controlled from within the function process_keys34.)
以上算是题目中给出的提示,解题时尽量朝着这些提示去想。
下面我们来分析一下代码,从主程序开始
int main(int argc, char *argv[]){ int dummy = 1; int start, stride; int key1, key2, key3, key4; char * msg1, *msg2; key3 = key4 = 0; if (argc < 3) { usage_and_exit(argv[0]); } key1 = strtol(argv[1], NULL, 0); key2 = strtol(argv[2], NULL, 0); if (argc > 3) key3 = strtol(argv[3], NULL, 0); if (argc > 4) key4 = strtol(argv[4], NULL, 0); process_keys12(&key1, &key2); start = (int)(*(((char *)&dummy))); stride = (int)(*(((char *)&dummy) + 1)); if (key3 != 0 && key4 != 0) { process_keys34(&key3, &key4); } msg1 = extract_message1(start, stride); if (*msg1 == '\0') { process_keys34(&key3, &key4); msg2 = extract_message2(start, stride); printf("%s\n", msg2); } else { printf("%s\n", msg1); } return 0;}
由于要先解出key1和key2,我截取只有两个命令行参数时执行的代码来分析
int main(int argc, char *argv[]){ int dummy = 1; int start, stride; int key1, key2, key3, key4; char * msg1, *msg2; key1 = strtol(argv[1], NULL, 0); key2 = strtol(argv[2], NULL, 0); process_keys12(&key1, &key2); start = (int)(*(((char *)&dummy))); stride = (int)(*(((char *)&dummy) + 1)); msg1 = extract_message1(start, stride); printf("%s\n", msg1); return 0;}
我们理一下思路,判断key1及key2的正确性,是根据最终控制台输出的结果。而控制台输出的msg1是extract_message1(start, stride)的返回值。让后我们分析一下extract_message1(start, stride)方法干了什么?
char * extract_message1(int start, int stride) { int i, j, k; int done = 0; for (i = 0, j = start + 1; !done; j++) { for (k = 1; k < stride; k++, j++, i++) { if (*(((char *)data) + j) == '\0') { done = 1; break; } message[i] = *(((char *)data) + j); } } message[i] = '\0'; return message;}
代码不多,主要是对指针和地址以及int和char转换的理解。
这个方法的作用就是把一个int型的数组中的每一个int数值转换为4个字符,最终从得到的char数组中读取部分字符放入数组message中,当读到‘\0’字符时结束。
参数的作用:
- start:从转换的到的char数组的start+1角标位置开始读取
- stride:当stride>1时,每读取stride-1个字符,隔一个不读,这样循环下去;当stride=1时候就什么也不读取了
好了分析清楚之后,我们知道extract_message1这个函数的结果由start和stride决定,而代码中start和stride的值又由dummy来决定。
start = (int)(*(((char *)&dummy)));stride = (int)(*(((char *)&dummy) + 1));
其中
start等于dummy最低地址那一字节中的数值。
stride等于dummy第二低地址那一字节中的数值。
那么可以肯定process_keys12(&key1, &key2)函数一定时改变了dummy的值。
我们来看process_keys12的代码
void process_keys12(int * key1, int * key2) { *((int *)(key1 + *key1)) = *key2;}
那么很明显(int *)(key1 + *key1)的值即等于dummy变量的地址值。
注意:这里key1是main中key1的地址,*key1等于main中的key1
这样的在指针层面key1(main中)=&dummy-&key1,在本题中这个值很容易看出来,因为它们之间就有3个int型的变量,所以key1=3;
当然也可以调试在代码中看内存中这两个变量的地址差,再除以4同样得到key1的值。
注:用vs调试时候发现这几个连续声明的int型变脸的内存地址竟然不是连续,间隔不是4,而是12,多用的8个字节不知道做什么了,其中的原因大致是vs默认的编译选项是这样的,可以修改编译选项,参考如下博文http://blog.csdn.net/pngfiwang/article/details/49624845。当然也可以不修改,只不过这时候要让key1=9了;
接下来我们分析key2,我们知道key2的值也就等于dummy的值。只有解出start和stride才能知道dummy应该满足什么条件。所以我们要分析start和stride各等于多少时才使输出以From:开头。
我写一段代码直接把data数组转换字符数组显示出来,观察。
int main(){ int data[] = { 0x63636363, 0x63636363, 0x72464663, 0x6F6D6F72, 0x466D203A, 0x65693A72, 0x43646E20, 0x6F54540A, 0x5920453A, 0x54756F0A, 0x6F6F470A, 0x21643A6F, 0x594E2020, 0x206F776F, 0x79727574, 0x4563200A, 0x6F786F68, 0x6E696373, 0x6C206765, 0x796C656B, 0x2C336573, 0x7420346E, 0x20216F74, 0x726F5966, 0x7565636F, 0x20206120, 0x6C616763, 0x74206C6F, 0x20206F74, 0x74786565, 0x65617276, 0x32727463, 0x6E617920, 0x680A6474, 0x6F697661, 0x20646E69, 0x21687467, 0x63002065, 0x6C6C7861, 0x78742078, 0x6578206F, 0x72747878, 0x78636178, 0x00783174 }; for(int i=0;i<44*4;i++){ char a=*((((char *)(data))+i)); printf("%c",a); }}
输出结果:
cccccccccFFrromo: mFr:ie ndCTTo:E YouTGooo:d! NYowo tury cEhoxoscineg lkelyse3,n4 tto! fYoroceu a cgalol tto eextvraectr2 yantdhavioind gth!e
根据上面分析的extract_message1的作用及参数的含义我们不难看出,当start=9,stride=3时候符合情况。读两个Fr隔一个再读om,再隔一个,读:
这时候我们就知道dummy应该满足什么情况了也就是
最高地址 —— ——- 最低地址
———— ———— 03 09
也就是说dummy的值只要后两个字节满足03,09的情况就可以了。内存中是按2进制来保存的,因此就符合16进制的计算规则。所以满足题目的key2值就有多个了,列举几个777,66313,131849等等无穷尽
ps:在做key1和key2的破解调试时候发现message默认容量100不够用
//在调试过程中发现message大小100不够报错,就改为了200char message[200];
输出结果如下:
From: FriendTo: YouGood! Now try choosing keys3,4 to force a call to extract2 andavoid the call to extract1
接下来我们就要破解key3和key4的值了。
分析相关代码
if (key3 != 0 && key4 != 0) { process_keys34(&key3, &key4); } msg1 = extract_message1(start, stride); if (*msg1 == '\0') { process_keys34(&key3, &key4); msg2 = extract_message2(start, stride); printf("%s\n", msg2); } else { printf("%s\n", msg1); }
首先很明确的是下面两句代码一定执行了,因为如extract_message1方法在唯一是From:开头的情况下,输出结果并不对。
msg2 = extract_message2(start, stride);printf("%s\n", msg2);
那么按照国际惯例(一本正经放屁)先来看看extract_message2方法的都干了什么鬼
char * extract_message2(int start, int stride) { int i, j; for (i = 0, j = start; *(((char *) data) + j) != '\0'; i++, j += stride) { message[i] = *(((char *) data) + j); } message[i] = '\0'; return message;}
比extract_message1方法明了一些
start:从char数组角标为start的元素开始
stride:每隔stride-1个元素读取一个
既然执行了extract_message2(start, stride);是不是一定意味着if语句条件为真呢?
莫慌,我们来分析一下
先来猜测一下process_keys34(&key3, &key4);函数干了什么
void process_keys34 (int * key3, int * key4) { *(((int *)&key3) + *key3) += *key4;}
获得形参key3的地址转为一个int指针加上一个常数,也就是便宜到内存中另一个地址处,然后修改了这处地址上的值,好奇怪,被自己说晕了。。。
那有没有可能是修改了start或者stride的值呢?
答案是:no
原因听我继续啰嗦。
如果是修改了start的值,那么让*msg=’\0’的情况就是直接从char数组中’\0’的位置读取,你肯定知道’\0’的ASCII码是0,那么瞅一眼data数组就很明显只有最后一个数组0x00783174的前两位等于0,也就是转成的char数组的最后一个字符时’\0’,如果是这样extract_message2方法得到的字符串肯是没有值的
再来看stride,很明显只有让stride<=1,那么extract_message2肯定不会得到我们要的结果
从以上分析我们可以发现,如果程序是顺序执行的,就会产生矛盾。
只有认为程序不是顺序执行的了,那么程序是在什么时候跳转,又跳转到哪里去了呢?(原谅我废话多)
动动脚趾头都知道process_keys34这个方法使程序发生了跳转,跳转到哪里去也是很容易看出来的,肯定跳过了if条件语句内的process_keys34(&key3, &key4);语句,否则程序将陷入死循环。
那么process_keys34是怎么让程序发生跳转的呢?
猜测一下,就是修改了函数的返回地址嘛
根据函数调用的堆栈原理(不清楚的同学请戳http://blog.chinaunix.net/uid-23069658-id-3981406.html),先看下图吧
对应到本题是这样的
从图中很容看出key3=-1
来来来,就剩最后一个key4 ,key4其实很简单的,就是第一句process_keys34(&key3, &key4);返回地址和第二句process_keys34(&key3, &key4);返回地址的差值。
调试时,打开vs的反汇编窗口,我拿出如下一段代码
if (key3 != 0 && key4 != 0) {003F1709 cmp dword ptr [key3],0 003F170D je main+105h (03F1725h) 003F170F cmp dword ptr [key4],0 003F1713 je main+105h (03F1725h) process_keys34(&key3, &key4);003F1715 lea eax,[key4] 003F1718 push eax 003F1719 lea ecx,[key3] 003F171C push ecx 003F171D call process_keys34 (03F11B3h) 003F1722 add esp,8 } msg1 = extract_message1(start, stride);003F1725 mov eax,dword ptr [stride] 003F1728 push eax 003F1729 mov ecx,dword ptr [start] 003F172C push ecx 003F172D call extract_message1 (03F1118h) 003F1732 add esp,8 003F1735 mov dword ptr [msg1],eax if (*msg1 == '\0') {003F1738 mov eax,dword ptr [msg1] 003F173B movsx ecx,byte ptr [eax] 003F173E test ecx,ecx 003F1740 jne main+159h (03F1779h) process_keys34(&key3, &key4);003F1742 lea eax,[key4] 003F1745 push eax 003F1746 lea ecx,[key3] 003F1749 push ecx 003F174A call process_keys34 (03F11B3h) 003F174F add esp,8
可看出key4=003F174F-003F1722 =0x2d=45
到此就完了,去撸了
如有错误,请指正(0.0)
- SSD6 Exercise1 深入分析
- 卡耐基 ssd6 exercise1
- SSD6 exercise1 解题思路
- exercise1
- ssd6 exercise2
- SSD3-exercise1
- SSD5 exercise1
- exercise1-17
- exercise1-19
- exercise1-16
- exercise1-18
- Exercise1.1.22
- python exercise1
- 做SSD6的感想
- SSD6 位运算作业
- Icarnegie SSD4 Exercise1 Answer
- 分支选择结构Exercise1
- Exercise1.1.29 等值键
- leetcode之Best Time to Buy and Sell Stock
- CSDN中怎样让图片居中和首行缩进
- 偏执却管用的10条Java编程技巧
- vim配置
- Linux下无效唤醒的应用
- SSD6 Exercise1 深入分析
- iOS 画板的简单实现
- 一些关于git的基本使用方法
- multipart/form-data与application/octet-stream的区别、application/x-www-form-urlencoded
- 【HNOI2014】【BZOJ3573】米特运输
- 2015年MyEclipse使用教程最强盘点
- Mapper XML 文件
- c语言入门
- java 实现反转链表