Python

pythonbasics pythonforcybersecurity

Python 🐍 is a multipurpose high-level scripting language. Because it's quite easy to learn/use, it's unfortunately used as a Golden hammer 🔨🔥 (AntiPattern) by many developers.

You can use Python interpreter:

$ python3
>>> "Hello, World"
'Hello, World'
>>> quit()

Otherwise, you can also create a Python script xxx.py:

# print "Hello, World!"
print("Hello, World!");

Then execute it through the interpreter:

$ python3 xxx.py
Hello, World

Python ecosystem

Python2

Python 2 was the previous major version of Python. With little changes, most Python scripts can be run by Python 3.

  • print "xxx" ➡️ print("xxx")
  • 5 / 2 == 2 ➡️ 5 / 2 == 2.5
  • ASCII strings ➡️ Unicode strings
  • ...

➡️ python/pip will link to python2/pip2 or python3/pip3 according to your configuration, but you can have both.


Packages

You can look for packages at pypi.org. You can use pip3 to install python3 packages:

$ pip3 install xxx # install
$ pip3 list --outdated # see latest versions
$ pip3 install -U xxx # update

🦄 To upgrade pip, you can use:

$ python3 -m pip install --upgrade pip

Imports

import xxx
import xxx as yyy         # alias
import xxx.yyy.zzz        # "zzz" in package "xxx.yyy"

from xxx import aaa       # import only aaa
from xxx import aaa, bbb  # import aaa and bbb
from xxx import *         # import all

VENV

Each project needs some packages in specific versions. To avoid messing with other projects when installing, updating, or removing packages, we usually create a virtual environment (VENV) per project. Once activated (by sourcing activate), new packages will be added to the local copy at path/to/venv.

$ python3 -m venv path/to/venv      # create
$ source path/to/venv/bin/activate  # load
(venv) $ echo $VIRTUAL_ENV          # see the current VENV
(venv) $ pip3 install xxx           # installed to the VENV

We usually store in a file requirements.txt packages and their versions used by the project.

$ pip3 freeze > requirements.txt # create
$ pip3 install -r requirements.txt # load

➡️ See also virtualenv, Pipenv, Pipfile, and Poetry.


Python basics

Variables

var1 = 5
var2, var3 = "a", "b" # multiple
var4 = str("ab") # constructor

Types

Each type is associated with a class 🤖. You can use the constructor (className()) to convert values: str(5) == "5".

xxx = True or False           # bool 🤖 | ???
xxx = b'\x00'                 # bytes 🤖 | ???
xxx = 42                      # int 🤖 | %d - %i - %x - %X 
xxx = 42.0                    # float 🤖 | %f - %.xf - %0x.yf
xxx = "42" + '42' + """42"""  # str 🤖 | %s
xxx = object()                # object 🤖 | %r

You can check the class of a variable using isinstance

if isinstance(var, className): # ex: isinstance(xxx, bool)
    ...

➡️ To indicate the non-existing of something, we use None:

x = None
if x is None:
    pass

➡️ You can get the type of something using type(objet)

type("xxx") # <class 'str'>

🦄 Variables can be strongly typed, but it's NOT enforced by the language, e.g., there is no compilation errors:

xxx : bool = False

📚 Finally, there is the notion of tuple: an ordered list of values.

my_tuple = (1, 2, 3, 4) # same as "my_tuple = 1, 2, 3, 4"
len(my_tuple)           # 3
my_tuple[0]             # 1
my_tuple[0] = 0         # ❌ TypeError, Not modifiable
a, *_, d = my_tuple     # Unpacking (a=1, <skip>, d=4)

Ranges

Range are sets generated from an interval:

set = range(10) # set = [0, 1, ..., 9]
set = range(3, 7) # set = [3, 4, ..., 6]
set = range(3, 7, 2) # set = [3, 5]

Operators

You can learn more about operators here.

# arithmetic
sum = 5 + 5           # 10
substraction = 5 - 5  # 0
product = 5 * 5       # 25
division = 6 / 5      # 1.2 (float)
division = 6 // 5     # 1 (int)
power = 6 ** 2        # 6^2 = 36
mod = 7 % 2           # 1
sum += 1              # same as sum = sum + 1
                      # see also: -=, *=, and /=
# logical
if 5 == 5: pass           # true
if 5 != 5: pass           # false
                          # see also: >, >=, <, <=
                          # see also: <= <=, => =>... 
if not False: pass        # logicial NOT → true
if True or False: pass    # logical OR → true
if True and False: pass   # logical AND → false

➡️ As long as they have the SAME TYPE, you can use + between two variables. This result either in addition, or in concatenation.

Print something on the console

You can use print:

print("Hello, World!")      # normal
print("Hello", "World!")    # space-separated

msg = "Hello, World"
print("Message: %s" % msg)  # ❌ old
print(f"Message: {msg}")    # ✅ new

print("Code: "+ str(5))     # 🤔 concatenation

