首页
论坛
课程
招聘
[原创]通过NtContinue修改3环程序执行流程
2021-9-25 05:27 27568

[原创]通过NtContinue修改3环程序执行流程

2021-9-25 05:27
27568

挺简单的一个小玩意,可以通过ContinueCall去执行想要执行的函数,执行结束后会返回到指定的SavedContext。逆向分析的时候对ContinueCall或者NtContinue进行F8步过的话直接会跟丢,或者程序会挂掉。(这个思路没见有人说过啊,我估计这也是很鸡肋的东西,对反调试的帮助应该不算很大,也就发着玩玩了,大火看个乐)

不仅可以用GetSavedContext获取context,也可以自定义构造,那样的话就灵活更多了。

#include "ContinueCall.h"

/*内部函数*/

/*
* ContinueCallRoutine
* 功能 - 调用指定的函数并返回到指定的context
* 参数 - SavedContext:返回的context
*	   - TargetProc:指定函数
*	   - Params:参数指针
*	   - ParamSize:参数大小
*	   - Result:返回值缓冲区
*/
__declspec(naked) VOID ContinueCallRoutine(PCONTEXT SavedContext, PROC TargetProc, PVOID Params, SIZE_T ParamSize, PDWORD Result) {
	__asm {
		push ebp
		mov ebp, esp

		sub esp, ParamSize
		mov edi, esp;
		mov esi, Params;
		mov ecx, ParamSize;
		rep movsb;
		mov eax, TargetProc;
		call eax;
		mov ebx, Result;
		mov[ebx], eax;
	}

	NtContinue(SavedContext, TRUE);
}

/*
* GetEsp
* 功能 - 返回当前函数执行中的ESP
*/
__declspec(naked)ULONG GetEsp() {
	__asm {
		mov eax, esp;
		add eax, 0x4;
		ret
	}
}

/*
* GetEip
* 功能 - 返回此函数的返回值
*/
__declspec(naked)ULONG GetEip() {
	__asm {
		mov eax, [esp];
		ret
	}
}

/*导出函数*/

/*
* NtContinue
* 功能 - 执行系统调用NtContinue
*/
ULONG WINAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert) {
	static const PVOID addr = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtContinue");

	if (!addr) {
		return FALSE;
	}
	__asm {
		mov eax, [addr]
		push[TestAlert]
		push[ContextRecord]
		call eax
	}
	return TRUE;
}

/*
* GetSavedContext
* 功能 - 获取此函数返回时的对应Context
* 参数 - SavedContext:保存获取的Context
*/
__declspec(naked) VOID WINAPI GetSavedContext(PCONTEXT SavedContext) {
	__asm {
		push ebp
		mov ebp, esp
	}

	__asm {
		push esi
		push edi
	}

	SavedContext->ContextFlags = CONTEXT_ALL;
	if (!GetThreadContext(GetCurrentThread(), SavedContext)) {
		DEBUG_PRINT("GetThreadContext Failed\n");
		goto END;
	}
	__asm {
		pop edi
		pop esi
		mov eax, SavedContext
		mov[eax + CONTEXT.Edi], edi;
		mov[eax + CONTEXT.Esi], esi;
		mov ebx, [ebp];
		mov[eax + CONTEXT.Ebp], ebx;
		mov ebx, [ebp + 0x4];
		mov[eax + CONTEXT.Eip], ebx;
		mov ebx, ebp;
		add ebx, 0xc;
		mov[eax + CONTEXT.Esp], ebx;
	}

END:
	__asm {
		mov esp, ebp
		pop ebp
		ret 0x4
	}
}

/*
* ContinueCall
* 功能 - 执行目标函数,执行结束后会返回到SavedContext
* 参数 - 其余:与ContinueCallRoutine相关
*	   - pFirst:此函数是否是第一次执行
*/
VOID WINAPI ContinueCall(PCONTEXT SavedContext, PROC TargetProc, PVOID Params, DWORD ParamNum, PDWORD Result, PBOOLEAN pFirst) {
	CONTEXT NewContext;
	CONTEXT Context;
	PCONTEXT pSavedContext;

	if (!SavedContext) {
		GetSavedContext(&Context);
		pSavedContext = &Context;
	}
	else {
		pSavedContext = SavedContext;
	}
	if (!*pFirst) {
		goto END;
	}
	*pFirst = FALSE;

	memcpy(&NewContext, pSavedContext, sizeof(CONTEXT));
	NewContext.Esp = (DWORD)GetEsp() - 0x400;
	((PDWORD)NewContext.Esp)[0] = 0;

	((PDWORD)NewContext.Esp)[1] = (DWORD)pSavedContext;
	((PDWORD)NewContext.Esp)[2] = (DWORD)TargetProc;
	((PDWORD)NewContext.Esp)[3] = (DWORD)Params;
	((PDWORD)NewContext.Esp)[4] = (DWORD)ParamNum * sizeof(DWORD);
	((PDWORD)NewContext.Esp)[5] = (DWORD)Result;
	NewContext.Eip = (DWORD)ContinueCallRoutine;

	NtContinue(&NewContext, TRUE);

END:
	return;
}

测试例子1

int add(int a, int b) {
	return a + b;
}

