printf()的Windows实现探秘及如何在控制台显示24位真彩色 (转)
来源:互联网 发布:红楼梦细节分析知乎 编辑:程序博客网 时间:2024/05/17 03:10
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])
文章作者:Styxal@THSS, [email]nepluno@yahoo.cn[/email]
prefix:发篇关于控制台的逆向水一下,没啥技术含量,可能内容火星了,大牛飘过即可~
每个学C语言的人都不会不知道printf()这个函数,第一个程序经常是printf("Hello World!");
今天试着在Windows下Debug了一下printf这个函数的内部实现,才发现竟然如此复杂。
首先关于字符串解析的就跳过去……
?!
教材上都说printf()实现的主要部分就是字符串解析,甚至还有各种题让自己模拟printf的解析,那么跳过去还有什么呢?
于是进入最最纠结的部分。
首先要说的是,参与printf的并不仅仅是是我们的程序(比如叫hello.exe),那个黑窗口是一个叫cmd.exe的命令行程序,问题在于,后者也不是一个有自主能力的程序,是靠winsrv.dll管理的,而这个winsrv.dll挂靠在csrss.exe这个进程里。
下面列举具体步骤:
1、当printf被调用后,首先会经过C函数库的处理,也就是字符串解析,得到要输出的字符串(这个程设老师总讲,跳过)
2、调用WriteChars(),它会调用WriteFile()这个API,所谓的File其实是控制台输出缓冲区的句柄。WriteFile判断句柄类型(如是文件句柄将调用ntdll.dll中的NtWriteFile函数),因为这里是控制台句柄所以将调用WriteConsoleA函数。
3、WriteConsoleA函数将调用ntdll.dll中的csrClientCallServer函数,这个函数的目的是通知csrss.exe要输出字符了。
4、csrClientCallServer最终会调用NtRequestWaitReplyPort,此时系统进入内核态,内核会通知csrss.exe
5、csrss.exe中一个叫CsrApiRequestThread的线程已经用一个叫NtReplyWaitReceivePort(这个函数被调用后,线程就会被阻断,直到上面的NtRequestWaitReplyPort被调用才继续执行)的函数等很久了,此时接到指令欣喜若狂的csrss.exe就会根据发来的内容经过一番纠结判断是要输出字符,于是找到自己的winsrv.dll
6、winsrv.dll有个叫SrvWriteConsole的函数被调用,这个函数会对发来的信息进行一番安全检查、处理,然后给一个叫DoSrvWriteConsole的函数
7、这个DoSrvWriteConsole会做一些单字节、多字节等编码的检查、转换,然后调用FE_DoSrvWriteConsole函数
8、然后调用FE_DoWriteConsole,这个函数调用FE_WriteChars。
9、FE_WriteChars会进行两步工作
(1)更新控制台缓冲区,这个使用叫做FE_StreamWriteToScreenBuffer和BisectWrite函数完成的
(2)更新屏幕缓冲区,这个使用叫做FE_WriteToScreen和FE_WriteRegionToScreen函数完成的,主要过程包括将文本用一个叫FE_PolyTextOutCandidate的函数放到待输出队列里,然后等这一批文本都放进去后调用GdiFlush函数刷到屏幕上。
10、终于快大功告成了,SrvWriteConsole返回,csrss.exe这个时候用一个叫NtReplyPort的函数告诉我们的Hello.exe:嗯,我写完了,于是我们的Hello.exe继续运行,然后你就会看到屏幕上出现可爱的:Hello World!
ps:其实所有对控制台的操作都是类似的,包括输入输出改颜色。
比如改前景颜色,上面从csrss开始不一样,分别是调用:
winsrv.dll - SrvSetConsoleTextAttribute
调用:
winsrv.dll - SetScreenColors
调用:
GDI32.dll - SetTextColor
完成的,因此如果想让控制台显示24位真彩色什么的(默认的话控制台只能显示16色,嗯……你问正常情况下怎么设置输出颜色?嗯,其实可以用SetConsoleTextAttribute来设置,不过真的只有16色……),就可以钩SetTextColor,然后每次调SetConsoleTextAttribute之前把钩子函数中的颜色换成想要的颜色,这样就可以自己用SetTextColor来随画随设了……经过逆向可以得知,csrss是这样拿到DC的:
mov eax,dword ptr fs:[00000018h] //得到当前线程的TEB(线程环境块)地址
然后通过:Teb->CsrClientThread->Process->ServerDllPerProcessData->HandleTable[控制台句柄 / 4].ScreenBuffer->Console->hDC
得到。不过因为ServerDllPerProcessData是在SetConsoleTextAttribute调用时系统随调随设的,而且用完就清,所以想要跟过去拿DC很难,而且拿了也很麻烦,过系统的检查还得pass一堆桩。
而控制台还提供一种整个缓冲区刷新颜色的方法WriteConsoleOutputAttribute,逆向后发现它是这样的:
1b010282 00808000 0059fe4c GDI32!SetTextColor
00000001 0059fcd0 0059ff78 winsrv!FE_WriteRegionToScreen+0x5f1
03693cd0 0059fe4c 0059ff78 winsrv!FE_WriteToScreen+0xc9
00000050 00500000 00000000 winsrv!FE_WriteOutputString+0x892
03693cd0 00184004 00000000 winsrv!WriteOutputString+0x28
0016b5a0 0059ffd8 00000005 winsrv!SrvWriteConsoleOutputString+0xad
00000094 00000000 00000000 CSRSRV!CsrApiRequestThread+0x431
嗯,很好,也是SetTextColor,于是我们要对两个都做Hook也是很简单的了。注意这个WriteConsoleOutputAttribute是每遇到一个不相同颜色的地方就设一次,所以如果控制台每个相邻字的颜色都可能要不同的话,传进去的缓冲区也要相邻颜色都不同(其实也很简单,从1-15反复传就可以了)。
下面呢,比如说可以很暴力地找一个Windows基本不用的函数(比如下面是gdiplayscript)替掉,然后把SetTextColor钩到替换的地方去……
示例代码(XP下通过):[code]
#include <Windows.h>
#include <stdio.h>
#include <math.h>
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
HANDLE hProcess;
BYTE HookCode[6] = {0x68, 0x00, 0x00, 0x00, 0x00, 0xc3};
BYTE oldCode[6];
BYTE HookFunc[22] = {0xc3, 0x50, 0xb8, 0x00, 0x00, 0x80, 0x00, 0x89, 0x44, 0x24, 0x0c, 0x58, 0x55, 0x8b, 0xec, 0x51, 0x68, 0x78, 0x56, 0x34, 0x12, 0xc3};
/*
__asm {
ret
push eax
mov eax, 0x800000
mov [esp+0x0C], eax
pop eax
push ebp
mov ebp, esp
push ecx
push 0x12345678
ret
}
}
*/
BYTE oldFunc[22];
ULONG HookProcLong = 22;
ULONG addrSetTextColor;
ULONG addrHookProc;
BOOL SetPrivilege(
HANDLE hToken, // token handle
LPCTSTR Privilege, // Privilege to enable/disable
BOOL bEnablePrivilege // TRUE to enable. FALSE to disable
)
{
TOKEN_PRIVILEGES tp = { 0 };
LUID luid;
DWORD cb=sizeof(TOKEN_PRIVILEGES);
if(!LookupPrivilegeValue( NULL, Privilege, &luid ))
return FALSE;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if(bEnablePrivilege)
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
} else
{
tp.Privileges[0].Attributes = 0;
}
AdjustTokenPrivileges( hToken, FALSE, &tp, cb, NULL, NULL );
if (GetLastError() != ERROR_SUCCESS)
return FALSE;
return TRUE;
}
void SetHook()
{
HANDLE hToken;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return;
if(!SetPrivilege(hToken, SE_DEBUG_NAME, TRUE))
{
CloseHandle(hToken);
return;
}
HMODULE hm = LoadLibrary(L"Gdi32.dll");
addrSetTextColor = (ULONG)GetProcAddress(hm, "SetTextColor");
addrHookProc = (ULONG)GetProcAddress(hm, "GdiPlayScript");
memcpy(oldFunc, (PVOID)addrHookProc, HookProcLong);
memcpy(oldCode, (PVOID)addrSetTextColor, 6);
ULONG fix = (ULONG)HookFunc + 17;
*((ULONG*)fix) = addrSetTextColor + 6;
*((ULONG*)(HookCode + 1)) = (addrHookProc + 1);
typedef LONG (WINAPI *CSRGETPROCESSID)();
CSRGETPROCESSID CsrGetProcessId = (CSRGETPROCESSID)GetProcAddress(GetModuleHandleA("ntdll"), "CsrGetProcessId");
DWORD pid = CsrGetProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
ULONG dwRet;
DWORD OldProtect;
VirtualProtectEx(hProcess, (LPVOID)addrHookProc, 4096, PAGE_EXECUTE_READWRITE, &OldProtect);
BOOL isOK = WriteProcessMemory(hProcess, (LPVOID)addrHookProc, (LPCVOID)HookFunc, HookProcLong, &dwRet);
if(isOK)
{
VirtualProtectEx(hProcess, (LPVOID)addrSetTextColor, 4096, PAGE_EXECUTE_READWRITE, &OldProtect);
BOOL isOK = WriteProcessMemory(hProcess, (LPVOID)addrSetTextColor, (LPCVOID)HookCode, 6, &dwRet);
}
}
void SetColor(COLORREF* color)
{
DWORD dwRet;
WriteProcessMemory(hProcess, (LPVOID)(addrHookProc + 3), color, 4, &dwRet);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 1);
}
void Unhook()
{
DWORD dwRet;
WriteProcessMemory(hProcess, (LPVOID)addrHookProc, oldFunc, HookProcLong, &dwRet);
WriteProcessMemory(hProcess, (LPVOID)addrSetTextColor, oldCode, 6, &dwRet);
CloseHandle(hProcess);
}
void test()
{
COLORREF c;
SetHook();
for(int i = 0; i < 25; i++)
for(int j = 0; j < 80; j++) {
float x = float(i) / 80.0f * 6.28f;
float y = float(j) / 25.0f * 3.14f;
float r = (sinf(y) * cosf(x) + 1.0f) * 127.5f;
float g = (sinf(y) * sinf(x) + 1.0f) * 127.5f;
float b = (cosf(y) + 1.0f) * 127.5f;
c = RGB((int)(r), (int)(g), (int)(b));
SetColor(&c);
printf("/x1");
}
Unhook();
}
void main()
{
test();
}
- printf()的Windows实现探秘及如何在控制台显示24位真彩色 (转)
- printf()的Windows实现探秘及如何在控制台显示24位真彩色
- 24位真彩色图像转换为16位高彩色图像的实现方法及效果改进
- 实现实现24位真彩色工具条
- C# 图像 24位转32位(真彩色)
- 24位真彩色位图转换成8位灰度图片的代码实现
- 24位真彩色位图和8位灰度位图相互转换(C语言实现)
- 32位真彩色与24位真彩色区别
- 32位真彩色与24位真彩色区别
- 24、32位真彩色
- 24位真彩色工具栏
- 24位真彩色图像转8位灰度图像
- Linux下彩色进度条的实现(printf的格式化输出成彩色的,在linux下才有效)
- 用Delphi实现24位真彩色图标
- 256色转灰度+24位真彩色转灰度
- 轻松制作24位真彩色工具栏 (转)
- 24位真彩色图高斯模糊(VC++)
- 实现真彩色的按钮
- 读取xml学习:使用jdom读取xml文件
- java 串口编程
- 经典,好用的firefox插件
- Linux服务器中的TCP连接状态详细解释
- 用10.04 alternate CD定制安装Ubuntu方法
- printf()的Windows实现探秘及如何在控制台显示24位真彩色 (转)
- 常用MAC软件推荐
- C#窗体的一些总结
- mysql 单表查询
- 托管代码
- 001--Python入门学习
- perl 编码转换
- 关于报表中的跳转url的设定
- 各类型变量与零值的比较