PRX 剖析Proxifer “远程域名解析”NSP工作原理

Proxifer应该是SOCKS5协议代理客户端中最优秀与可靠的工具 但它只支持UDP同时不开放源代码 但好在Proxifer并没有对其应用程式进行混淆加壳 本文将深度剖析其“远程代理解析”的实现原理。

笔者对Proxifer NSP部分进行长达半月之久的研究拉锯战 成功吸收与实现了与Proxifer“远程域名解析”相类似的功能 它本身并不神奇而且Proxifer解决此问题的方式 应是出乎意料的简单,本文不介绍与Proxifer NSP的其它相关部分 只是探讨其NSP/DLL工作原理 至于“远程域名解析”其它的坑 不在本文中累赘;

那么什么是“远程域名解析 / Remote DNS Resovle ” ? 本质上是将域名提交给代理服务器 由代理服务器进行获取其A、AAAA地址 然后与对应服务器建立对应IP传输层协议的数据连接。

它解决了GFW(中国防火长城)对指定DNS数据包污染的问题 即客户端通过DNS查询类似Youtube、Google、facebook等外国受GFW屏蔽的网站时 GFW会不停地探测来自公网上所有流通的DNS协议数据包;一般DNS服务器端口为53;然后劫持客户端请求并伪造一个指向无效地址的DNS响应数据包,与DNS数据交换协议相关的内容 你可以参考RFC1035定义的标准;

远程域名解析,具备几种不同的方法

如:

1、将应用层解析DNS的行为传送至远程代理 由远程代理解析后返回地址到应用层

2、本地DNS服务器

3、Virtual DNS Resolve

很明显“Proxifer”它采取的是第三种方法 即“Virtual DNS Resolve”,一旦开启远程域名解析 那么应用层所有DNS解析将不再离开网卡 它是在应用层虚拟的DNS解析返回 即按照Proxifer的控制端定义为 从“127.8.0.0”地址开始“原子自增”。

“Proxifer”很明智的没有采取hook的方式 这是由于hook的方式容易被杀软拦截所导致 所以它采取了另一种方法来解决“DNS”解析的问题 即通过开发NSP(namespace service provider)名字服务器提供者

应用程式通过“PrexNsp”解析DNS时 它本身没有真正意义上的向DNS服务器查询域名解析结果 而是通过伪造一个struct WSAQUERYSETW分配一个唯一的虚拟IP(fuck ip)

给此域名返回上层 同时与RnR之间交互(SOCKET交互)而在笔者的轻量级实现内是剔除这些实现的。

下面列出“远程域名解析”实现的关键代码:

int WINAPI NSPLookupServiceNext(HANDLE hLookup, DWORD dwControlFlags, LPDWORD lpdwBufferLength, LPWSAQUERYSETW lpqsResults)
{
	try {
		PaperAirplaneConfiguration* conf = PaperAirplaneInteractive_Current.Configuration();
		if (!(conf->EnableProxyClient && conf->ResolveDNSRemote))
		{
			return NamespaceServiceProvider_Current.NSP_ROUTINE.NSPLookupServiceNext(hLookup, dwControlFlags, lpdwBufferLength, lpqsResults);
		}
		NSPLookupContext* context = (NSPLookupContext*)hLookup;
		if (context == NULL || IsBadReadPtr(context, sizeof(NSPLookupContext)) || context->behavior >= 1)
		{
			return SOCKET_ERROR;
		}
		LPCSTR hostaddr = StringExtension::W2A(context->hostname);
		context->hostaddr = hostaddr;
		if (hostaddr == NULL)
		{
			return SOCKET_ERROR;
		}
		else
		{
			strlwr((char*)hostaddr);
		}
		memset(lpqsResults, 0, sizeof(WSAQUERYSETW));
		lpqsResults->dwSize = 120;
		lpqsResults->dwNameSpace = NS_DNS;
		lpqsResults->dwNumberOfProtocols = 0;
		lpqsResults->dwNumberOfCsAddrs = 1;
		lpqsResults->dwOutputFlags = 0;
		lpqsResults->lpszQueryString = context->hostname;
		lpqsResults->lpszServiceInstanceName = context->hostname;
		lpqsResults->lpcsaBuffer = new CSADDR_INFO({ 0 });

		struct sockaddr_in* localEP = new struct sockaddr_in({ 0 });
		localEP->sin_family = AF_INET;

		struct sockaddr_in* remoteEP = new struct sockaddr_in({ 0 });
		remoteEP->sin_family = AF_INET;

		ULONG address = 0;
		if (NamespaceMappingTable_Current.ContainsKey(hostaddr))
		{
			address = NamespaceMappingTable_Current.Get(hostaddr);
			remoteEP->sin_addr.s_addr = address;
		}
		else
		{
			address = InterlockedIncrement(&RAND_MAP_IPADDRESS_NUM);
			address = ntohl(address);
			remoteEP->sin_addr.s_addr = address;

			NamespaceMappingTable_Current.Add(address, hostaddr);
		}
		CSADDR_INFO* addr = lpqsResults->lpcsaBuffer;

		addr->iSocketType = SOCK_RAW;
		addr->iProtocol = 23;

		addr->RemoteAddr.iSockaddrLength = sizeof(struct sockaddr_in);
		addr->LocalAddr.iSockaddrLength = sizeof(struct sockaddr_in);
		addr->LocalAddr.lpSockaddr = (struct sockaddr*)localEP;
		addr->RemoteAddr.lpSockaddr = (struct sockaddr*)remoteEP;

		context->behavior = 1;
		return NO_ERROR;
	}
	catch (...) {
		return SOCKET_ERROR;
	}
}
但“远程域名解析”需要处理的部分远远不止上述这些代码 它是一个很复杂的体系 上述代码只是伪造一个域名解析地址 同时记录在“名称服务映射表”中 但它不代表你可以使用

它就可以直接翻墙、只是说它可以规避掉被“DNS污染”的问题。

猜你喜欢

转载自blog.csdn.net/liulilittle/article/details/78023812