滴水逆向笔记系列-win32总结4-50.创建线程-51.线程控制_CONTEXT结构

第五十课 win32 创建线程

1.进程与线程

  • 程序就是在硬盘里还没跑起来的二进制文件,进程就是已经运行中的程序,一个进程至少有一个线程,比如一个正在举行的活动需要几十个人帮忙干活,进程就是那个活动,线程就是那几十个人
  • 一个线程启动是需要占用一个cpu的
  • 一个新线程也会创建一个新堆栈
  • 进程就是一个4GB容器,线程就是EIP

2.创建线程

HANDLE CreateThread(				
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性 通常为NULL				
  SIZE_T dwStackSize,                       // 参数用于设定线程可以将多少地址空间用于它自己的堆栈				
				        					// 每个线程拥有它自己的堆栈
  LPTHREAD_START_ROUTINE lpStartAddress,    // 参数用于指明想要新线程执行的线程函数的地址				
  LPVOID lpParameter,                       // 线程函数的参数				
				        					// 在线程启动执行时将该参数传递给线程函数
				        					// 既可以是数字,也可以是指向包含其他信息的一个数据结构的指针
  DWORD dwCreationFlags,                    // 0 创建完毕立即调度  CREATE_SUSPENDED创建后挂起				
  LPDWORD lpThreadId                        // 线程ID 				
);				
				        					// 返回值:线程句柄

lpThreadId是个out类型,相当于也是一个返回值,会把线程id写进这里面

关闭句柄

下面代码并没有关闭线程,只是关闭了句柄
image.png

线程句柄与线程ID

  • 线程是由Windows内核在0环负责创建与管理的,正常我们是访问不了0环内核层的,句柄相当于一个令牌,有这个令牌就可以使用线程对象
  • 线程ID是身份证,唯一的,系统进行线程调度的时候要使用的.

线程传参问题


#include <iostream>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	int* p = (int*)lpParameter;
	for (int i = 0; i < 1000; i++)
	{
		Sleep(1000);
		printf("--------%d---\n", *p);
	}
	return 0;
}

void Mytest()
{
	int x = 2;
	HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, &x, 0, NULL);
	::CloseHandle(hThread);
}

int main()
{
	Mytest();
	Sleep(12000);
	return 0;
}

这里为什么运行后打印不出2,而是一个不知道什么地址?
image.png
因为这里是x变量是局部变量,而我们创建了一个新线程去循环打印x,我们新线程会一直打印,但mytest那边可能已经结束了,结束后x变量这个地址也就没了,所以会打印出奇怪的数字
所以我们可以采用全局变量去传参或者强转
image.png

3.倒计时程序(线程问题)

#include <windows.h>
#include "resource.h"
#include <stdio.h>

HWND hEDIT;
VOID Timer()
{
	//获取子窗口数值
	TCHAR szBuffer[10];
	DWORD dwTimer;
	memset(szBuffer, 0, 10);
	GetWindowText(hEDIT, szBuffer, 10);
	//转成整型
	sscanf(szBuffer, "%d", &dwTimer);
	while (dwTimer > 0)
	{
		
		Sleep(1000);
		sprintf(szBuffer, "%d", --dwTimer);

		SetWindowText(hEDIT, szBuffer);
	}
	
}
BOOL CALLBACK DialogProc(
	HWND hwndDlg,  // handle to dialog box			
	UINT uMsg,     // message			
	WPARAM wParam, // first message parameter			
	LPARAM lParam  // second message parameter			
)
{

	switch (uMsg)
	{
	case  WM_INITDIALOG:
	{

		//TCHAR szBuffer[5];

		hEDIT = GetDlgItem(hwndDlg, IDC_EDIT1);

		SetWindowText(hEDIT, TEXT("1000"));

		return TRUE;
	}
	case  WM_CLOSE:

		EndDialog(hwndDlg, 0);

		return TRUE;

	case  WM_COMMAND:

		switch (LOWORD(wParam))
		{
		case   IDC_BUTTON1:
			
			Timer();
			return TRUE;

		}
		break;
	}
	return FALSE;
}

int CALLBACK WinMain(
	_In_  HINSTANCE hInstance,
	_In_  HINSTANCE hPrevInstance,
	_In_  LPSTR lpCmdLine,
	_In_  int nCmdShow
)
{
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
	return 0;
}

为什么按照上面的写法程序会直接卡死?
因为我们把倒计时功能写在了主线程,主线程被用去跑倒计时了,程序也就卡死了,所以这时候我们应该另外创建一个线程去跑
image.png

#include <windows.h>
#include "resource.h"
#include <stdio.h>