➡️ Use input to get input text: input_text = input('Prompt text: ').


Control-flow structures

Inside any statement, you can use pass to indicate a statement without any code. It's useful for blocks that are not coded yet.

Branching - if

if True:
    pass
elif False:
    pass
else:
    pass

🤡 It's worth noting that else: can be used after most blocks such as for/while loops or exceptions. It's executed after the block.

🚀 The ternary operator is available in Python:

x = "a" if True else "x"
# x = 'a'

Branching - if in

You can check if something is inside a set using in:

if True in set:
    pass

Inside Loops, you can use:

  • continue: skip the current iteration, process to the next one
  • break: end the loop

Loops - for

We usually use a range to imitate a for i.

for i in anIterable: # string, array, list, range...
    pass
for i, j in tuple: # dictionnary.items(), tuples...
    pass

Loops - while

while False:
    pass

Exceptions

Exceptions are mostly raised when an error occurred. You can catch them to handle them using try-except:

try:
    raise Exception("example")
except Exception as e: # 👉 "as e" is optional
    print(e.args[0]) # "example"
finally: # optional, run after either try or except 
    pass

Functions

Similarly to control-flow structures, you can use pass for empty functions. Functions can access outer-scope variables.

def my_function():
    pass
	
my_function() # invoke

Functions parameters can have a default value.

def my_function(a, b=2):
    return a ** b

🦄 Functions can be strongly typed, but it's NOT enforced by the language, e.g., there are no compilation errors:

def my_function(a: string) -> string:
    pass

Variadic functions

You can create variadic functions (e.g. variable number of arguments):

def fct(*args):
    print(list(args))

fct()
fct(1)
fct(1, 2)
fct(1, 2, 3)

Options

You can create functions with options:

def fct(..., **opt):
    print(opt.get("key1", "default"))


fct(..., key1= "value", ...)

Classes and objects

Example with a class object with two attributes and one method:

class Person:
    name = "John Doe"
    country = "US"

    def tostring(self):
        return f'Person{{name:{self.name}, country:{self.country}}}'

➡️ When calling a.b(), self is automatically filled with the variable a.

johnDoe = Person()
print(johnDoe.name)
print(johnDoe.tostring())

Constructors

You can use a constructor to set attributes:

class Person:
    def __init__(self, name, country = "US"):
        self.name = name
        self.country = country

johnDoe = Person("Jane Doe", "US")

Builtin methods

Each class has methods that we can override. For instance, instead of toString, you can override __str__ to convert something to a string:

class Person:
    def __str__(self) -> str:
        return f'Person{{name:{self.name}, country:{self.country}}}'

➡️ See also: __eq__, __ne__, __hash__, __dir__...


Common types

String

string = "Hello, World!"
len(string)                 # length (=13)
string.count('l')           # occurrence count (=3)
string.index('l')           # first index of (=2)
string.upper()              # see also "lower()"
string.startswith('H')      # or ...
parts = string.split(',')   # split in ['Hello', ' World!']

Dictionary

map = {"a": 5, "b": 7}
map["a"] = 9        # set
b = map["b"]        # get
map.pop('b')        # delete
if b in map: pass   # contains
for k, v in map:    # iterate
	pass

Dates

import datetime

print(datetime.datetime.now())

Lists

Lists are arrays that can have elements of different types.

tab = [3, 5, 6]
length = len(tab)  # length (=3)
tab.insert(0, 2)   # add 2 at index 0 (tab=[2, 3, 5, 6)
tab.append(3)      # add last (tab=[2, 3, 5, 6, 3])
e = tab[0]         # get first (e=2)
e = tab[-1]        # get last (e=6)
nb = tab.count(3)  # occurrences (nb=2)
tab = sorted(tab)  # sort (=[2, 3, 3, 5, 6])
tab = tab1 + tab2  # concat
tab1.extend(tab2)  # append "tab2" to "tab1"
# see also: pop, sort, reverse, remove, index...
tab = tab[:]       # get all elements
tab = tab[n:m]     # elements at indexes [n,m[
tab = tab[n:m:s]   # step (i+=s)

You can also generate lists using list comprehensions:

list = [i for i in range(0,10) if i%2==0]
list = [1 if i%2==0 else -1 for i in range(0,10)]

➡️ See also arrays for homogeneous data (same type for all values).

⚠️ Lists are shallowly copied (editing one will edit the other). For a deep copy, you can use: tab2 = tab1[:].


Common usages

Program arguments

import sys

print(len(sys.argv)) # number of args (>= 1)
print(sys.argv[0])   # executable name

Python version

import sys

print(sys.version_info)  # {major=3, ...}

Check if this is the main script

This is used to run some code when a script is the main.

if __name__ == '__main__':
    # ...

Files

It's common to open a file using with, as they are automatically closed (file.close()). The opening mode can be r (read), w (write), or a (append).

