怀化市网站建设_网站建设公司_展示型网站_seo优化
2025/12/22 11:28:22 网站建设 项目流程

听同学说安全项目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,使访问到的值的数值变化,*key4main里那个 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_keys34
  • CPU 会把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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询