HWND hEDIT;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	//获取子窗口数值
	TCHAR szBuffer[10];
	DWORD dwTimer;
	memset(szBuffer, 0, 10);
	GetWindowText(hEDIT, szBuffer, 10);
	//转成整型
	sscanf(szBuffer, "%d", &dwTimer);
	while (dwTimer > 0)
	{
		
		Sleep(1000);
		sprintf(szBuffer, "%d", --dwTimer);

		SetWindowText(hEDIT, szBuffer);
	}
	return 0;
}


BOOL CALLBACK DialogProc(
	HWND hwndDlg,  // handle to dialog box			
	UINT uMsg,     // message			
	WPARAM wParam, // first message parameter			
	LPARAM lParam  // second message parameter			
)
{

	switch (uMsg)
	{
	case  WM_INITDIALOG:
	{

		//TCHAR szBuffer[5];

		hEDIT = GetDlgItem(hwndDlg, IDC_EDIT1);

		SetWindowText(hEDIT, TEXT("1000"));

		return TRUE;
	}

	case  WM_CLOSE:

		EndDialog(hwndDlg, 0);
		return TRUE;

	case  WM_COMMAND:

		switch (LOWORD(wParam))
		{
		case   IDC_BUTTON1:
			
			HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);
			return TRUE;

		}
		break;
	}

	return FALSE;
}

int CALLBACK WinMain(
	_In_  HINSTANCE hInstance,
	_In_  HINSTANCE hPrevInstance,
	_In_  LPSTR lpCmdLine,
	_In_  int nCmdShow
)
{
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
	return 0;
}

Dialog问题

1、提示函数使用不安全是可以在属性设置禁用安全检查
2、字符串类型不匹配的报错可以尝试设置符合模式为否或者修改字符集

需要用到的API函数:

:::info
文本框赋值:
SetWindowText(句柄,数据缓存区);
文本框取值:
GetWindowText(句柄,数据缓冲区,长度);
数字转字符:
sprintf(数据缓冲区,"%d",数字);
字符转数字:
sscanf( szBuffer, "%d", &dwTimer );
获取子窗口:
GetDlgItem(hDlg,IDC_EDIT_TIMER);
:::

第五十一课 win32 线程控制_CONTEXT结构

进程就是4GB,线程就是EIP

1.线程控制函数

挂起线程:

::SuspendThread(hThread);

恢复线程:

::ResumeThread(hThread);

终止线程:

方式一:
::ExitThread(DWORD dwExitCode);
可以看到并不是用句柄控制线程,所以一般用在线程函数内,执行会返回一个值,会直接清除堆栈,没办法去释放资源
image.png
方式二:
线程函数返回(自然死亡)自然退出循环后执行后面的代码(一般是释放资源等操作),然后线程自然结束
image.png
方式三:
::TerminateThread(hThread,2);
::WaitForSingleObject(hThread,INFINITE);
TerminateThread函数属于异步调用,他会另起一个线程去执行结束这个线程,所以代码还是在继续往下走的,所以如果后面有函数需要结束线程后才能运行的话,就有可能出现线程后面结束就执行函数的问题,所以我们需要使用WaitForSingleObject函数在后面堵塞住确定线程被关闭后才继续运行,而且不会清理堆栈
image.png

判断线程是否结束

BOOL GetExitCodeThread(				
    HANDLE hThread,				
    LPDWORD lpExitCode				
);
STILL_ACTIVE 正在运行								
			
参数:				
hThread: 要结束的线程句柄								
dwExitCode: 指定线程的退出代码。可以通过GetExitCodeThread来查看一个线程的退出代码								

2.CONTEXT结构体(线程切换)

每个线程在执行的时候,都会独自占用一个CPU,当系统中的线程数量 > CPU的数量时,就会存在多个线程共用一个CPU的情况。但CPU每次只能运行一个线程,Windows每隔20毫秒会进行线程的切换,那比如线程A执行到地址:0x2345678 eax:1 ecx:2 edx:3 ebx:4...还有eflag标志寄存器中的值等等。。。
此时,线程执行时间到了,被切换到了线程B。。。。当线程B的时间片也到了,再切换会线程A时,系统是如何知道该从哪个地址开始执行呢?被切换前用到的各种寄存器的值该如何恢复呢?

