技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 177|回复: 12
收起左侧

【C】只导入ntdll.dll的Hello World

[复制链接]

38

主题

108

帖子

4814

积分

用户组: 管理员

UID
1043
精华
19
威望
223 点
宅币
3704 个
贡献
461 次
宅之契约
0 份
在线时间
759 小时
注册时间
2015-8-15
发表于 2020-5-24 15:17:44 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
一个Hello World程序的关键代码就是printf的调用,而按照posix标准,printf是对stdout输出字符串。按这个道理,在Windows中,可以通过取得stdout的句柄后用WriteFile函数把文本输出到控制台。不过这必然会依赖GetStdHandle这个kernel32.dll的API,因此我们就有必要跳过这个函数去找我们需要的控制台句柄。不过如果你深入读过并探索我写的关于不使用API获取当前进程路径的文章的话(https://www.0xaa55.com/thread-16873-1-1.html),你就会注意到进程参数中保存了这三个控制台句柄。拿到这三个句柄之后,就可以直接使用ntdll.dll的NtReadFile和NtWriteFile函数读写控制台。
鉴于我们直接使用ntdll.dll的函数,并且还要用PEB等结构体的定义,一般的SDK是没有这些定义的,但也不是没有,WRKv1.2的SDK就包含这些。为了方便起见,本文附带的源码包为傻瓜式源码包,里面有需要用到的头文件以及编译器(注:WRKv1.2的编译器版本相近于VC2005)等。只需要将压缩包完整解压出来,双击批处理就可以开始编译。


先说说怎么拿到控制台句柄。这个句柄位于进程参数中,我们可以通过fs gs段拿到进程环境块的地址,进而取得进程参数的结构体指针。拿到进程参数后,直接取出那三个句柄即可,代码如下:
[C] 纯文本查看 复制代码
HANDLE StdIn=NULL;
HANDLE StdOut=NULL;
HANDLE StdErr=NULL;
PPEB Peb=NULL;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters=NULL;

void Init()
{
	Peb=(PPEB)__readtebptr(TEB_PEB_OFFSET);
	ProcessParameters=Peb->ProcessParameters;
	StdIn=ProcessParameters->StandardInput;
	StdOut=ProcessParameters->StandardOutput;
	StdErr=ProcessParameters->StandardError;
}

StdIn, StdOut, StdErr三个变量赋值以后,我们就可以读写控制台了。
接下来我们就需要实现printf了,这个实现其实很简单,先把完整的字符串用sprintf之类的函数打印出来,再用NtWriteFile输出到控制台上。
ntdll.dll中有个导出函数叫_vsnprintf,正适合我们格式化字符串,尤其是它还能防止栈溢出。通常来说,512字节够我们使了,代码如下:
[C] 纯文本查看 复制代码
#define PRINT_BUFFER_SIZE	512

int __cdecl ntprintf(const char* format,...)
{
	IO_STATUS_BLOCK iosb={0};
	int len;
	char buff[PRINT_BUFFER_SIZE];
	va_list args;
	va_start(args,format);
	len=_vsnprintf(buff,PRINT_BUFFER_SIZE,format,args);
	if(len>0)NtWriteFile(StdOut,NULL,NULL,NULL,&iosb,buff,len,NULL,NULL);
	va_end(args);
	return len;
}

如果嫌不够就加大PRINT_BUFFER_SIZE这个值。
那么程序的入口函数就需要先Init(),再ntprintf(),代码如下:
[C] 纯文本查看 复制代码
void Main()
{
	Init();
	ntprintf("Hello Native Console!\n");
}

将其编译并运行,效果如下:
ntcmd_test.JPG
拖进IDA,可以发现已经导入表里只有ntdll.dll:
ntcmd.PNG
不过即便是只导入ntdll.dll,系统依然会加载kernel32.dll。由于枚举模块列表可以通过直接遍历LDR双向链表来实现,不需要API,代码如下:
[C] 纯文本查看 复制代码
void PrintLdrList()
{
	PLDR_DATA_TABLE_ENTRY pLdr=(PLDR_DATA_TABLE_ENTRY)PebLdrData->InLoadOrderModuleList.Flink;
	PLDR_DATA_TABLE_ENTRY tLdr=pLdr;
	do
	{
		ntprintf("Base: 0x%p Size: 0x%08X Name: %wZ\t Path: %wZ\n",pLdr->DllBase,pLdr->SizeOfImage,&pLdr->BaseDllName,&pLdr->FullDllName);
		pLdr=(PLDR_DATA_TABLE_ENTRY)pLdr->InLoadOrderLinks.Flink;
	}while(pLdr!=tLdr);
}

然后修改Init和Main函数:
[C] 纯文本查看 复制代码
PPEB_LDR_DATA PebLdrData=NULL;

void Init()
{
	Peb=(PPEB)__readtebptr(TEB_PEB_OFFSET);
	ProcessParameters=Peb->ProcessParameters;
	PebLdrData=Peb->Ldr;
	StdIn=ProcessParameters->StandardInput;
	StdOut=ProcessParameters->StandardOutput;
	StdErr=ProcessParameters->StandardError;
}

void Main()
{
	Init();
	PrintLdrList();
}

编译并运行:
ntcmdldr.JPG
似乎就可以得出结论:即便程序的整个导入表树上没有kernel32.dll,系统仍然会加载kernel32.dll。
最后再实现个阻塞控制台吧,代码如下:
[C] 纯文本查看 复制代码
void Pause()
{
	IO_STATUS_BLOCK iosb={0};
	char k;
	NtWriteFile(StdOut,NULL,NULL,NULL,&iosb,"Press Enter key to continue...",30,NULL,NULL);
	NtReadFile(StdIn,NULL,NULL,NULL,&iosb,&k,1,NULL,NULL);
}

在入口函数的结尾处调用刚写的Pause函数,其中NtReadFile的调用会阻塞住控制台,双击运行能看到控制台,结果如下:
ntcmdpause.JPG


结语
由于ntdll.dll里有很多crt函数(比如qsort,sin,_wcsnicmp),很多时候在写C程序时可以绕过msvcrt.dll,甚至是kernel32.dll。比如NtAllocateVirtualMemory替代VirtualAlloc(Ex),RtlAllocateHeap替代HeapAlloc等等。总之,脱离msvcrt甚至kernel32都是可行的,只要愿意挖掘Windows的特色即可。
源码包里包含了WRKv1.2的编译器和SDK头文件,以及WDK7600中2K3版的ntdll.lib。其中还包括了四个批处理文件分别用于32位和64位的Debug和Release编译(注:chk即Checked,也就是Debug编译;fre即Free,也就是Release编译),以及一个用于清理所有已编译文件的批处理文件。双击批处理文件即可实现编译。
很多人调用ntdll.dll的函数时都会用GetProcAddress去取函数地址,但实则大可不必,链接器中带上ntdll.lib即可。

NtdllConsole.zip

7.08 MB, 下载次数: 4

下载源码不回帖是一种很欠扁的行为

评分

参与人数 2威望 +20 宅币 +60 贡献 +20 收起 理由
watermelon + 10 + 30 + 10 学习了
0xAA55 + 10 + 30 + 10 干货

查看全部评分

flowers for Broken spirits - a woman turned into stake will hold the world in the basin of fire.
回复

使用道具 举报

0

主题

20

帖子

107

积分

用户组: 小·技术宅

UID
4725
精华
0
威望
2 点
宅币
83 个
贡献
0 次
宅之契约
0 份
在线时间
16 小时
注册时间
2019-3-2
发表于 2020-5-24 19:39:42 | 显示全部楼层
俺自己捏导入库惹,用到啥就加啥

28

主题

185

帖子

2076

积分

用户组: 版主

UID
1821
精华
6
威望
67 点
宅币
1612 个
贡献
115 次
宅之契约
0 份
在线时间
346 小时
注册时间
2016-7-12
发表于 2020-5-24 22:34:22 | 显示全部楼层
可以自己申请控制台 打开 conin$ conout$获取 stdin 和 stdout句柄

36

主题

161

帖子

7373

积分

用户组: 管理员

UID
77
精华
11
威望
125 点
宅币
6765 个
贡献
142 次
宅之契约
0 份
在线时间
121 小时
注册时间
2014-2-22
发表于 2020-5-25 06:42:20 | 显示全部楼层
没看懂纠结是否加载KERNEL32有啥意义。。。
不过简单WIN32小程序用WDK7编译是很好的,它会直接导入MSVCRT.DLL,编译出来的EXE/DLL非常小。

11

主题

48

帖子

201

积分

用户组: 中·技术宅

UID
5832
精华
0
威望
2 点
宅币
149 个
贡献
0 次
宅之契约
0 份
在线时间
32 小时
注册时间
2020-4-15
发表于 2020-5-25 10:03:31 | 显示全部楼层
学习一下了。

1060

主题

2443

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
221
威望
348 点
宅币
19518 个
贡献
40309 次
宅之契约
0 份
在线时间
1846 小时
注册时间
2014-1-26
发表于 2020-5-26 14:56:09 | 显示全部楼层
美俪女神 发表于 2020-5-25 06:42
没看懂纠结是否加载KERNEL32有啥意义。。。
不过简单WIN32小程序用WDK7编译是很好的,它会直接导入MSVCRT.D ...

很多古代的程序对kernel32.dll的版本有要求,比如有的就只能加载Win95和WinME的kernel32.dll。

虽说我们现在可以不需要担心因为依赖了kernel32.dll而出现对系统版本的过分要求,但如果不依赖,似乎更容易在那些只会玩eXeScope的小朋友圈子里成为爸爸。

以及有些环境(具体记不得了)你不一定有Kernel32.dll可用

0

主题

18

帖子

54

积分

用户组: 小·技术宅

UID
5782
精华
0
威望
6 点
宅币
24 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2020-4-4
发表于 2020-6-2 18:12:02 | 显示全部楼层
顶一下 原来这些函数r3 也可以用

0

主题

20

帖子

107

积分

用户组: 小·技术宅

UID
4725
精华
0
威望
2 点
宅币
83 个
贡献
0 次
宅之契约
0 份
在线时间
16 小时
注册时间
2019-3-2
发表于 2020-6-18 16:10:48 | 显示全部楼层
本帖最后由 小冰 于 2020-6-18 16:15 编辑

我正确获取到了STDOUT的句柄, 使用下面的方式
[C] 纯文本查看 复制代码
#define STD_OUTPUT_HANDLE_INDEX 3UL
HANDLE hStdOut = ((PPEB)RtlGetCurrentPeb())->ProcessParameters->Reserved2[STD_OUTPUT_HANDLE_INDEX];


hStdOut的值和调用GetStdHandle(STD_OUTPUT_HANDLE)返回的值是一个值。

但问题就出在输出
[C] 纯文本查看 复制代码
PCSTR str = "Hello World!";
NTSTATUS status = NtWriteFile(hStdOut, NULL, NULL, NULL, &iosb, str, strlen(str), NULL, NULL);


status的值是0xc0000024, 经查MSDN列出的NTSTATUS VALUE, 得知是STATUS_OBJECT_TYPE_MISMATCH
在32/64位程序中的执行结果都如此。

但能使用WriteFile来输出。
[C] 纯文本查看 复制代码
WriteFile(hStdOut, str, strlen(str), NULL, NULL);


但是什么原因导致NtWriteFile不能输出呢?
您的这个例子我下载到我的电脑中编译执行也输出不了。

下面是我所有的代码:

[C] 纯文本查看 复制代码
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <Windows.h>
#include <winternl.h>

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

NTSYSAPI PVOID NTAPI RtlGetCurrentPeb();

#define STD_OUTPUT_HANDLE_INDEX 3UL

NTSYSAPI NTSTATUS NTAPI NtWriteFile(
  HANDLE           FileHandle,
  HANDLE           Event,
  PIO_APC_ROUTINE  ApcRoutine,
  PVOID            ApcContext,
  PIO_STATUS_BLOCK IoStatusBlock,
  PVOID            Buffer,
  ULONG            Length,
  PLARGE_INTEGER   ByteOffset,
  PULONG           Key
);

int main(void) {
  HANDLE hStdOut = ((PPEB)RtlGetCurrentPeb())->ProcessParameters->Reserved2[STD_OUTPUT_HANDLE_INDEX];
  IO_STATUS_BLOCK iosb = {NULL};
  PCSTR str = "Hello World!";
  NTSTATUS status = NtWriteFile(hStdOut, NULL, NULL, NULL, &iosb, str, strlen(str), NULL, NULL);
  //WriteFile(hStdOut, str, strlen(str), NULL, NULL);
  return 0;
}


我的电脑是windows 7 64位。

28

主题

185

帖子

2076

积分

用户组: 版主

UID
1821
精华
6
威望
67 点
宅币
1612 个
贡献
115 次
宅之契约
0 份
在线时间
346 小时
注册时间
2016-7-12
发表于 2020-6-18 19:48:25 | 显示全部楼层
小冰 发表于 2020-6-18 16:10
我正确获取到了STDOUT的句柄, 使用下面的方式
[mw_shl_code=c,true]#define STD_OUTPUT_HANDLE_INDEX 3UL
H ...


因为控制台没经过ntwritefile 走应该是写指针 然后 ntRequestWaitReplyPort来着

0

主题

20

帖子

107

积分

用户组: 小·技术宅

UID
4725
精华
0
威望
2 点
宅币
83 个
贡献
0 次
宅之契约
0 份
在线时间
16 小时
注册时间
2019-3-2
发表于 2020-6-18 22:03:06 | 显示全部楼层
Ayala 发表于 2020-6-18 19:48
因为控制台没经过ntwritefile 走应该是写指针 然后 ntRequestWaitReplyPort来着

用IDA看了一下WriteFile, 它调用了WriteConsole, 然后又调用了WriteConsoleInternal, 最后调用CsrClientCallServer给CSRSS发消息。不过我好奇,为啥在唐的机器上可以输出,是只有在win10上才有效?我手头上没有win10的机器。

28

主题

185

帖子

2076

积分

用户组: 版主

UID
1821
精华
6
威望
67 点
宅币
1612 个
贡献
115 次
宅之契约
0 份
在线时间
346 小时
注册时间
2016-7-12
发表于 2020-6-19 18:48:29 | 显示全部楼层
小冰 发表于 2020-6-18 22:03
用IDA看了一下WriteFile, 它调用了WriteConsole, 然后又调用了WriteConsoleInternal, 最后调用CsrClientC ...

win7上不同版本也不一样

38

主题

108

帖子

4814

积分

用户组: 管理员

UID
1043
精华
19
威望
223 点
宅币
3704 个
贡献
461 次
宅之契约
0 份
在线时间
759 小时
注册时间
2015-8-15
 楼主| 发表于 2020-6-20 10:07:35 | 显示全部楼层
小冰 发表于 2020-6-18 22:03
用IDA看了一下WriteFile, 它调用了WriteConsole, 然后又调用了WriteConsoleInternal, 最后调用CsrClientC ...

我在win7上倒是测试成功的,只不过把所有能打的补丁都给打了。至于是什么原因我其实并没有想过,以后再研究,或者你自己研究吧。
flowers for Broken spirits - a woman turned into stake will hold the world in the basin of fire.

0

主题

20

帖子

107

积分

用户组: 小·技术宅

UID
4725
精华
0
威望
2 点
宅币
83 个
贡献
0 次
宅之契约
0 份
在线时间
16 小时
注册时间
2019-3-2
发表于 2020-6-23 11:12:01 | 显示全部楼层
tangptr@126.com 发表于 2020-6-20 10:07
我在win7上倒是测试成功的,只不过把所有能打的补丁都给打了。至于是什么原因我其实并没有想过,以后再研 ...

好的,谢谢你。

本版积分规则

QQ|申请友链||Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图  

GMT+8, 2020-7-16 11:47 , Processed in 0.117902 second(s), 34 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表