2020-06-16


title: Smali 格式解析
date: 2020-6-15 15:07:01


Smali 格式主要分为3个部分

头部定义

1
2
3
.class
.suoer
.source

域定义

1
2
3
4
- .field public
- .field static
- .field private
- etc

函数定义

1
2
3
4
5
.method
instruction
instruction
instruction
.end method

Smali类型与Java类型

Javatype Smali
short S
long J
int[] [I
int I
float F
double D
char C
byte B
Void V
Object Ljava/lang/Object;
OBbject[][] [[Ljava/lang/Object

Android 调试方法

调试方法

方法一(老)

  • 1.java -jar apktool.apk d -d [path] -o [out]
  • 2.修改Android.mainfest 文件,在application 节点添加 android:debuggable=”true”
  • 3.在入口点的类onCreate方法中添加,invoke-static{},Landroid/os/Debug;->waitForDebugger()V
  • 4.java -jar apktool.jar b -d [path] -o [taget.apk]
  • 5.java -jar signapk.jar xxx.pem xxx.pk8 in.apk out.apk

方法二

Python框架值Scrapy的学习

前言

已经很久很久没有写笔记了,这次要打算系统的学习Scrapy框架,因此把笔记做好,以免以后又要重新查文档。

在做这个之前已经把xici代理所有IP+端口下载下来了,但是为了学习这个Scrapy,重新拿那个项目做个例子,有助于学习,也可以对比出普通requests的爬虫和这个Scrapy框架的优缺点。

准备工作

开发环境

  • Python 3.5
  • VSCode + CoderRunner 插件

目前一直使用VSCode作为开发环境,包括笔记的记录,也是使用VSCode来做的,感觉VSCode除了不能对Python的输入进行处理,其他一切都OK。

安装Scrapy

  • python -m pip install scrapy

只需要使用命令行输入以上代码即可,注意环境变量Python的顺序,如果有anaconda更好。

开始项目

创建项目

  • scrapy startproject [project_name] [domain]

使用以上方法可以创建一个基础的爬虫项目

创建爬虫

  • scrapy genspider [spider_name]
  • scrapy genspider -t crawl [project_name] [domain]

需要在项目的最上层目录,即与cfg文件所在的同级目录执行以上命令,使用 -t 可以指定crawl模板。

Spinx环境搭建

前言

之前存了很多文本数据 - 具体是啥就不说了

很早之前就想对其内容进行检索了,自己写一个又觉得太麻烦,而且效率估计还低下。

百度搜了下又ELK和Spinx这2个方案,都测试了下,ELK太大了,感觉要花费好多时间去了解,Spinx暂时应该能满足我的需求了。

加上最近觉得自己挺久没写博客了,将这次搭建Spinx文本检索环境的过程记录下来。

环境搭建

1.从官网下载sphinx-3.1.1,我使用的是这个版本。

2.将etc目录下的sphinx-min.conf.dist配置文件复制到Bin下面,命名为sphinx.conf。

3.配置如下,该配置支持中文。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
source src1
{
type = mysql

sql_host = localhost
sql_user = root
sql_pass = root
sql_db = sheg
sql_port = 3306 # optional, default is 3306
sql_query_pre = set charset utf8
sql_query = \
SELECT id, mail, name, idcard, passwd, phone, mail2 \
FROM `12306`
sql_attr_uint = group_id
sql_attr_timestamp = date_added
}


index sk12306
{
source = src1
path = F:\sk\sphinx-3.1.1\data\sk12306\
# 设置中文匹配
min_word_len = 1
# 指定字符集(已废弃)
charset_type = utf-8
# 指定utf-8的编码表
charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
min_prefix_len = 0
min_infix_len = 3
# 开启中文分词支持
ngram_len = 1
# 需要分词的字符
ngram_chars = U+3000..U+2FA1F
}


index rt
{
type = rt
rt_mem_limit = 128M
path = F:\sk\sphinx-3.1.1\data\testrt\
rt_field = name
rt_field = idcard
rt_field = mail
rt_field = passwd
rt_field = mail_2
rt_field = phone
rt_attr_uint = gid
}


indexer
{
mem_limit = 128M
}


searchd
{
listen = 9312
listen = 9306:mysql41
log = F:\sk\sphinx-3.1.1\log\searchd.log
query_log = F:\sk\sphinx-3.1.1\log\query.log
read_timeout = 5
max_children = 30
pid_file = F:\sk\sphinx-3.1.1\log\searchd.pid
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
workers = threads # for RT to work
binlog_path = F:\sk\sphinx-3.1.1\log
}

查询指定列 idcard: @idcard xxxxxxx
模糊查询 352225*
中文默认模糊查询

4.将某库代码放到PHP下运行就可以了。

那么问题了,先整理数据还是把某库修改成适合我的文本查询器呢。

C语言操作Mysql数据库

前言

前几天要用开发个工具,需要连接到数据库,然后增删查改,图形化界面,还有大量指针操作,所以只能用C比较合适,但是C语言连接Mysql没接触过,遍开始了C语言mysql尝试,之后过了几天(就是现在),又要重新配置下环境,虽然依稀记得怎么操作,但是感觉这种事情有必要记下来,不然时间久了还得再去百度搜索,麻烦的很。

环境安装

从mysql官网下载Mysql connector 6.1版本,为什么是6.1呢,因为我找了半天,8.0版本的装了好几遍,就是没有C语言的,只有C++版本的。

C语言版本下载地址

安装好后开始下一步,记得安装目录。

开始开发

C++ 头文件添加目录 C:\Program Files (x86)\MySQL\MySQL Connector C 6.1\include
C++ lib添加目录 C:\Program Files (x86)\MySQL\MySQL Connector C 6.1\lib

将 C:\Program Files (x86)\MySQL\MySQL Connector C 6.1\lib\libmysql.dll复制到程序的运行目录下。

若使用VS开发,则放到VS目录(直接调试运行时),编译的情况下放到应用程序目录即可。

代码部分

1
2
3
4
5
6
7
#include <mysql.h>

#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 3306
#define SERVER_USER "root"
#define SERVER_PASS "root"
#define SERVER_TABLE "xxx"

Vue学习及自我反思

昨天说的要做一个文本搜索的功能, 用的Sphinx来进行索引, 后台应该是用PHP了, 毕竟有现成的代码,打算改一改, 前后端分离。

因此,突然想起了Vue这个框架, 但是呢, 以前只学过JQuery 和 Bootstrap (会点皮毛,也就能写出简单页面的水平)

看了官方VUE的文档, 导入JS直接使用还行, 简单方便, 紧接着去看了 element-ui, 这一看我就蒙了.

在我的观念里一直是 JQuery和 Bootstrap这个模式, 导入JS文件和CSS就可以了.

谁知道这玩意需要nodejs和npm, 这我都没接触过啊, 内心相当抗拒, 想着不就开发个简单的web显示页面吗, 还要去弄这些, 真麻烦.

试了半天, 直接从他给的cdn地址扣js和css都还是不行.

于是心里发狠, 别人都可以用, 为什么我不用呢, 然后就反思了, 就是我这种保留原有方式学习新东西的操作, 让我的技术原地踏步!!

因此开始百度,先了解nodejs是什么, 发现是一个JavaScript运行环境, 可以实现把JavaScript放到服务器上运行.

然后开始了解npm, 是nodejs的包管理系统.

下载了nodejs, 自带了npm(自动添加到了环境变量)

然后下面开始了Vue的学习之路, 其实也不是要学Vue, 就是用它来完成我要的效果就可以了.

记一次PHPstudy无法正常启动

C盘满了,把PHPStudy换到D盘,结果启动不了。。

由于我之前比较谨慎,一直用的非服务模式,也就是没有系统服务,那么唯一的可能就是配置文件写了绝对路径了。

打开 php下的php.ini 修改 extension_dir属性
打开 apache下的httpd.conf 修改 ServerRoot 和 DocumentRoot 属性
打开 mysql下的my.ini 修改 basedir 和 datadir

重新打开,搞定收工!。

关于C++网络通信开发的坑

1.协议设计

1.关键数据加密
2.防止数据重放
3.注意数据对齐

2.协议实现

1.服务端多线程同步问题。
2.客户端秘钥登录认证问题。
3.处理TCP粘包问题

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,也可能合并空间。

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

sqlalchemy 数据库操作

sqlAlchemy是python中最著名的ORM(Object Relationship Mapping)框架。

ORM 从开发角度说,就是采用面向对象的方式来直接操作数据库,方便的很,不用写那么多数据库查询语句。

这里记录一下增删查改命令。

安装方法:

1
2
python -m pip install sqlalchemy
python -m pip install pymysql

参考代码:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from sqlalchemy import Column,String,Integer,create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

engine = create_engine('mysql+pymysql://root:root@localhost:3306/info')

DBSession = sessionmaker(bind=engine)


class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
passwd = Column(String)

# 增加数据
try:
db = DBSession()
db.add(User(name="Hacker1",passwd="1234561"))# 增
db.commit()
db.close()
except :
pass

## 查询数据
try:
db = DBSession()
# 查所有
results = db.query(User).all()
for r in results:
print(r.name)
#条件过滤
result = db.query(User).filter_by(id = 4).first()
print(result.name)
# 条件大于
results = db.query(User).filter(User.id > 5).all()
for r in results:
print(r.name)
db.close()
except :
pass

## 修改数据
try:
db = DBSession()
result = db.query(User).filter_by(id = 7).first()
result.name = "what"
db.commit()
db.close()
except :
pass

## 删
try:
db = DBSession()
result = db.query(User).filter_by(id = 9).first()
db.delete()
db.commit()
db.close()
except Exception as e :
print(e)
pass

2019年4月25日13:44:24

最近发现自己学的知识很乱,而且写的笔记都仅限于自己阅读,作为自己查阅的资料,想做出一些改变,规划一下新的生活方式。

首先先确定自己要做哪些事情,然后对其进行规划,合理安排时间(不过得等毕业设计完全搞定才能开始自己的计划,原本的作息已被毕业设计打乱,每天写论文到2点真心痛苦)

现在确定下来要做的事情用4件:

  • 写文档
  • 学技术
  • 健身
  • 学厨艺

从写文档开始规划,从3个方面进行提高:

  • 掌握几款画图工具,将想表达的想法通过图片展示出来
  • 提高写作技巧,将文档要表达的主要框架搭建起来,再填充内容
  • 阅读更多技术材料,读的多才能写的多

学习技术也有很多可以提高效率的地方:

  • 学习新技术将案例同理解记录下来
  • 对一些项目进行实战,先查看是否有相关设计,理解其设计思想后做出自己的设计方案
  • 对设计的方案进行实现,给自己定个期限,不让自己的想法黄了
  • 主要针对PWN、逆向、系统底层、WEB相关、渗透相关进行学习

这里先预定一个目标,待毕业设计完成之后,写一个脚本代理池,通过某些免费的代理IP地址进行收集、整合、测试、筛选、评分、形成自己稳定的代理池。

健身方面严格要求自己,待毕业设计完成开始,每天4公里打底,外加做一套腹肌训练和做一些能将背部拉直的动作(挽救驼背)

厨艺方面要学会各种饼、面包的做法,同时把pizza做好(这个可能得转正之后住房确定下来才能开始)。

最后就是要genuine以上内容给出一张作息时间表,规定自己在什么时间做什么事,严格按照自己的计划来(这部分最难做到,但如果真的想做出改变,则非做不可)

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移动到前面去但是要注意内存对齐)。

Windows64位下关于SSDT寄存器

想要对SSDT进行HOOK,首先要找到SSDT表,修改表的内容,但是64位情况却有些不同。

  • 64位关于SSDT的KeServiceDescriptorTable并没有导出,无法使用extend来使用
  • 64位关于SSDT无法直接修改,有PG保护,甚至不能inline HOOK (触发蓝屏)

解决方法:

  • 使用PG补丁,强行将PG disable
  • 使用MSR接管,重新生成一份SSDT表,在自己的表中HOOK

无论是方案1还是方案2,在没有KeServiceDescriptorTable的情况下只能通过特征码搜索SSDT表

其中,关键函数就是KiSystemCall64,该函数的地址可以通过读取c0000082寄存器来获取
windbg

1
rdmsr c0000082

并查集算法学习

蓝桥杯题目:合根植物

问题描述

w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输出格式

第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20

样例输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

样例输出

1
5

其和根情况如下图

一直把6这个点给忽略了,6也可以算一个根。

解决方案

这里采用并查集算法。

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
36
37
#include <stdio.h>
#include <string.h>
int* all;
int count = 0 ;

void union1(int a, int b){
all[b]=a;
}

int find(int a){
if(all[a]==0) return a;
all[a] = find(all[a]);
return all[a];

}
void func(int a , int b){

int ra = find(a);
int rb = find(b);
if(ra==rb) return ;
union1(ra,rb);
count ++;

}
int main(int argc, char *argv[]) {
int n = 0, m = 0,sum = 0, a = 0, b = 0;
scanf("%d%d",&m,&n);
scanf("%d",&sum);
all = new int[n*m + 1];
memset(all,0,(m*n+1)*sizeof(int));
for(int i = 0; i < sum ; i ++){
scanf("%d%d",&a,&b);
func(a,b);
}
printf("%d",m*n-count);
return 0;
}

就是把相同的两个点连接起来,最后分成多少个分组,这里使用的是并查集算法,百度有并查集算法的解释,这里只贴出这个例子

Windows 驱动入门

Windows驱动层是我一直想学的东西,最近也在看软件调试。

如果不了解驱动层的开发,就很难把驱动层的逆向学好。

目标很简单,先把驱动的基本安装代码实现,再把R0层与R3层通信的方式实现

最后通过R3层的程序能通过直接调用R0层的接口来实现R3层操作R0层。

DbgView配置

涉及到开发DbgView是必不可少的一个工具

windows7需要添加一个注册表项才能接受到DbgView的消息:

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager]
"Debug Print Filter"=dword:0000000f

配置好后重启即可。

WDK + VS2012

这里用VS2012是因为win10对驱动开发不友好,选择Windows7做开发环境,VS2010代码提示有点问题,上次学C++开发CLR程序的时候根本没提示。

因此选用了VS2012,目前用的蛮好,现在我的开发环境一直用VS2012,那么WDK只能选择8.0了,8.1版本的WDK没办法支持VS2012。

还有我比较喜欢本地版本的MSDN,不想在WEB上查询代码,这些VS2012都能搞定,给大家推荐一下。

最简单的驱动程序

开发驱动,也是要从Hello,world开始入门的,这就来写一个最简单的驱动程序。

选用WDM空项目

上MSDN,找DriverEntry的代码,拷贝复制

编写一段入门程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <ntddk.h>

DRIVER_UNLOAD Unload;

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath){

DbgPrint("驱动加载成功!");

DriverObject->DriverUnload = Unload;


return STATUS_SUCCESS;
}