typedef struct _CONTEXT {							
							
    //							
    // The flags values within this flag control the contents of							
    // a CONTEXT record.							
    //							
    // If the context record is used as an input parameter, then							
    // for each portion of the context record controlled by a flag							
    // whose value is set, it is assumed that that portion of the							
    // context record contains valid context. If the context record							
    // is being used to modify a threads context, then only that							
    // portion of the threads context will be modified.							
    //							
    // If the context record is used as an IN OUT parameter to capture							
    // the context of a thread, then only those portions of the thread's							
    // context corresponding to set flags will be returned.							
    //							
    // The context record is never used as an OUT only parameter.							
    //							
							
    DWORD ContextFlags;							
							
    //							
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is							
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT							
    // included in CONTEXT_FULL.							
    //							
							
    DWORD   Dr0;							
    DWORD   Dr1;							
    DWORD   Dr2;							
    DWORD   Dr3;							
    DWORD   Dr6;							
    DWORD   Dr7;							
							
    //							
    // This section is specified/returned if the							
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.							
    //							
							
    FLOATING_SAVE_AREA FloatSave;							
							
    //							
    // This section is specified/returned if the							
    // ContextFlags word contians the flag CONTEXT_SEGMENTS.							
    //							
							
    DWORD   SegGs;							
    DWORD   SegFs;							
    DWORD   SegEs;							
    DWORD   SegDs;							
							
    //							
    // This section is specified/returned if the							
    // ContextFlags word contians the flag CONTEXT_INTEGER.							
    //							
							
    DWORD   Edi;							
    DWORD   Esi;							
    DWORD   Ebx;							
    DWORD   Edx;							
    DWORD   Ecx;							
    DWORD   Eax;							
							
    //							
    // This section is specified/returned if the							
    // ContextFlags word contians the flag CONTEXT_CONTROL.							
    //							
							
    DWORD   Ebp;							
    DWORD   Eip;							
    DWORD   SegCs;              // MUST BE SANITIZED							
    DWORD   EFlags;             // MUST BE SANITIZED							
    DWORD   Esp;							
    DWORD   SegSs;							
							
    //							
    // This section is specified/returned if the ContextFlags word							
    // contains the flag CONTEXT_EXTENDED_REGISTERS.							
    // The format and contexts are processor specific							
    //							
							
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];							
							
} CONTEXT;							

获取线程CONTEXT结构:

//挂起线程
SuspendThread(线程句柄);
CONTEXT context
//设置要获取的类型
context.ContextFlags = CONTEXT_CONTROL;
//获取
BOOL ok = ::GetThreadContext(hThread,&context);
//设置
context.Eip = 0x401000;
SetThreadContext(hThread,&context);

作业

问题:为什么下面代码会出现问题
两个线程各自加到100,结果是200,正常
两个线程各自加到1000,结果是1340,不是2000,有问题
image.png

HWND hEdit ;					
DWORD WINAPI ThreadProc1(LPVOID lpParameter)					
{					
	TCHAR szBuffer[10];				
	DWORD dwIndex = 0;				
	DWORD dwCount;				
					
	while(dwIndex<10)				
	{				
		GetWindowText(hEdit,szBuffer,10);			
		sscanf( szBuffer, "%d", &dwCount );			
		dwCount++;			
		memset(szBuffer,0,10);			
		sprintf(szBuffer,"%d",dwCount);			
		SetWindowText(hEdit,szBuffer);			
		dwIndex++;			
	}				
					
	return 0;				
}					
DWORD WINAPI ThreadProc2(LPVOID lpParameter)					
{					
	TCHAR szBuffer[10];				
	DWORD dwIndex = 0;				
	DWORD dwCount;				
					
					
	while(dwIndex<10)				
	{				
		GetWindowText(hEdit,szBuffer,10);			
		sscanf( szBuffer, "%d", &dwCount );			
		dwCount++;			
		memset(szBuffer,0,10);			
		sprintf(szBuffer,"%d",dwCount);			
		SetWindowText(hEdit,szBuffer);			
		dwIndex++;			
	}				
					
	return 0;				
}					
					
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)					
{					
	BOOL bRet = FALSE;				
					
	switch(uMsg)				
	{				
	case WM_CLOSE:				
		{			
			EndDialog(hDlg,0);		
			break;		
		}			
	case WM_INITDIALOG:				
		{			
			hEdit = GetDlgItem(hDlg,IDC_EDIT1);		
			SetWindowText(hEdit,"0");		
					
			break;		
		}			
	case WM_COMMAND:				
					
		switch (LOWORD (wParam))			
		{			
		case IDC_BUTTON_T1:			
			{		
				HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, 	
					NULL, 0, NULL);
					
				::CloseHandle(hThread1);	
				return TRUE;	
			}		
		case IDC_BUTTON_T2:			
			{		
				HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2, 	
					NULL, 0, NULL);
					
				::CloseHandle(hThread2);	
				return TRUE;	
			}		
		}			
		break ;			
	}				
					
	return bRet;				
}					
					
int APIENTRY WinMain(HINSTANCE hInstance,					
                     HINSTANCE hPrevInstance,					
                     LPSTR     lpCmdLine,					
                     int       nCmdShow)					
{					
 	// TODO: Place code here.				
					
	DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainDlgProc);				
					
	return 0;				
}					

posted @ 2024-03-16 23:21  小新07  阅读(92)  评论(0编辑  收藏  举报