听同学说安全项目1有点烦,但做了感觉挺简单。项目1本身是很有助于复习指针和理解栈的含义的。
写下一份总结帮助自己顺一下思路。
1. 前置知识
(一些在mooc安全项目提示讲过)
指针运算;
内存memory里变量和代码的按顺序存储;
多字节数据在内存地址按小端序排列;
堆栈的用法;
函数调用的活动记录;
main函数的参数 int main(int argc, char* argv[]);
放一张图帮助理解栈帧:
2. message1(key1和key2)
贴出全部源码
#include <stdio.h> #include <stdlib.h> int prologue[] = { 0x5920453A, 0x54756F0A, 0x6F6F470A, 0x21643A6F, 0x6E617920, 0x680A6474, 0x6F697661, 0x20646E69, 0x63636363, 0x63636363, 0x72464663, 0x6F6D6F72, 0x63636363, 0x63636363, 0x72464663, 0x6F6D6F72, 0x2C336573, 0x7420346E, 0x20216F74, 0x726F5966, 0x7565636F, 0x20206120, 0x6C616763, 0x74206C6F, 0x20206F74, 0x74786565, 0x65617276, 0x32727463, 0x594E2020, 0x206F776F, 0x79727574, 0x4563200A }; 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 }; int epilogue[] = { 0x594E2020, 0x206F776F, 0x79727574, 0x4563200A, 0x6E617920, 0x680A6474, 0x6F697661, 0x20646E69, 0x7565636F, 0x20206120, 0x6C616763, 0x74206C6F, 0x2C336573, 0x7420346E, 0x20216F74, 0x726F5966, 0x20206F74, 0x74786565, 0x65617276, 0x32727463 }; char message[100];//默认初始值全为0 void usage_and_exit(char* program_name) { fprintf(stderr, "USAGE: %s key1 key2 key3 key4\n", program_name); exit(1); } void process_keys12(int* key1, int* key2) { *((int*)(key1 + *key1)) = *key2; } void process_keys34(int* key3, int* key4) { *(((int*)&key3) + *key3) += *key4; } 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; } 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; } 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);//把key1,2做了一个strtol的转换,字符到long init型 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; }题目解读:解码16进制密文
实验要求:源文件的源码不能修改
目标:四个整数,key
由题目得,解密后应该又From开头
调试→窗口→内存→输入data 可以看到data部分在内存中经过小端排序,再经过ascii编码转换,可以得到一串字符,似乎包含我们需要的From
理清思路:从提示中的值start和stride决定dummy
审一下main函数部分:
发现当没有key3和key4时,通过start和stride读取一段消息,得到message1
extract_message1函数处理:
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);//对 data 的起始地址按字节做偏移 } } message[i] = '\0'; return message; }总结:从start+1开始读取,每读stride-1跳过一个字字节
再会看一开始data经过ascii编码的那段文字
很轻易得到从第十个字节开始读,每读两个字节跳一个
所以start=9,stride=3
start = (int)(*(((char *) &dummy))); // 取 dummy 的第1个字节 stride = (int)(*(((char *) &dummy) + 1)); // 取 dummy 的第2个字节 &dummy→ dummy 的地址 (char *)&dummy→ 指向 dummy第 1 个字节 (((char *)&dummy)→取这个字节的值 (int)(...)→ 转成 int而start和stride是由dummy决定的,虽然main函数一开始定义了dummy=1,所以其实是这一步对dummy函数进行了修改,利用指针篡改数据
((char *) data):把int型的data变成char型(四个字节一个int变成四部分,四个字符)
((char *) data) + j:指针 + n = 地址 + n × sizeof(指向的类型)
查看key1和dummy的地址
得到:
key1=key1 = (&dummy - &key1) / sizeof(int)=-3
key2=0x00000309(前四位无所谓)=777
调试→属性→配置属性→调试—>写参数,再运行就能得到正确的message1
3. message2(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); }要想得到message2而不是message1似乎只能让message1的第个字节是’0’
不过这是错误的尝试。
再查看提示,
提示我们从process_keys34函数内部进行控制
除了刚才那里的if控制流调用了process_keys34,在这里也使用了
if (key3 != 0 && key4 != 0) { process_keys34(&key3, &key4); }再观察一下process_keys34函数执行了什么操作?有什么可以实现劫持控制流的地方
void process_keys34(int* key3, int* key4) { *(((int*)&key3) + *key3) += *key4; }左边:
解引用
key3 这个指针变量在栈上的地址 当成 int* 来用,再在在栈上按sizeof(int)为单位偏移key3 个 int
通过地址,去访问该地址处存放的“值”
右边:
再+key4,使访问到的值的数值变化,*key4是main里那个 key4 的值
这里的起点是&key3在process_keys34 自己栈帧的位置,也只能在 process_keys34 的栈附近改
思路:用key3和key4控制流程,篡改process_keys34 的返回地址,计算出 extract_message2 函数的地址。 用 Key3 定位到返回地址在栈上的位置。 用 Key4 把返回地址修改成 extract_message2 的函数入口地址
劫持程序,改变执行流
查看&key3在函数栈上的位置,在调试到进入process_keys34函数时看到:
- 0x000000d9416ffd10 :这是 &key3 的地址 。也就是 process_keys34 这个函数在自己的栈帧里存放参数 key3 的位置。
- 0x000000d9416ffd44 :这是 key3 指向的地址 。也就是 main 函数里那个变量 key3 的地址。
需要关注的是前面那个:0x000000d9416ffd10
虽然这里对message2的处理函数其实是不同的,但仁慈的题目实则不需要我们重新给stride和start,而且这俩也已经被key1和2固定了
for (i = 0, j = start; *(((char*)data) + j) != '\0'; i++, j += stride) { message[i] = *(((char*)data) + j); } //从 start 开始,每隔 stride 个字节取一个字符寻找process_keys34 的返回地址和 extract_message2 函数的起始地址
当 main 调用
process_keys34时:call process_keys34CPU 会把main 中 call 指令的下一条地址
压入栈中,所以在main里查看返回地址
call X 里有两个地址: X:写在指令里的,是“函数入口地址” 被压栈的:是“call 指令下一条指令的地址”,它不会写在指令里
返回地址内容:00007FF65C4C1D19,但我们是要进到存放这个地址值的位置进行修改
key3 = (返回地址内容所在位置 - &key3) / sizeof(int) (一定要分清楚栈地址和内容值啊!!)
给一个错误例子:
别去内存窗口输入地址值查找,窗口输入返回地址内容对应的是代码…别弄混了
正确的找返回地址方法:
在process_keys34的堆栈上找:
先找到&key3在process_keys34函数堆栈的位置(这里重新做了一遍,所以反汇编里地址和&key3地址都变了)
在栈附近找小端排序的返回地址值00007FF71C271A59
找到返回地址的位置:0x0000004B154FFA88
而这个返回地址的内容值加上key4大小后变成成 extract_message2
起始地址值的地址:
07FF71C271082
这里因为是要得到extract_message2 的“起始地址的值”(内容值)
所以直接用用07FF71C271082本身即可
key3:
0x88-0x90=-0x08
0x08=8
-8/4=-2
0x07FF71C271082-0x07FF71C271A59=0x29=41
key3=-2,key4=41,key12不变,重复一开始写入key1和key2的步骤写入所有key,得到最后的message2