驱动开发:内核实现进程汇编与反汇编

在笔者上一篇文章《驱动开发:内核MDL读写进程内存》简单介绍了如何通过MDL映射的方式实现进程读写操作,本章将通过如上案例实现远程进程反汇编功能,此类功能也是ARK工具中最常见的功能之一,通常此类功能的实现分为两部分,内核部分只负责读写字节集,应用层部分则配合反汇编引擎对字节集进行解码,此处我们将运用capstone引擎实现这个功能。

首先是实现驱动部分,驱动程序的实现是一成不变的,仅仅只是做一个读写功能即可,完整的代码如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
#include <windef.h>

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

#define DEVICENAME L"\\Device\\ReadWriteDevice"
#define SYMBOLNAME L"\\??\\ReadWriteSymbolName"

typedef struct
{
	DWORD pid;       // 进程PID
	UINT64 address;  // 读写地址
	DWORD size;      // 读写长度
	BYTE* data;      // 读写数据集
}ProcessData;

// MDL读取封装
BOOLEAN ReadProcessMemory(ProcessData* ProcessData)
{
	BOOLEAN bRet = TRUE;
	PEPROCESS process = NULL;

	// 将PID转为EProcess
	PsLookupProcessByProcessId(ProcessData->pid, &process);
	if (process == NULL)
	{
		return FALSE;
	}

	BYTE* GetProcessData = NULL;
	__try
	{
		// 分配堆空间 NonPagedPool 非分页内存
		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
	}
	__except (1)
	{
		return FALSE;
	}

	KAPC_STATE stack = { 0 };
	// 附加到进程
	KeStackAttachProcess(process, &stack);

	__try
	{
		// 检查进程内存是否可读取
		ProbeForRead(ProcessData->address, ProcessData->size, 1);

		// 完成拷贝
		RtlCopyMemory(GetProcessData, ProcessData->address, ProcessData->size);
	}
	__except (1)
	{
		bRet = FALSE;
	}

	// 关闭引用
	ObDereferenceObject(process);

	// 解除附加
	KeUnstackDetachProcess(&stack);

	// 拷贝数据
	RtlCopyMemory(ProcessData->data, GetProcessData, ProcessData->size);

	// 释放堆
	ExFreePool(GetProcessData);
	return bRet;
}

// MDL写入封装
BOOLEAN WriteProcessMemory(ProcessData* ProcessData)
{
	BOOLEAN bRet = TRUE;
	PEPROCESS process = NULL;

	// 将PID转为EProcess
	PsLookupProcessByProcessId(ProcessData->pid, &process);
	if (process == NULL)
	{
		return FALSE;
	}

	BYTE* GetProcessData = NULL;
	__try
	{
		// 分配堆
		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
	}
	__except (1)
	{
		return FALSE;
	}

	// 循环写出
	for (int i = 0; i < ProcessData->size; i++)
	{
		GetProcessData[i] = ProcessData->data[i];
	}

	KAPC_STATE stack = { 0 };

	// 附加进程
	KeStackAttachProcess(process, &stack);

	// 分配MDL对象
	PMDL mdl = IoAllocateMdl(ProcessData->address, ProcessData->size, 0, 0, NULL);
	if (mdl == NULL)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(mdl);

	BYTE* ChangeProcessData = NULL;

	__try
	{
		// 锁定地址
		ChangeProcessData = MmMapLockedPages(mdl, KernelMode);

		// 开始拷贝
		RtlCopyMemory(ChangeProcessData, GetProcessData, ProcessData->size);
	}
	__except (1)
	{
		bRet = FALSE;
		goto END;
	}

	// 结束释放MDL关闭引用取消附加
END:
	IoFreeMdl(mdl);
	ExFreePool(GetProcessData);
	KeUnstackDetachProcess(&stack);
	ObDereferenceObject(process);

	return bRet;
}