VOID Unload(struct _DRIVER_OBJECT *DriverObject){
DbgPrint("驱动卸载成功!");

}

将警告视为错误 这个选项禁用

编译成功,驱动成功被注册。

使用DbgView查看输出

第一步:打开选项

第二步:安装启动卸载驱动

总结一下,就是驱动要有个入口,类似于exe程序的main,然后入口的第一个参数是一个引用,你需要将引用的卸载地址指向卸载驱动的函数地址。

至此,HelloWorld之路已经走完了,下面要开始学习关于通信的方式了。

DriverEntry特性

DriverEntry()一共有 2 个参数

  • PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针。
  • PUNICODE_STRING RegistryPath,驱动程序的服务主键,在DriverEntry() 返回后则会释放,需要用时记得保存。

在DriverEntry中,一般需要做一下几件事情:

  • 设置驱动卸载例程
  • 注册 IRP 的派遣函数
  • 创建设备对象

驱动卸载例程与 IRP 派遣函数都是对驱动对象设置的。

驱动通信的方式有很多:

  • 共享内存
  • 共享事件
  • IOCTL宏
  • ReadFile() 或 WriteFile()

今天这里学习的使用IOCTL宏来进行通信。

Win32程序使用 DeviceIoControl() 与驱动进行通信。

驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。

IRP 分为2部分:

  • IRP 首部
  • IRP堆栈。

在驱动程序中,IRP 派遣例程几乎都有对应的 Win32 API 函数,下面介绍IRP派遣例程。

名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或丢弃输入缓冲区 FlushFileBuffers\FlushConsoleInputBuffer \PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown

IRP(I/O Request Package)

Ring3(用户层)应用程序调用kernel32.dll导出的DeviceIoControl函数后,会调用到ntdll.dll导出的NtDeviceIoControlFile函数,进而调用到ntoskrnl.exe提供的服务函数NtDeviceIo ControlFile,该函数会将I/O请求转化为IRP包,并发送到对应驱动的派遣例程函数中,其机制类似于Windows的消息机制。

IRP是一个很复杂的数据结构,IRP两个基本的属性:

  • MajorFunction
  • MinorFunction

分别记录IRP的主类型和子类型。

操作系统根据MajorFunction将IRP 分发给派遣函数,在派遣函数中还可以判断IRP属于哪种MinorFunction。

DriverEntry有个函数指针数组MajorFunction.都是派遣函数地址。

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,位于段描述符或门描述符中,用于表示一个段或门的特权级别

PE文件结构学习总结

之前是基于数据来学习PE结构,学习的是PE结构的基础,但为了快速开发,应该使用winnt中提供的结构体来实现基于PE结构的相关功能,如内存载入DLL,内存注入,IAT HOOK等技术。

从文件头开始,即 0x0这个地址开始,遇到的会是一个DOS头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

逆向工程核心原理

第一章 软件逆向工程

软件逆向分析的2种方法:

1.静态分析

该方法主要使用IDA PRO等工具,对软件进行静态分析,整理软件分支结构,从而判断出软件的真正意图。

该方法适合简单应用程序以及未对代码进行保护的程序,通常加壳程序需要先对其进行脱壳才可使用IDA PRO 进行分析。

2.动态分析

该方法使用Ollydbg工具进行动态调试,使用Ollydbg进行动态调试时需要对PE文件结构,TSL回调函数,软件执行流程等有一点的认知

软件逆向工程中需要的基础知识:

1.C/C++代码知识
2.十六/二进制思维
3.汇编语言基础
4.操作系统知识

第二章 使用两种方式打补丁

1.通过直接在内存中修改

在内存中修改字符创后,重新生成PE文件,该方法局限于源字符串大小。

第三章 小端标记法

大端标记法常出现在底层,unix系统和网络协议大多使用大端标记法

小段标记法在windows和linux中被使用

小端的特点是高位放在高地址,低位放在低地址

大端的特点就是存储时像字符串一样

内存中的数据大多以4K对其方式

第四章 寄存器

X86体系结构计算机通常有如下寄存器:

8个通用寄存器 : eax ebx ecx edx esi edi esp ebp
6个段寄存器: cs ds ss es fs gs
一个状态/控制寄存器: eflags
一个程序指针寄存器: eip

eax常用于结果返回,edx通常协助eax进行计算
ebx用于地址计算
ecx用于累加
esi edi 用于数据传输
esp 堆栈当前地址
ebp 堆栈参考指针
cs 代码段
ds 数据段
ss 堆栈段
es 拓展数据段
fs gs 拓展数据段(有用的数据)

程序调试常用到FS寄存器,用于计算SEH 结构化异常处理机制,TEB 线程环境块 ,PEB 进程环境块等地址,属于高级调试必备的数据。

还有CR0-CR4 折4个控制寄存器,学习到了再讨论

第五章 栈

先进先出

第七章 栈帧

EBP保存当前栈空间位置,退出函数还原,即栈帧的概念

第八章 VB程序

消息框函数 rtcMsgBox

第十章 函数调用约定

cdecl 调用方式,调用者负责处理堆栈平衡,add esp,8
stdcall 调用方式,被调用者自行处理堆栈,return 8
fastcall 调用方式,与stdcall类似,但是会用到寄存器

第十三章 PE文件结构

用010 editor观察即可,最难的是导入表和导出表这部分

导出表 由 导出表的数据目录指向,数据目录指向的导出表地址是个链表,直到00000000结束

数据内容为一个结构一,其中有OriginalFirstThunk 和 FirstThunk 2个指针,指向一个指针数字

1数组指向需要的dll函数名
2在执行的时候,会把DLL文件中函数名的覆盖地址到该指针数组,但是未执行时该数组和1数组内容相同

第二十一章 DLL注入之Windows消息钩子

HOOK消息钩子,实现DLL注入。

第二十三章 DLL注入

方法:
1.线程注入DLL
2.注册表注入DLL
3.HOOK注入DLL
4.输入法注入DLL
5.导入表注入
6.内存注入/代码注入

第二十九章 API钩取技术(HOOK)

第三十七章 X64函数调用约定

X64中统一采用fastcall方式来调用

参数1 放在rcx XMM0
参数2 放在rdx XMM1
参数3 放在r8 XMM2
参数4 放在r9 XMM3

减少 push的使用 用move 传递参数,直接使用rsp进行堆栈操作