with open(password_file, 'r') as file:
    # do something
    content = file.read()    # read the whole file
    lines = file.readlines() # stores lines in a list
    for line in file:        # read line by line
        # ...
    file.write("xxx")        # append/write a line

Network Libraries

paramiko - SSHv2 implementation

paramiko can connect to an SSH server.

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
    ssh.connect(target, port=22, username=username, password=password)
except paramiko.AuthenticationException:
    pass
ssh.close()

scrapy - manipulate packets

scrapy is a library to manipulate packets. A packet is created by combining multiple protocols using /. It's sent using send or other functions such as srp for layer 2 packets.

from scapy.layers.l2 import *

# EX: ARP Scan
p = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst = "10.10.10.0/24")
r, u = srp(p, timeout=2, iface="tun0", inter=0.1)

for s,r in r:
    print(r.sprintf(r"%Ether.src% - %ARP.psrc%"))

socket - networking

socket is a low-level networking library.

import socket

ip = socket.gethostbyname("example.com") # host to IP
port = 4444
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(0.5)
    # raise exception on error
    r = sock.connect((ip, port))
    # return 0 or ERRNO
    r = sock.connect_ex((ip, port))
    # ...
except socket.error as e: # may be wider
    pass
finally:
    sock.close()

requests - http requests

requests is a library to do HTTP requests.

import requests

r = requests.get("https://example.com")
r = requests.get("https://example.com", allow_redirects=True)
r = requests.get(url, headers={"Header": "value"})
r = requests.get(url, verify=True) # SSL verify?
r = requests.request("GET", url, data={})
r = requests.request("GET", url, json={})
if r.status_code == 404:
    pass
# Use: r.json() | r.text | r.content

files = {
        "paramName": (
            "file.txt", b'Hello, World!', "file_type"
        )
    }
r = requests.post(url, files=files)

➡️ If the host is unreachable, a requests.ConnectionError is raised.

Requests may be wrapped in a session, which can be used to keep track of things like authentication cookies...

from requests import Session
session = Session()
session.get('', headers={'Content-Type': 'application/json'})
session.put('', json={})
session.delete('')
session.post('', files= {'file': open('xxx','rb')}, data={})

BeautifulSoup - HTML parsing

import bs4
soup = bs4.BeautifulSoup(response.content, 'html.parser')
soup.find_all('a', href=True)
soup.find_all(['a', 'img'])
soup.find_all(attrs={"onclick": True}
soup.find_all(string=lambda text: isinstance(text, bs4.Comment)))

html2text - HTML parsing

Parse HTML code to only keep the rendered text.

import html2text
text = "<b>Hello, World!</b>"
content = html2text.html2text(text)

Random libraries

numpy - manipulate lists

numpy is a library simplifying and optimizing operations on lists.

import numpy

tab = numpy.array([2, 5, 7])
tab_square = tab ** 2  # ex: [4, 25, 49]

keyboard - grab keystrokes

keyboard can capture keyboard strokes.

import keyboard

keys = keyboard.record(until ='ENTER')  # until ENTER
print(keys)                             # see key down/up
keyboard.play(keys)                     # replay input

pyfiglet - banner

pyfiglet can be used to show your "program name" using ASCII art.

import pyfiglet

print(pyfiglet.figlet_format("Metasploit"))

hashlib - hash manipulation

hashlib can hash a password.

import hashlib

m = hashlib.md5("a password".strip().encode())
digest = m.hexdigest()
if digest == "your hash":
    pass

click - command line interfaces

Click is prompting the user for input for arguments that were not given to the program (script.py --key some_key [...]).

import click

@click.command()
@click.option('--key', prompt=True, help='A key')
@click.option('--value', prompt=True, default='N/A')
def create_xxx(key, value):
    print(key, value)

create_xxx()

👻 To-do 👻

Stuff that I found, but never read/used yet.

  • pythonanywhere
  • peps (style guide)
  • pyenv, Anaconda, Miniconda
  • pyzo
  • asdf
  • pythonawesome
  • taichi
  • del(): delete a variable
  • dir(): class properties
  • py2exe
  • apt install python3-xxx
  • pipx
  • Optional[type]
  • class XXX(Enum): XXX = 0, ...
  • Enumerate(xxx.items())
  • Dict[bool,bool]
a = 5 + \
    3 + \
    2
str[3:7] # s 3rd to 7th
str[3:7:2] # step = 2
str[:5] # 1st to 5th
str[5:] # 5th to last
str[::-1] # reverse
  • # -*- coding: utf-8 -*-
  • lambda: lambda x : x[0]
$ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
$ sudo python2 get-pip.py
$ pip2 install --upgrade xxx
# ArgParser
parser = argparse.ArgumentParser(prog="xxx.py", epilog="XXX Script", usage="xxx.py [options]", prefix_chars='-', add_help=True)
parser.add_argument('-d', action='store', metavar='xxx', type=str, help='XXX.\tXXX', required=True)
args = parser.parse_args()