这是一次课程设计,话说这个题目有些复杂,但毕竟是最后一次,可以理解
实现目标:
实现内容:
可自由发挥的空间不多,在密钥交换后,我实现的是客户端可以输入简单指令在服务端执行,并获取反馈
使用工具:
因为限定了C语言和Linux
大数计算:Libtommath
加密: Libtomcrypt
抓包发包: Libpcap
由于这三个库安装起来较简单,这里就不叙述了(网上一堆)
libtommath和libtomcrypt网上资料较少,看github上的pdf文档即可
部分内容凭记忆写的,有问题欢迎指正
libtomcrypt虽然本身有D-H密钥交换的功能实现,但是直接用的话怕是太水了(总不能课设一个include就完事),故以学习为目的手动实现了下,设计如下:
符号&&流程概述:
双方共享p,g,g为p的原根
A生成一个只有自己知道的数字a,计算x=g^a mod p
将x发送给B
B生成一个只有自己知道的数字b,计算y=g^b mod p
将y发送给A
A计算key = y^a mod p
B计算key = x^b mod p
根据交换原理,二者生成key相同
具体实现:
libtommath 生成一个大素数
mp_prime_random_ex(p, 8, 256, LTM_PRIME_2MSB_ON | LTM_PRIME_SAFE, rng, NULL);
注意,一定要带上 LTM_PRIME_SAFE 参数,原因在下一部分会说
g是p的原根,并不需要多大
根据原根检测技巧(参考自:https://blog.csdn.net/zhang20072844/article/details/11541133)
即有一个数x, 求出x-1所有不同的质因子p1,p2…pm,对于任何2<=a<=x-1,判定a是否为x的原根,只需要检验a^((x-1)/p1) ,
a^((x-1)/p2) …a^((x-1)/pm)这m个数中,是否存在一个数mod x为1,若存在,a不是x的原根,否则就是x的原根
而文档中对LTM_PRIME_SAFE参数的叙述是这样的:
Make a prime p such that (p - 1)/2 is also prime.
This option implies LTM PRIME BBS as well.
即p-1一定只有两个质因子2和(p-1)/2,这大大方便了检测,具体实现代码如下
(可能有些麻烦,不过功能没问题)
void get_primitive_root(mp_int* num, mp_int* root)
{
mp_set(root, 2);
mp_int temp, param1, param2;
// 第一个参数为2
mp_init_set(¶m1, 2);
mp_init_multi(&temp, ¶m2, NULL);
// 第二个参数问 (roo-1)/2
mp_sub_d(num, 1, ¶m2);
mp_div_2(¶m2, ¶m2);
while (true)
{
mp_exptmod(root, ¶m1, num, &temp);
if (mp_cmp_d(&temp, 1) != MP_EQ)
{
mp_exptmod(root, ¶m2, num, &temp);
if (mp_cmp_d(&temp, 1) != MP_EQ)
{
break;
}
}
mp_add_d(root, 1, root);
}
mp_clear_multi(&temp, ¶m1, ¶m2, NULL);
}
随机且足够大的数即可,这里我采用的是mp_rand方法
官方文档中的mp_rand是这样描述的
This function generates a random number of digits bits.
然而实际测试中这个bits并不是二级制位,而是空间占用? 看了下源码,是mp_int.used的值
故如果生成一个和p差不多大的数字:
mp_rand(a, key->p.used);
我自定义的协议头:
开头两个字节为df,第三位为1(版本号),第四位为操作特征符
定义特征符为
1:发起密钥交换
2:回应密钥交换
0:交换后的正常数据传输
定义一个特征头部有助于抓包中进行判定和筛选数据包
这里直接调用的libtomcrypt库中相关加密代码(自己写头大且没必要)
由于是单个包加密,所以没那么麻烦
加密:
int aes_256_gcm_encrypt(mp_int* key, unsigned char* plain_text, int pt_len, unsigned char* enc_text, unsigned char* IV, int IV_len, unsigned char* tag, unsigned long tag_len)
{
gcm_state gcm;
unsigned char pass[32];
register_cipher(&aes_desc);
mp_to_unsigned_bin(key, pass);
return gcm_memory(find_cipher("aes"), pass, 32, IV, IV_len, NULL, 0, plain_text, pt_len, enc_text, tag, &tag_len, GCM_ENCRYPT);
}
解密:
int aes_256_gcm_decrypt(mp_int* key, unsigned char* plain_text, int pt_len, unsigned char* enc_text, unsigned char* IV, int IV_len, unsigned char* tag, unsigned long tag_len)
{
gcm_state gcm;
register_cipher(&aes_desc);
unsigned char pass[32];
mp_to_unsigned_bin(key, pass);
return gcm_memory(find_cipher("aes"), pass, 32, IV, IV_len, NULL, 0, plain_text, pt_len, enc_text, tag, &tag_len, GCM_DECRYPT);
}
几乎直接调用库函数,这里IV为32字节随机字符,tag为16字节,无需预先赋值
由于解密也需要iv和tag,故这两个参数随着socket一起发过去
memcpy(buf + 4, iv, 32);
memcpy(buf + 36, tag, 16);
if (aes_256_gcm_encrypt(&k, input, len, output, iv, 32, tag, 16) != CRYPT_OK)
{
printf("Error in encrypt!\n");
continue;
}
memcpy(buf + 52, output, len);
D-H密钥交换能应付简单的截获数据的中间人,即中间人在只截获数据的情况下无法获得密钥
而专门对付D-H的中间人则是建立双向连接
即分别和双方进行秘钥交换建立连接,双方的通信内容会被先加密后解密
计网相关:
当网卡判断目的IP和本机IP在同一个子网时,会将数据包广播出去(目的MAC为目的主机的MAC),否则会直接交给网关(目的MAC为网关)
当本机网卡收到一个数据包时,会判断数据包的目的MAC是不是本机,如果是,保留并有效,否则直接丢弃
ARP攻击的目的则是让通讯的双方都认为目的主机MAC是中间人机器,发送的数据包虽然是广播(即能到达目的主机),但会因为MAC地址不同而被丢弃,反而是中间人机器会正常收到数据包
本次实验模拟的环境是同一子网下的两台主机,即ARP欺骗两台主机即可
如果是不同子网的,则需要欺骗一台主机和网关
首先中间人要保证双方不能收到真实数据包,这里采用ARP欺骗的方法
arpspoof -i ens33 -t 192.168.13.128 -r 192.168.13.129
ens33为网卡名称
这里双向投毒(-r)保证双方的数据包都无法正常到达对方
投毒后实现思路如下:
(这里坑了我一下午。。日)
IP校验和只校验IP头,TCP校验和是校验伪头部+整个TCP头+数据字段,如果你修改了数据字段(上思路第二步第三步),不修改校验和的话,发送的数据包会直接(静悄悄地)被当作错误包丢弃
TCP校验和是计算伪首部(源IP,目的IP,0,6,长度)+校验和字段置为0的TCP字段
(代码来自:https://blog.csdn.net/airarts_/article/details/49496579)
C
uint16_t calc_cksm(void *pkt, int len)
{
uint16_t *buf = (uint16_t*)pkt;
uint32_t cksm = 0;
while (len > 1)
{
cksm += *buf++;
cksm = (cksm >> 16) + (cksm & 0xffff);
len -= 2;
}
if (len)
{
cksm += *((uint8_t*)buf);
cksm = (cksm >> 16) + (cksm & 0xffff);
}
return (uint16_t)((~cksm) & 0xffff);
}
这里实现由于未改动IP头,故IP校验和不用改
发包:
我是用libpcap的pcap_sendpacket直接发的包(libnet发包没发出去也没报错,不知为何)
其它的内容就是bug和代码的堆砌,没什么好说的
来自: https://bbs.csdn.net/topics/370116470
使用 PSK 时,AP 和客户端必须配置相同的密钥或加密密码。AP 发送一个随机字符串到客户端。客户端接受该字符串,根据密钥对其进行加密(或编码),然后发送回 AP。AP 获取加密的字符串,并使用其密钥解密(或解码)。如果从客户收到的字符串在解密后与原来发送给客户端的字符串匹配,就允许该客户端连接。
具体实现: