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

并查集算法学习

蓝桥杯题目:合根植物

问题描述

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进行堆栈操作