2016年4月19日星期二

Tinc, Proxy-arp(TODO)

首先需要明确一下什么是Proxy ARP,它有什么用?
给两个参考链接,解释的挺到位的。
Cisco关于Proxy ARP的解释
某人对于Proxy ARP的见解


其实这个工作模式的tinc就是我一直想要的场景,目标内网机器反向vpn连回vps,我可以直接在vps打内网。省得再拖着一堆工具进去。有时候不小心留了个exe在里面,又不巧碰上弱扫和杀软定时检查。那就懵b了。况且在这种配置下工作,也不会把你暴露到目标网络里,相对隐蔽。


目标:搭建一个反向vpn服务,可以直接访问目标网络里的机器(tinc里给出的配置)

工具:tinc

工作模式:route/switch

所需权限:Host1(root)
这种模式的配置,相比 Tinc,Route Mode 而言需要的权限不要那么大。不需要动到路由器。动静也小的多。

网络结构如下:
TODO

2016年4月14日星期四

php_screw在未知密钥情况下的破解

php_screw模块是一个依赖与zlib与流式加密的php代码加密模块。
主要构成为:
密钥模块:php_screw-1.5/my_screw.h
加密模块:php_screw-1.5/tools/screw.c
解密模块:php_screw-1.5/php_screw.c

具体的加密细节可以参考文件中的实现。

大体加解密的过程如下:
加密过程:
PHP文件(明文) -> zencode ->流式加密(my_screw.h秘钥)-> PHP文件(密文)
解密过程(可逆):
PHP文件(密文)-> 流式解密(my_screw.h秘钥)-> zdecode -> PHP文件(明文)

整个加密模块的弱点在于,在每次加密的过程中都使用相同的密钥,且没有用到IV。因此基于xor运算的特性,在已知一段未知密文,一段明文与密文的对照之后。可以破解未知密文多对照的明文。

破解只需要关注加密部分,解密部分完全可以忽略,因为解密部分依赖的密钥又不在我们手上= =!

下面来分解一下加密的过程:
输入:(PHP文件(明文)-> zencode)->
加密:流式加密(my_screw.h秘钥)->
输出:PHP文件(密文)

撇开加解密过程中zlib的参与(既zencode方法),仅关注中间的流式加密部分。
可以把经过zencode处理的php文件理解为流式加密过程中的明文。
经过流式加密处理的内容既为密文。

因此要破解一段密文a,我们还缺少一分明文与密文的对照。
密文b很简单,直接用对方的screw模块处理一下就能获得。
明文b也和简单,将一个PHP文件中的内容交给zencode处理一下就行了。
接下来将
(密文a ) xor (密文b) xor (明文b) 既可得到明文a
只不过这里得到的明文a是经过zencode处理的。
得到明文a之后调用zdecode一下即可得到最终解密的php代码。


以上情况基于密钥每次都相同的情况。
但实际情况下密钥并不是每次都相同的,但是密钥是有一定规律的,其规律与输入的明文长度有关。来看screw.c中的加密代码。可以知道密钥基于 pm9screw_mycryptkey短整型数组,在实际使用的时,并不是从0元素开始到循环尾元素的,而是基于明文的长度来选择一个起始位置,然后向 前迭代。如果两段明文的长度之间遵守一定规律,是可以构造出相同密钥加密后的密文的。

规律很简单,因为是流式加密,且没有带入任何影响密文长度的内容,因此密文的长度就是明文的长度。
假如密钥的长度为len(key), 对于一段长度为n的密文,其明文的长度也为n。
密钥的起始位置应该为(n-i) % len(key),又因为i默认为0,因此起始位置为:
n % len(key)。
为了构造起始位置相同的密钥,其明文的长度k必须大于n并且满足
k % len(key) == n % len(key)。
构造出此明文,经过screw加密一下得到对应的密文。在加上未知的密文。
这三者拿去xor一下,把xor得到的结果丢给zdecode处理,最终可获得未知密文对应的明文信息。

本场景适用于仅对密钥内容进行了修改, 未对 php_screw的原始加密算法进行过修改。
如果密钥长度修改过的话,就自己按
“明文的长度k必须大于n并且满足k % len(key) == n % len(key)”的规则去创建明文,然后一个个匹配结果吧!

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include "../php_screw.h"
#include "../my_screw.h"