int main()
{
	int a[2] = { 1,3 };
	int result;
	BOOLEAN bFirst = TRUE;
	CONTEXT context;
	
	GetSavedContext(&context);
	printf("saved context end\n");
	ContinueCall(&context, (PROC)add, a, 2, (PDWORD)&result, &bFirst);

	printf("%d + %d = %d\n", a[0], a[1], result);
END:
	system("pause");

	return 0;
}

/*
输出结果:
saved context end
saved context end
1 + 3 = 4
*/

测试例子2

int a[2] = { 1,3 };
int result;
CONTEXT context;
BOOLEAN bFirst = TRUE;

int add(int a, int b) {
	return a + b;
}

void func1() {
	printf("func1 start\n");
	ContinueCall(&context, (PROC)add, a, 2, (PDWORD)&result, &bFirst);
	printf("func1 end\n");
}

void func2() {
	printf("func2 start\n");
	GetSavedContext(&context);
	func1();
	printf("func2 end\n");
}

int main()
{
	func2();
	printf("%d + %d = %d\n", a[0], a[1], result);
	system("pause");

	return 0;
}

/*
输出结果:
func2 start
func1 start
func1 start
func1 end
func2 end
1 + 3 = 4
*/

测试例子3

int a[2] = { 1,3 };
int result;
CONTEXT context;
BOOLEAN bFirst = TRUE;

int add(int a, int b) {
	return a + b;
}

void func1() {
	printf("func1 start\n");
	ContinueCall(&context, (PROC)add, a, 2, (PDWORD)&result, &bFirst);
	printf("func1 end\n");
}

void func2() {
	printf("func2 start\n");
	func1();
	printf("func2 end\n");
}

int main()
{
	GetSavedContext(&context);
	if (bFirst) {
		func2();
	}
	printf("%d + %d = %d\n", a[0], a[1], result);
	system("pause");

	return 0;
}

/*
输出结果:
func2 start
func1 start
1 + 3 = 4
*/



【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

上传的附件:
收藏
点赞2
打赏
分享
最新回复 (11)
雪    币: 198
活跃值: 活跃值 (1241)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yy虫子yy 活跃值 2021-9-25 11:43
2
0
NtContinue相当于jmp,F8就会直接跟丢
用NtContinue来实现call调用,调用完还要通过CONTEXT返回
效率会比较低,不过对于程序接口防止第三方调用稍微有点作用
雪    币: 5904
活跃值: 活跃值 (1475)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 活跃值 1 2021-9-25 13:25
3
0
暗桩这种东西,单独一种或许很简单,每样都来一点就很麻烦了。 
雪    币: 218
活跃值: 活跃值 (772)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2021-9-27 16:37
4
0
mark
雪    币: 6802
活跃值: 活跃值 (2721)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
海风月影 活跃值 22 2021-9-30 11:50
5
1
类似:setjmp/longjmp
雪    币: 950
活跃值: 活跃值 (549)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Caim Astraea 活跃值 2021-10-22 21:27
6
0
yy虫子yy NtContinue相当于jmp,F8就会直接跟丢 用NtContinue来实现call调用,调用完还要通过CONTEXT返回 效率会比较低,不过对于程序接口防止第三方调用稍微有点作用
F7也会跟丢,和jmp还是有点区别。效率方面,如果把CONTEXT返回变成三环过程,应该就和SEH改流程之类的没什么分别
雪    币: 179
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_hgrbqfun 活跃值 2021-10-22 22:00
7
0
不是很懂,调试器为什么会跟丢,为什么jmp不会丢,好像协程库也不会丢,为什么NtContinue会丢
最后于 2021-10-22 22:01 被mb_hgrbqfun编辑 ,原因:
雪    币: 3003
活跃值: 活跃值 (838)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 活跃值 2021-10-23 08:27
8
0
cpu有个单步执行模式,我记得是flag标志位修改,这样肯定不会跟丢,调试器F7的原理是把下一步要执行的汇编处改成CC断点,都不走那步流程下断也没用啊
雪    币: 198
活跃值: 活跃值 (1241)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yy虫子yy 活跃值 2021-10-23 09:22
9
0
Caim Astraea F7也会跟丢,和jmp还是有点区别。效率方面,如果把CONTEXT返回变成三环过程,应该就和SEH改流程之类的没什么分别
这个好像是不返回的吧,相当于是从Ring0里面jmp到目标CONTEXT,F7和F8也就都会跟丢
雪    币: 198
活跃值: 活跃值 (1241)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yy虫子yy 活跃值 2021-10-23 09:28
10
0
qj111111 cpu有个单步执行模式,我记得是flag标志位修改,这样肯定不会跟丢,调试器F7的原理是把下一步要执行的汇编处改成CC断点,都不走那步流程下断也没用啊
这个要看情况,因为有的系统调用进入Ring0后并不会返回,就算cpu有单步标志,F7都会跟丢
雪    币: 3003
活跃值: 活跃值 (838)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 活跃值 2021-11-16 18:27
11
0
好吧这个确实没考虑到
雪    币: 2649
活跃值: 活跃值 (991)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 活跃值 2021-11-20 21:46
12
0

。。。

最后于 2021-11-20 21:52 被木志本柯编辑 ,原因:
游客
登录 | 注册 方可回帖
返回