NTSTATUS DriverIrpCtl(PDEVICE_OBJECT device, PIRP pirp)
{
	PIO_STACK_LOCATION stack;
	stack = IoGetCurrentIrpStackLocation(pirp);
	ProcessData* ProcessData;

	switch (stack->MajorFunction)
	{

	case IRP_MJ_CREATE:
	{
		break;
	}

	case IRP_MJ_CLOSE:
	{
		break;
	}

	case IRP_MJ_DEVICE_CONTROL:
	{
		// 获取应用层传值
		ProcessData = pirp->AssociatedIrp.SystemBuffer;

		DbgPrint("进程ID: %d | 读写地址: %p | 读写长度: %d \n", ProcessData->pid, ProcessData->address, ProcessData->size);

		switch (stack->Parameters.DeviceIoControl.IoControlCode)
		{
		// 读取函数
		case READ_PROCESS_CODE:
		{
			ReadProcessMemory(ProcessData);
			break;
		}
		// 写入函数
		case WRITE_PROCESS_CODE:
		{
			WriteProcessMemory(ProcessData);
			break;
		}

		}

		pirp->IoStatus.Information = sizeof(ProcessData);
		break;
	}

	}

	pirp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	if (driver->DeviceObject)
	{
		UNICODE_STRING SymbolName;
		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

		// 删除符号链接
		IoDeleteSymbolicLink(&SymbolName);
		IoDeleteDevice(driver->DeviceObject);
	}
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_OBJECT device = NULL;
	UNICODE_STRING DeviceName;

	DbgPrint("[LyShark] hello lyshark.com \n");

	// 初始化设备名
	RtlInitUnicodeString(&DeviceName, DEVICENAME);

	// 创建设备
	status = IoCreateDevice(Driver, sizeof(Driver->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &device);
	if (status == STATUS_SUCCESS)
	{
		UNICODE_STRING SymbolName;
		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

		// 创建符号链接
		status = IoCreateSymbolicLink(&SymbolName, &DeviceName);

		// 失败则删除设备
		if (status != STATUS_SUCCESS)
		{
			IoDeleteDevice(device);
		}
	}

	// 派遣函数初始化
	Driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtl;
	Driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtl;
	Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtl;

	// 卸载驱动
	Driver->DriverUnload = UnDriver;

	return STATUS_SUCCESS;
}

上方的驱动程序很简单关键部分已经做好了备注,此类驱动换汤不换药没啥难度,接下来才是本节课的重点,让我们开始了解一下Capstone这款反汇编引擎吧,Capstone是一个轻量级的多平台、多架构的反汇编框架。Capstone旨在成为安全社区中二进制分析和反汇编的终极反汇编引擎,该引擎支持多种平台的反汇编,非常推荐使用。

  • 反汇编引擎下载地址:http://cdn.lyshark.com/sdk/capstone_msvc12.zip

这款反汇编引擎如果你想要使用它则第一步就是调用cs_open()官方对其的解释是打开一个句柄,这个打开功能其中的参数如下所示;

  • 参数1:指定模式 CS_ARCH_X86 表示为Windows平台
  • 参数2:执行位数 CS_MODE_32为32位模式,CS_MODE_64为64位
  • 参数3:打开后保存的句柄&dasm_handle

第二步也是最重要的一步,调用cs_disasm()反汇编函数,该函数的解释如下所示;

  • 参数1:指定dasm_handle反汇编句柄
  • 参数2:指定你要反汇编的数据集或者是一个缓冲区
  • 参数3:指定你要反汇编的长度 64
  • 参数4:输出的内存地址起始位置 0x401000
  • 参数5:默认填充为0
  • 参数6:用于输出数据的一个指针

这两个函数如果能搞明白,那么如下反汇编完整代码也就可以理解了。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
#include <inttypes.h>
#include <capstone/capstone.h>

#pragma comment(lib,"capstone64.lib")

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

typedef struct
{
	DWORD pid;
	UINT64 address;
	DWORD size;
	BYTE* data;
}ProcessData;

int main(int argc, char* argv[])
{
	// 连接到驱动
	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	ProcessData data;
	DWORD dwSize = 0;

	// 指定需要读写的进程
	data.pid = 6932;
	data.address = 0x401000;
	data.size = 64;

	// 读取机器码到BYTE字节数组
	data.data = new BYTE[data.size];
	DeviceIoControl(handle, READ_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
	for (int i = 0; i < data.size; i++)
	{
		printf("0x%02X ", data.data[i]);
	}

	printf("\n");

	// 开始反汇编
	csh dasm_handle;
	cs_insn *insn;
	size_t count;

	// 打开句柄
	if (cs_open(CS_ARCH_X86, CS_MODE_32, &dasm_handle) != CS_ERR_OK)
	{
		return 0;
	}

	// 反汇编代码
	count = cs_disasm(dasm_handle, (unsigned char *)data.data, data.size, data.address, 0, &insn);

	if (count > 0)
	{
		size_t index;
		for (index = 0; index < count; index++)
		{
			/*
			for (int x = 0; x < insn[index].size; x++)
			{
				printf("机器码: %d -> %02X \n", x, insn[index].bytes[x]);
			}
			*/

			printf("地址: 0x%"PRIx64" | 长度: %d 反汇编: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str);
		}
		cs_free(insn, count);
	}
	cs_close(&dasm_handle);

	getchar();
	CloseHandle(handle);
	return 0;
}

通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,反汇编当前位置处向下64字节。

说完了反汇编接着就需要讲解如何对内存进行汇编操作了,汇编引擎这里采用了XEDParse该引擎小巧简洁,著名的x64dbg就是在运用本引擎进行汇编替换的,本引擎的使用非常简单,只需要向XEDParseAssemble()函数传入一个规范的结构体即可完成转换,完整代码如下所示。

  • 汇编引擎下载地址:http://cdn.lyshark.com/sdk/XEDParse.zip
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

extern "C"
{
#include "D:/XEDParse/XEDParse.h"
#pragma comment(lib, "D:/XEDParse/XEDParse_x64.lib")
}

using namespace std;

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

typedef struct
{
	DWORD pid;
	UINT64 address;
	DWORD size;
	BYTE* data;
}ProcessData;

int main(int argc, char* argv[])
{
	// 连接到驱动
	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	ProcessData data;
	DWORD dwSize = 0;

	// 指定需要读写的进程
	data.pid = 6932;
	data.address = 0x401000;
	data.size = 0;

	XEDPARSE xed = { 0 };
	xed.x64 = FALSE;

	// 输入一条汇编指令并转换
	scanf_s("%llx", &xed.cip);
	gets_s(xed.instr, XEDPARSE_MAXBUFSIZE);
	if (XEDPARSE_OK != XEDParseAssemble(&xed))
	{
		printf("指令错误: %s\n", xed.error);
	}

	// 生成堆
	data.data = new BYTE[xed.dest_size];

	// 设置长度
	data.size = xed.dest_size;

	for (size_t i = 0; i < xed.dest_size; i++)
	{
		// 替换到堆中
		printf("%02X ", xed.dest[i]);
		data.data[i] = xed.dest[i];
	}

	// 调用控制器,写入到远端内存
	DeviceIoControl(handle, WRITE_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);

	printf("[LyShark] 指令集已替换. \n");
	getchar();
	CloseHandle(handle);
	return 0;
}

通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,可打开反内核工具验证是否改写成功。

打开反内核工具,并切换到观察是否写入了一条mov eax,1的指令集机器码,如下图已经完美写入。

文章作者:lyshark (王瑞)
文章出处:http://www.cnblogs.com/LyShark/p/17145544.html
版权声明:本博客文章,除去特殊声明 [转载标注/参考文献] 部分, [均为原创] 作品,禁止任何形式的转载!
本文转载于网络 如有侵权请联系删除

相关文章

  • while,do whlie,for循环

    while循环while语句是一个循环语句,它会首先判断一个条件是否满足,如果条件满足,则执行后面紧跟着的语句或语句括号,然后再次判断条件是否满足,如果条件满足则再次执行,直到条件不满足为止。后面紧跟的语句或语句括号,就是循环体。#include<stdio.h> intmain(){ intx; intn=0; scanf("%d,&x"); n++; x/=10; while(x>0){ n++; x/=10; } printf("%d",n); return0; }复制 程序分析: 此代码是计算一个数值有几位数 当我们输入x的值为352,先做一遍n++和x/=10,那么n=1,x=35; 现在x的值>0,符合循环条件,进入循环,继续做n++和x/=10, 此时n=2,x=3;x的值还是大于0,继续进入循环,继续做n++和x/=10, 此时n=3,x=0;得出352这个数值有三位数。如果我们把while翻译成"当",那么一个while循环的意思就是:当条件满足时,不断地重复循环体内的语句。循

  • Spring不能将包含key值为null的map集合转换成JSON

    #Spring不能将包含key值为null的map集合转换成JSONNullkeyforaMapnotallowedinJSONSpring不能将包含key值为null的map集合转换成JSON#1.问题描述编写代码进行测试的时候,控制台报出如下错误org.springframework.http.converter.HttpMessageNotWritableException:CouldnotwriteJSON:NullkeyforaMapnotallowedinJSON(useaconvertingNullKeySerializer?);nestedexceptioniscom.fasterxml.jackson.databind.JsonMappingException:NullkeyforaMapnotallowedinJSON(useaconvertingNullKeySerializer?)(throughreferencechain:com.common.base.common.ResultVO["data"]->java.util.HashM

  • 脏读、幻读与不可重复读[通俗易懂]

    大家好,又见面了,我是全栈君。最近在读《MySQL技术内幕InnoDB存储引擎》,里面提到的各种概念都很新鲜,以前听说过脏读、幻读、不可重复读,但是对于概念不甚了解,于是查了一下,这里做个笔记。数据库事务特征数据库事务特征,即ACID:AAtomicity原子性事务是一个原子性质的操作单元,事务里面的对数据库的操作要么都执行,要么都不执行,CConsistent一致性在事务开始之前和完成之后,数据都必须保持一致状态,必须保证数据库的完整性。也就是说,数据必须符合数据库的规则。IIsolation隔离性数据库允许多个并发事务同事对数据进行操作,隔离性保证各个事务相互独立,事务处理时的中间状态对其它事务是不可见的,以此防止出现数据不一致状态。可通过事务隔离级别设置:包括读未提交(Readuncommitted)、读提交(readcommitted)、可重复读(repeatableread)和串行化(Serializable)DDurable持久性一个事务处理结束后,其对数据库的修改就是永久性的,即使系统故障也不会丢失。MySQL数据隔离级别首先MySQL里有四个隔离级别:Readuncom

  • 二叉树的建立与遍历

    为什么学二叉树?因为计算机二级会涉及到一部分知识。在模拟考试的时候看到就直接跳过去了,心塞。终于花时间在网上找了源码好好看了一下也懂了个一二三。搜的时候是简单二叉树建立与遍历,所以自己学的不深,但是我感觉应付计算机二级也是够了。计算机二级主要还是主要以选择题出,所以基本知识点还是有必要了解的。 本次参考文章讲解:点击访问(本文章代码几乎和原文相同) 本文基本知识点参考于:未来教育二级C计算机二级主要还是主要以选择题出,所以基本知识点还是有必要了解的。树的基本树(Tree)是简单的非线性结构。样子基本长这样 关键词:父节点:例如A就是父节点(根节点),在它没有前件(也就是它之上没有连接它的了,它是开头)。子节点:B、C、D就是子节点,有上一个点连接它,它同时还连接下面的点。叶节点:E、F、G、H、I、J是叶节点,上面有连接它的,但是它没有连接下面的。度:简单而言就是它所连接下面的个数,例如A连接B、C、D,A的度就是3个,B连接E、F、G度也是3个,C连接一个H,所以C的度是1个。对于这一个树而言度的大小取决于所有节点中最大的度,A的度是3,B的度是3,C的度是1,D的度是2,所以取最大

  • ansible(2)——基本命令

    可以类比于hadoop命令: -v/-vv/-vvv/-vvvv:随着v的增多,信息也会更详细: 切换目录chdir: 查看模块:ansible-doc 查看帮助信息: 查看所有的模块: 查看某些模块的简单帮助,此处列举为ping模块(详细帮助不用加-s): 测试主机是否存活ping: 检测所有主机是否存活: 查看配置:ansible-config 查看帮助信息: 查看ansible的配置: 查看ansible的变量: yaml格式显示的详细配置信息list选项:

  • Electron安装过程深入解析(读完此文解决Electron安装失败导致的无法启动,无法打包的问题)

    1.安装Electron依赖包开发者往往通过npminstall(或yarnadd)指令完成为Node.js工程安装依赖包的工作,安装Electron也不例外,下面是npm和yarn的安装Electron依赖包的指令:npminstallelectron--save-dev yarnaddelectron--dev复制官方推荐我们把electron依赖包安装为开发依赖(devDependencies),这实际上是为了将来制作应用程序安装包时,避免把electron包和其可执行文件包装两次。这部分内容后文我们会详细讲解。如果你希望观察npminstall指令的具体执行细节,可以为其添加两个参数,如下所示:npminstallelectron--save-dev--timing=true--loglevel=verbose复制通过以上指令安装Electron依赖包,你会观察到整个安装过程的执行情况日志,这里我截取几个重要的日志分析一下。npmhttpfetchGET200https://registry.npm.taobao.org/electron125ms复制这是npm通过http协议

  • 人群接触网络中的 SIR 疫情模拟

    查看本案例完整的数据、代码和报告请登录数据酷客(http://cookdata.cn)案例板块。快速获取案例链接、直播课件:数据酷客公众号内发送“疫情案例”。视频内容如何用网络来表示人之间的接触关系?在接触网络中,如何通过SIR模型模拟疫情的发展趋势?本案例将介绍SIR模型,图和网络的基本知识。然后使用networkx工具,在生成的随机网络和真实的网络数据上,实现网络中的SIR模型进行疫情模拟。1SIR模型介绍SIR模型用于计算封闭人群中随着时间推移感染传染性疾病的人数。最早提出来解释在瘟疫(1665-1666年伦敦,1906年孟买)和霍乱(1865年伦敦)等流行病中观察到的感染病人数量的迅速上升和下降。 伦敦大瘟疫是1665年~1666年间,爆发在英国伦敦的大规模传染病,超过8万人死于瘟疫,相当于当时城市人口的1/5。它假定人口规模是固定的(即无出生和自然死亡等)。传染源的潜伏期是瞬时的,传染持续时间与疾病的长度相同。人群是没有结构的,人之间的接触是完全随机的。恢复之后即获得免疫力,不会继续染病。1.1SIR模型SIR是传染病中的最基础最核心的模型,可以使用下面的图形来表示。如图所示

  • 利用Python进行数据分析(8) pandas基础: Series和DataFrame的基本操作

    利用Python进行数据分析(8)pandas基础:Series和DataFrame的基本操作一、reindex()方法:重新索引针对Series的重新索引操作重新索引指的是根据index参数重新进行排序。如果传入的索引值在数据里不存在,则不会报错,而是添加缺失值的新行。不想用缺失值,可以用fill_value参数指定填充值。fill_value会让所有的缺失值都填充为同一个值,如果不想这样而是用相邻的元素(左或者右)的值填充,则可以用method参数,可选的参数值为ffill和bfill,分别为用前值填充和用后值填充:针对DataFrame的重新索引操作二、drop()方法:丢弃数据针对Series针对DataFrame不仅可以删除行,还可以删除列:三、索引、选取和过滤针对Series需要注意一点的是,利用索引的切片运算与普通的Python切片运算不同,其末端是包含的,既包含最后一个的项。比较:赋值操作:针对DataFrameDataFrame中的ix操作:四、算术运算和数据对齐针对Series将2个对象相加时,具有重叠索引的索引值会相加处理;不重叠的索引则取并集,值为NA:针对Da

  • Spring Cloud Ribbon负载均衡

    SpringCloudRibbon负载均衡SpringCloudRibbon负载均衡一、简介二、客户端负载均衡三、RestTemplate详解GET请求POST请求PUT请求DELETE请求一、简介 SpringCloudRibbon是一个基于HTTP和TCP的客户端负载工具,它基于NetflixRibbon实现,我们可以使用它来进行远程服务负载均衡的调用。它不像Zuul和Eureka等可以独立部署,它虽然是一个工具类框架,但是几乎所有的SpringCloud微服务架构和基础设施都离不开它,包括后面所介绍的Feign远程调用,也是基于Ribbon实现的工具二、客户端负载均衡负载均衡是在一个架构中非常重要,而且不得不去实施的内容。_因为负载均衡对系统的高可用,网络压力的缓解和处理能力扩容的重要手段之一。通常负载均衡分为两种:硬件负载均衡和软件负载均衡,硬件负载均衡一般是通过硬件来实现,在_服务器节点之间安装特定的负载均衡设备_,比如F5。而软件负载均衡是采用软件控制的手段实现的,它实在_服务器之间安装某种特定功能的软件来完成特定的请求分开工作,比如Nginx等。无论硬件负载还是软件负载,

  • 最近面试Java后端开发的感受

    来源:cnblogs.com/JavaArchitect/p/10011253.html在上周,我密集面试了若干位Java后端的候选人,工作经验在3到5年间。我的标准其实不复杂:第一能干活,第二Java基础要好,第三最好熟悉些分布式框架。我相信其它公司招初级开发时,应该也照着这个标准来面的。 我也知道,不少候选人能力其实不差,但面试时没准备或不会说,这样的人可能在进团队干活后确实能达到期望,但可能就无法通过面试,但面试官总是只根据面试情况来判断。但现实情况是,大多数人可能面试前没准备,或准备方法不得当。要知道,我们平时干活更偏重于业务,不可能大量接触到算法,数据结构,底层代码这类面试必问的问题点,换句话说,面试准备点和平时工作要点匹配度很小。作为面试官,我只能根据候选人的回答来决定面试结果。不过,与人方便自己方便,所以我在本文里,将通过一些常用的问题来介绍面试的准备技巧。大家在看后一定会感叹:只要方法得当,准备面试第一不难,第二用的时间也不会太多。别让人感觉你只会山寨别人的代码框架是重点,但别让人感觉你只会山寨别人的代码!在面试前,我会阅读简历以查看候选人在框架方面的项目经验,在候选人

  • 如何使用Ubuntu 14.04上的Git Hooks将Hugo站点部署到生产环境

    介绍Hugo是一个静态站点生成器,允许您通过使用简单的标记语言轻松创建和发布Web内容。Hugo可以根据提供的要求解析您的内容并应用主题,以生成可以轻松托管在任何Web服务器或主机上的一致网页。在本指南中,我们将向您展示如何设置一个系统git,您可以使用该系统将新内容自动部署到生产Web服务器。准备对于本指南,我们假设您已经启动并运行了Ubuntu14.04计算机作为您的开发计算机。(一台已经设置好可以使用sudo命令的非root账号的Ubuntu服务器,并且已开启防火墙。没有服务器的同学可以在这里购买,不过我个人更推荐您使用免费的腾讯云开发者实验室进行试验,学会安装后再购买服务器。)我们将建立第二台Ubuntu14.04服务器来为我们的实际生产网站服务。在此服务器上,确保已创建具有sudo权限的非root用户。准备开发服务器我们将从我们的开发服务器(通过之前的Hugo指南设置的服务器)开始。使用您上次使用的相同非root帐户登录该服务器。我们需要在此服务器上执行一些操作以设置一步式部署。我们要:配置对我们的生产服务器的SSH密钥访问将初始git存储库传输到生产服务器将生产服务器作为g

  • 微信车票背后的设计故事

    作者:黎翠霞|腾讯高级交互设计师导语|公交车票,对于现在的我们而言或许稍显陌生,但在回忆里,那曾是一代人的情怀,伴随着共同的成长,伴随时光的逝去,让我们品味一路风景与欢乐,到达人生的每一站。微信车票的设计师试图通过“微信车票”来收集用户情感,还原用户出行场景,从而打造出一个温暖的产品。公交车票,对于现在的我们而言或许稍显陌生,但在回忆里,那曾是一代人的情怀,伴随着共同的成长,伴随时光的逝去,让我们品味一路风景与欢乐,到达人生的每一站。小小的车票虽不起眼,却是历史最忠实的记录者。从1899年中国出现首辆有轨电车,发行弥足珍贵的首套车票,到无人售票公交、BRT快速公交、观光专线的出现,车票的不断变迁见证了中国公交的发展历程乃至中国日新月异的发展进程。车票上的面值反应了当时的经济状况,公司的名称反应了当时的社会制度,标语口号反应了当时的时代特征,甚至从印刷车票所使用的纸张中也可以看出当时的物质条件情况。但随着时代的发展,公交车票却渐渐淡出人们的生活。为了重拾那份遗失的小美好,结合时代发展的步伐,腾讯设计师为用户打造了一系列电子车票,让传统车票以新的生命出现在大众视野中。今天,你能在手机上收到

  • Golang语言实现猜数字小游戏的方法

    随机生成一个数字,输入一个数字看是否匹对,匹配则结速,反之提示是大了还是小了 packagemain import( "bufio" "fmt" "math/rand" "os" "strconv" "time" ) var( endNumint//设置生成数的范围 ) funcmain(){ i:=createRandomNumber(endNum) //fmt.Println("生成规定范围内的整数:",i)//本句调试用 fmt.Println("请输入整数,范围为:0-",endNum) flag:=true reader:=bufio.NewReader(os.Stdin) forflag{ data,_,_:=reader.ReadLine() command,err:=strconv.Atoi(string(data))//stringtoint,并作输入格式判断 iferr!=nil{ fmt.Println(&

  • 秋招提前批已来,万字长文教你如何增加面试大厂的成功率&#128293;

    本文是笔者在春季在@前端早早聊(手动笔芯)的面试专场分享的文字稿,主要针对前端社招,校招和实习的同学仅供参考,感兴趣的同学可以点击链接查看PPT和录屏——《前端如何提高面试大厂的通过率》 字节跳动秋季招聘提前批已经启动,欢迎投递幸福里业务线,内推码:WPBP917,大量HC,帮忙跟进面试进度,详细介绍可见文末。咨询内推事宜可加微信389399428. 内推码:WPBP917 字节跳动幸福里2022届校招提前批简历投递通道: 【前端校招(北京)】投递链接:https://jobs.toutiao.com/s/eggqpqB 【前端校招(上海)】投递链接:https://jobs.toutiao.com/s/eggvKXF 【后端研发(北京)】投递链接:https://jobs.toutiao.com/s/egguv2k 【后端研发(上海)】投递链接:https://jobs.toutiao.com/s/eggc5KV 【客户端研发(北京)】投递链接:https://jobs.toutiao.com/s/eggCRQy 【客户端研发(上海)】投递链接:https://jobs.to

  • linux 安卓 node.js

    如果是宝塔面板直接下载版本管理器安装即可   linux: 推荐版本 Node.jsv14.17.3. 2022年11月9日 下载:https://registry.npmmirror.com/binary.html?path=node/   上传解压这里不说多   改名字成nodejs   关键:[下面路劲记得改】 ln-s/xxxxxxx/bin/node/usr/bin/node--将node源文件映射到usr/bin下的node文件 ln-s/xxxxxx/bin/npm/usr/bin/npm 例::::【我解压在test中了,文件名是nodejs] 引入npm ln-fs/test/nodejs/bin/node/usr/bin/node复制 引入node ln-fs/test/nodejs/bin/npm/usr/bin/npm复制 检查: npm-v复制 如果出来版本号即可。   注意!!!!!!!!!test不能删除即:你解压的目录相当于你安装目录,怎么可能删除呢? 这里是测试才放test&nbs

  • 5815. 扣分后的最大得分 dp

    给你一个 mxn 的整数矩阵 points (下标从0 开始)。一开始你的得分为0 ,你想最大化从矩阵中得到的分数。 你的得分方式为:每一行 中选取一个格子,选中坐标为 (r,c) 的格子会给你的总得分增加 points[r][c] 。 然而,相邻行之间被选中的格子如果隔得太远,你会失去一些得分。对于相邻行 r和 r+1 (其中 0<=r<m-1),选中坐标为 (r,c1)和 (r+1,c2) 的格子,你的总得分 减少 abs(c1-c2) 。 请你返回你能得到的最大 得分。 abs(x) 定义为: 如果 x>=0 ,那么值为 x 。 如果 x< 0 ,那么值为-x 。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/p

  • 一个可以再WIN2000及以上操作系统使用的GetTickCount64

    以后尽量用Delphi/C++双代码写. GetTickCount返回值是整数,这样的话最多49天多就会复位重新从0开始.Vista以后提供了GetTickCount64这个函数,但是WindowsXP还是主流.在项目中为了处理这个,自己实现了一套办法.这个GetTickCount和真正的额GetTickCount相比会恒定的相差800毫秒左右.不过这个不影响使用.获取开机时间这800毫秒完全可以忽略.计算时间差的话就和GetTickCount是一样的了. Delphi版XE下编译通过. type _SYSTEM_INFORMATION_CLASS=( SystemBasicInformation, SystemProcessorInformation, SystemPerformanceInformation, SystemTimeOfDayInformation, SystemNotImplemented1, SystemProcessesAndThreadsInformation, SystemCallCounts, SystemConfigurationInfor

  • 深入理解正则表达式环视的概念与用法

    文章大纲: 深入理解正则表达式环视的概念与用法 一、环视的概念 (一)环视概念与匹配过程示例 示例一:简单环视匹配过程 (二)什么是消耗正则的匹配字符? 示例二:一次匹配消耗匹配字符匹配过程 示例三:多次匹配消耗匹配字符匹配过程 二、环视的类型 (一)肯定和否定 (二)顺序和逆序 ·两种类型名称组合 ·四种组合的用法 四种组合正则与环视的摆放位置 1、肯定顺序:(?=exp) (1)常规用法 示例四:肯定顺序环视常规用法 (2)变种用法 示例五:肯定顺序环视变种用法 2、否定顺序:(?!exp) 示例六:否定顺序环视 3、肯定逆序:(?<=exp) 示例七:肯定逆序环视 4、否定逆序:(?<!exp) 示例八:否逆序环视 三、环视的应用 示例九:正则分块组合法-必须包含字母、数字、特殊字符 示例十:正则逐步完善法-排除特定标签p/a/img,匹配html标签 示例十一:正则减除查错法-匹配异常原因查找 总结 复制 在《深入讲解正则表达式高级教程-环视》中已经对环视做了简单的介绍,但是,可能还有一些读者比较迷惑,今天特意以专题的形式,深入探讨一下正则表达式的环视的概念与用法。

  • BarTender安装常见问题集结

    很多人在安装BarTender时,会出现各种安装程序信息警告提示,导致软件无法继续安装下去,那么针对这些Bartender安装问题我们要怎么正确解决呢?下面,小编将BarTender安装失败常见问题,给大家整合了一下,有需要的小伙伴可参考后对症下药。 BarTender未检测到IIS 问题分析: 安装BarTender时,同样选择了配套程序的安装,而PC上没有安装MicrosoftInternet信息服务(IIS),因此,BarTender配套程序WebPrintServer无法安装。 解决办法: 需要下载IIS组件并成功安装。(以Win7系统为例,IIS7.0是Win7自带的) 1、打开计算机控制面板>程序>打开或关闭windows程序; 2、在弹出的“Windows功能”对话框中,找到“Internet信息服务”功能,将所需项勾选上,然后单击确定;等待一会IIS就安装好了。 BarTender安装过程中的.NET问题 原因分析: 这是因为电脑系统内没有安装合适的.NETFramework组件,Bartender是基于.NETFramework3.5环境运行,缺少必

  • test

    test! 转载需注明出处。

  • 使用getElementById获取xml中的指定元素

    Document有一个getElementById的方法,在文档中的解释是:    返回具有带给定值的ID属性的Element。如果不存在此类元素,则此方法返回null。如果多个元素具有带该值的ID属性,返回哪一个元素是不确定的。注:具有名称"ID"或"id"的属性不属于类型ID,除非这样定义。 <root>    <testid="test">        haha    </test></root>上面是一个简单的xml实例,发现在java中通过getElementById("test")的方法,获取到的是一个null值,一查询才知道,正如文档中所说,即使设置id属性,Document也是无法识别的,因为不属于类型ID必须要在xml有相关定义。这时就需要dtd出场了。声明一个简单的内部样式即可解决问题:<!DOCTYPE

相关推荐

推荐阅读