char *CryptBinaryData(char *pData, int nDataLen, int *pNewDataLen)
{
    char *pRet=NULL;
    *pNewDataLen = 0;
    pRet = zencode(pData, nDataLen, pNewDataLen);
  
    int    cryptkey_len = sizeof pm9screw_mycryptkey / 2;
    printf("cryptkey_len: %d\r\nfirst_key_elemnt:%d\r\n",cryptkey_len,pm9screw_mycryptkey[(*pNewDataLen - 0) % cryptkey_len]);
    int i=0;
    for(i=0; i<*pNewDataLen; i++) {
        pRet[i] = (char)pm9screw_mycryptkey[(*pNewDataLen - i) % cryptkey_len] ^ (~(pRet[i]));
    }
    return pRet;
  
}

void printf_string_on_element(char *pString, int nLen)
{
    int i=0;
    for(i=0; i<nLen; ++i)
    {
        printf("%d ",pString[i]);
    }
    printf("\r\n");
}

int main(int argc, char **argv)
{
    /*
  
     默认key长度为5
     "<? echo '123'?>";//zencode后的长度为23
     "<? echo '12345678'?>";//zencode后的长度为28
   
     满足
     23 % 5 == 28 % 5 && 28 > 25
    */
    char *pSourceText1="<? echo '123'?>";
    int nSourceText1Len = strlen(pSourceText1);
    char *pSourceText2="<? echo '12345678'?>";
    int nSourceText2Len = strlen(pSourceText2);
  
    //构造pSourceText2对应的“明文”
    int nPlainText2Len = 0;
    char *pPlainText2=zencode(pSourceText2,nSourceText2Len, &nPlainText2Len);
    printf("%s encoded by zencode:%d\r\n%sr\n",pSourceText2, nPlainText2Len, pPlainText2);
    printf_string_on_element(pPlainText2, nPlainText2Len);

    //构造pSourceText1的密文
    int nCipherText1Len = 0;
    char *pCipherText1 = CryptBinaryData(pSourceText1,nSourceText1Len,&nCipherText1Len);
    printf("%s Cryptyed %d:\r\n%s\r\n",pSourceText1, nCipherText1Len, pCipherText1);
    printf_string_on_element(pCipherText1, nCipherText1Len);

    //构造pSourceText2的密文
    int nCipherText2Len = 0;
    char *pCipherText2 = CryptBinaryData(pSourceText2,nSourceText2Len,&nCipherText2Len);
    printf("%s Cryptyed %d:\r\n%s\r\n",pSourceText2, nCipherText2Len, pCipherText2);
    printf_string_on_element(pCipherText2, nCipherText2Len);
  
    //xor
    int i =0;
    char plaintextBuffer[1024]={0};
    for(i=0; i<nCipherText1Len; ++i)
    {
        //plaintextBuffer[i] =pCipherText2[i] ^ pCipherText1[i] ^ (~pPlainText2[i]);
        //plaintextBuffer[i] = ~plaintextBuffer[i];
        //上面两句可以简化为下面这一句,因为两个非运算后,还是得到原始结果
        plaintextBuffer[i] =pCipherText2[i] ^ pCipherText1[i] ^ (pPlainText2[i]);
    }
    printf("%s\r\n",plaintextBuffer);
    printf_string_on_element(plaintextBuffer, nCipherText1Len);
    int nPlainTextResultLen = 0;
    char *pPlainTextResult = zdecode(plaintextBuffer, nCipherText1Len, &nPlainTextResultLen);
    printf("(%d)%s\r\n",nPlainTextResultLen, pPlainTextResult);
  
    return 0;
}

Tinc,Route Mode


目标:搭建一个反向vpn服务。

工具:tinc

工作模式:route

所需权限:Host1(root),Route/Firewall(root)

网络结构如下:
    当前权限位于Route/Firewall内的LAN中,网段为:192.168.1.0/24, 主机为192.168.1.121(下面图有错。)
    VPN所在的虚拟网段为10.0.0.0/8
    希望在Host1与vps server之间开一条隧道,通过vps server连入192.168.1.0/24。



配置可以参考官网给的windows下的配置:http://tinc-vpn.org/examples/windows-install/

Host1上的配置文件:
%TINC_HOME%/to_vps/tinc.conf
    Name = local_host
    ConnectTO = vps
    Interface = vpn


%TINC_HOME%/to_vps/hosts/local_host
    Subnet = 10.0.0.3/32
    Subnet = 192.168.1.0/24

附加Key后拷贝到vps server上的 %TINC_HOME%/vps/hosts/ 目录下

Host1上的网卡配置:

vps server上的配置文件
%TINC_HOME%/vps/tinc.conf
    Name = vps
    Interface = vpn


%TINC_HOME%/vps/hosts/vps
    Subnet = 10.0.0.2/32
 
