【C语言】SCU安全项目1-FindKeys

目录

前言

命令行参数

16进制转字符串

extract_message1

process_keys12

extract_message2

main

process_keys34


前言

因为这个学期基本都在搞CTF的web方向,C语言不免荒废。所幸还会一点指针相关的知识,故第一个安全项目做的挺顺利的,也把思维切换切换,接触点新东西。

贴出源码

#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];

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);
	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;
}

命令行参数

在C语言程序中,主函数main()可以有两个参数,用于接收命令行参数。带有参数的函数main()习惯上书写为:

(颇有pearcmd的感觉哈哈哈)

int main(int argc,char *argv[])
{
...
}

argc和argv是函数main()的形参(argc和argv分别是argument count和argument
vector的缩写)。用命令行的方式运行程序时,函数main()被调用,与命令行有关的信息作为实参传递给两个形参。

第一个参数argc接收命令行参数(包括命令)的个数;第二个参数argv接受以字符串常量形式存放的命令行参数(包括命令本身也作为一个参数)。字符指针数组argv[]表示各个命令行参数(包括命令),其中argv[0]指向命令,argv[1]指向第1个命令行参数,argv[2]指向第2个命令行参数…argv[argc-1]指向最后一个命令行参数。

审一下源码的这部分

其实就是让我们最少传2个参数(第0个参数为程序名),否则就会exit

16进制转字符串

这里显然只有data数组有用,只需要转换data数组即可

如果直接用16进制转字符串的网站,会发现转换结果并不正确

于是跟着代码逻辑自己写一版16进制逐字节解码程序

#include <stdio.h>

	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
	};
	void decode1(int* data)
	{
		for (int i = 0; ; i++) {
			char c = *(((char*)data) + i);
			putchar(c);
			if (c == '\0') break;
		}
	}
	int main()
	{
		decode1(data);
		return 0;
	}

运行结果![](https://img-
blog.csdnimg.cn/direct/5547fdf0a04e4b2593a0f1d64ec0ca90.png)

两者的差异是什么原因呢?

因为Intel处理器是小端,所以数组里每个int的低位存储在内存的低地址处,即先被转化

例如

int 0x5A33723479(Z3r4y)

在内存中:

79 34 72 33 5A(y4r3Z)

所以我们把data的数据先预处理一下再放入16进制转字符串在线网站即可(虽然没有必要)

接下来看一些关键代码

extract_message1

传入一个start和stride,将data数组转换成字符串

这个函数的功能是,从 data 数组的首地址偏移 start + 1 地址开始,每转换 stride -1
个字符后,就跳过一个字符不转换,重复执行这样的操作直到转换到最后一个字符

已知明文最开始为:From: ,可利用其来倒推出start和stride

这里显然是从第10个字符开始(从0开始计数),每次转换两个字符,故start=9,stride=3

再发现start和stride是由dummy得来的

将dummy最低的字节赋值给start(要等于9),将dummy第二低的字节赋值给stride(要等于3)

即dummy为0x???0309

记录一下低2位转10进制是777

process_keys12

访问以 key1 变量的地址为初始地址,偏移 key1 的值×sizeof(int) 即4字节的地址,赋成key2的值。

这里就可以操纵key1地址将其偏移到dummy的地址,再操纵*key2来控制dummy的值

调试看key1和dummy的地址

(左为key1,右为dummy)

计算地址差值

24➗4=9 ,所以只要令*key1=9即可偏移到dummy的地址

再令*key2=777,使得dummy为0x00000309,成功使得start为9,stride为3

回显正确

提示我们要选择key3key4来调用extract2并且避免调用extract1

extract_message2

其实和extract_message1大同小异

从 data 数组的首地址偏移 start 地址开始,每读入一个字符,就跳过stride-1个字符,直到转换到最后一个字符

可见start=9,stride=3 (刚好就是msg1对应的数据,故key1,key2传的值不用改)

main

通过代码审计,发现只要让extract_message1返回一个空字符串就可以打印出msg2

我们接着看,怎么操纵msg1=‘\0’

extract1 最初会访问第10个字符(从0开始计数),而第10个字符恰好会在 extract2
(从第9个字符开始,每隔2个取一个)中被忽略不会影响答案。
所以我们可以直接尝试改变 data 数组,使其表示的第10个字符为 \0 ,即将data[2]=0x72464663转变成 0x72004663 。
可以借助 process_keys34 来操纵data[2]

process_keys34

这个函数就是把指向key3指针的地址偏移key3的值×sizeof(int)即4个字节的地址,将其解引用,自增一个key4的值

那么我们就可以将指向key3指针的地址偏移到data[2]上,然后利用*key4改变data[2]的值,这里72004663-72464663=-4587520,key4传入-4587520即可

调试观察&key3和data[2]的地址

2597600/4=649400

所以key3传入649400

综上,key1,key2,key3,key4分别传入9 777 649400 -4587520

运行回显正确

实验1还是比较友好的,做的时候没有太坐牢,对于一个C语言小白这种难度刚刚好QWQ

接下来我将给各位同学划分一张学习计划表!

学习计划

那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:

阶段一:初级网络安全工程师

接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。

综合薪资区间6k~15k

1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?

阶段二:中级or高级网络安全工程师(看自己能力)

综合薪资区间15k~30k

7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。

零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完

用Python编写漏洞的exp,然后写一个简单的网络爬虫

PHP基本语法学习并书写一个简单的博客系统

熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)

了解Bootstrap的布局或者CSS。

阶段三:顶级网络安全工程师

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

学习资料分享

当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值