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)

3 0
原创粉丝点击