附加Key后拷贝到Host1上的 %TINC_HOME%/to_vps/hosts/ 目录下

vps server上的网卡配置








配置好之后两台机器可以互通

 
 


现在需要增加配置,让vps server可以访问目标的192.168.1.0/24网段。
tinc启动后,vps server上的路由表是这样的

里面并没有到192.168.1.0/24的路由,需要手工添加。
Route add 192.168.1.0 mask 255.255.255.0 10.0.0.3 metric 1 if 14
解释一下这条路由:
目标网络
    192.168.1.0 mask 255.255.255.0 对应的就是192.168.1.0/24
网关
    10.0.0.3,对应的就是Host1那台主机
接口
    这里14是tinc所使用的虚拟网卡的网卡ID


刚开始以为只要配置了这条路由之后,就能访问内部的192.168.1.0/24网络了。
但是ping的时候却出问题了。




然后我就想这是什么毛病。。。想了很久想不明白,什么书都翻出来看了也没头绪,头都要炸了。算了干脆tcpdump抓个包看看。
这不是有东西么。。

在看看目标网络上的包情况。

这不是响应了么。。。
但是我在vps server为什么收不到?

看下包的详细信息吧。

下面那条192.168.1.111 > 10.0.0.2,响应的
在看看Host1上的arp table。
arp源地址 (ac:xx:xx:xx:xx:xx)对应的是192.168.1.111,这条没错。
arp 目标地址(50:xx:xx:xx:xx:xx)对应的是192.168.1.1 ,这尼玛是目标网段(192.168.1.0/24)的网关啊。

ICMP Reply没往Host1(192.168.1.121)上发,再由经tinc转回vps server,而是发到网关去了,难怪收不到。
怎么办?如果我在192.168.1.111上加一条arp记录,直接指定10.0.0.2的位置行不行?


看来不行 =,=

那加一条路由记录指定一下10.0.0.0/8网段的路由是不是就ok了?
往路由表里塞一条记录试试看。

加上了!!回去ping一下看看!

卧槽!通了。
抓包看看!

还是下面那条 192.168.1.111 > 10.0.0.2
arp源地址 (ac:xx:xx:xx:xx:xx)对应的是192.168.1.111,这条没错。
arp 目标地址(00:xx:xx:xx:xx:xx)对应的是Host1(192.168.1.121) ,这是Host1的mac地址,这下没错了。

那我现在要让192.168.1.0/24的所有机器都能和vps server互通,是不是要一台台机器去加路由=,=。
这不科学。。。

然后我就想,既然10.0.0.2被路由到了192.168.1.1。那我直接在192.168.1.1上加个路由是不是就ok了?
先把192.168.1.111的路由删了,然后上192.168.1.1去试试。


删了。然后不通了。。。

上192.168.1.1去加



加上了




好激动。。在回去ping试试看。



都通了!!

这里有几个概念没搞明白,导致问题折腾这么久。
首先vpn所处的网段10.0.0.0/8和目标网段192.168.1.0/24是不同的网段。
需要路由的介入两个网段才能完成通信。
虽然在vps server上添加了到达192.168.1.0/24的路由记录。
数据包从vps server上的tinc服务到达Host1上的tinc服务,并由Host1送达目标网络上的主机流程如下。
Ping 192.168.1.111后
ICMP Echo数据包首先通过vps server上的路由记录

送达tinc所管理的虚拟网卡(10.0.0.2)上,vps server上的tinc服务将该数据包转发给Host1上的tinc服务
因为目标地址192.168.1.111与Host1(192.168.1.121)处在相同网络,通过路由表+arp表。

可以找到192.168.1.111的位置。(Host1开启了ip转发功能)
(
Windows上同样需要开启IP转发功能



比较蛋疼,需要重启
)
接下来ICMP Echo数据包会到达192.168.1.111。
此时数据包的源地址为10.0.0.2。
问题就出在这!192.168.1.111在回应ICMP Reply数据包的时候会检查自己的路由表,查找10.0.0.2这个IP地址的下一站在哪。

而没有经过配置的192.168.1.111的路由表是没有10.0.0.0/8这个网络下一条的地址的。
于是ICMP Reply被送往默认网关(路由表中的第一条记录)

很明显,网关也没经过配置,自然也不知道10.0.0.0/8的下一跳在哪,所以将该ICMP Reply丢弃。
这就导致了在vps server虽然有了通往192.168.1.0/24网段的路由,但是却无法ping通192.168.1.111的问题。
网络间的互通不仅需要路由的介入,还需要每台主机上的路由表介入。
这下192.168.1.0/24上的主机,也能直接与10.0.0.2通信了。