Attribution

This short Introduction to Python is based on our own notes taken during the TCM Security - Python 101 for Hackers video course by Riley Kidd.

We highly recommend this course as a great starting point for learning Python – it was instrumental in our own understanding of the subject. We would like to extend a big thank you to the TCM Security Team for providing such valuable resources!

Introduction

Welcome and Course Introduction

Project: https://247CTF.com/

What is Python?

  • A dynamic, interpreted, object-oriented, high-level language
  • Enables cross-platform rapid prototyping and scripting
  • Relatively simple, easy-to-learn syntax
  • General-purpose and versatile, with a mature standard library
  • Extensible, actively used, and supported by a large community

Why Learn Python as a Hacker?

  • Understand how scripts or proof-of-concept exploits work
  • Debug, modify, and run existing scripts
  • Create your own scripts and tools
  • An easy first programming language for beginners
  • Understanding how an application works makes it easier to anticipate how it could break (and how to fix it)

Python 2 vs Python 3

  • Python 2 is no longer under development – but people still use it
  • Python 2 code will (mostly) not run in a Python 3 environment
  • Python 3 stores strings as Unicode by default (not ASCII)
  • If you are unsure how to run a script, try both versions (or try to print something)
  • If you have a choice, use Python 3 (Python 2 is End of Life)

Setup

Installing VirtualBox

Follow the simple default installation.

Installing Kali Linux

Download the ISO from the “Bare Metal” section – choose the “Installer 64-bit” option.

VirtualBox settings for Kali:

  • RAM: 2GB
  • Disk: 40GB
  • Devices > Shared Clipboard > Bidirectional
  • Devices > Drag and Drop > Bidirectional

Kali installation settings:

  • Select “Graphical install”
  • Create user: notroot with password: notroot
  • Use default partitioning (no separate partition for /var required)
  • Install the GRUB boot loader

The Python Interpreter

  • Start the Python interpreter: python3
  • Start version 2: python2
  • Check CLI options: man python3
  • Print “hello world”: python3 -c 'print("hello world")'
  • Spawn a bash shell: python3 -c 'import pty; pty.spawn("/bin/bash");'

How to Run a Python Script

#!/bin/python3

print(1 + 1)
print(__name__)
		  
if __name__ == "__main__":
print("do something")
  • Run with Python 3: python3 calculate-demo.py
  • Run with Python 2: python2 calculate-demo.py
  • Run using the shebang:
#!/bin/python3      # place this on the first line
chmod +x calculate-demo.py      # make the script executable
./calculate-demo.py     # run the script

Understanding __name__ == "__main__"

When the Python interpreter runs a file directly, __name__ is set to "__main__".

However, if the file is imported from another module, __name__ is set to the name of that module. This allows you to write code that only runs when the script is executed directly, not when it is imported.

Installing Sublime Text

  • Navigate to sublimetext.com
  • Download for Linux
  • Follow the installation instructions:
wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/sublimehq-archive.gpg > /dev/null
# select the channel to use
echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list
# update sources and install
sudo apt-get update
sudo apt-get install sublime-text
  • Launch Sublime: subl calculate-demo.py

Python 101

Python Syntax

def test():
    print("test")

    print("test2")

    string = "string"
    number = 10

test()

x = 1 + 2 \
    + 3

print(x)

y = 1 + 1
print(y)

Indentation

Python uses indentation to define code blocks. You need at least one space, though we recommend using four spaces consistently throughout your code. Blank lines are ignored by Python. Use a backslash (\) to continue a statement across multiple lines.

Getting Help

Use Python’s built-in help system with help(print) or dir(print) to list the attributes and methods available for an object. Alternatively, refer to the PEP 8 Style Guide for Python best practices.

Variables and Data Types

name = "neut"
name_length = 4
name, name_length = "neut", 4

print(type(name))
print(type(name_length))

# casting
name_length = int("4")

# case sensitivity
name_length = 4
Name_lenght = 5

# lists
name_list = ["neut", "247CTF", "asd"]
name1, name2, name3 = name_list
    
# tuple
name_tuple = ("neut", "247CTF")
    
# dict
name_dictionary = {"neut": 4, "247CFT": 6}
name_boolean = True
name_range = range(6)
name_bytes = b"neut2"

Numbers

t1_int = 1
t1_float = 1.0
t1_complex = 3.14j
t1_hex = 0xa
t1_octal = 0o10

print(1 + 0x1 + 0o1) # =3
print(abs(4))
print(abs(-4))

print(round(8.4)) #8
print(round(8.5)) #8
print(round(8.6)) #9

print(bin(8))
print(hex(8))

String Formatting

In Python 3, all strings are represented in Unicode. Python strings are immutable – they cannot be changed after creation. When using .format(), no casting is needed.

string1 = "I am a string!"
string2 = 'I am a string too!'
string3 = """I am a long
long
string!"""

string4 = "I'm a string"
string5 = "I\"m a string"
# escape --> \ ABC
string6 = "\\ \x42\42\43"
string7 = "aaaaaaaaaa"
string8 = "a" * 10
# substring in string
print("neut" in string4)
print(string4.startswith("I"))
print(string4.startswith("n"))

# substring location
print(string4.index("string"))
# upper; lower
print(string4.upper())
print(string4.lower())

# cleaning up a messy string
messy_string = "    Messy string!   "

# by default -- by space
print(messy_string.strip())
messy_string = "Messy,string!"
print(messy_string.strip(","))

# replace parts
print(messy_string.replace("!","?").strip())
print(messy_string.replace("string","example"))

# ENCODING -- default --> Unicode
string4 = "I am a string!"
print(string4)
print(string4.encode())
print(string4.encode("utf-8"))

# left and right adjustment
# for padding and keeping consistent length
print(string4.rjust(25))
print(string4.ljust(25))

# use other delimiter -- not space
print(string4.rjust(25,"X"))
print(string4.ljust(25,"X"))

# concatenation
print("I am" + " a string")
print("String 4 is " + str(len(string4)) + " characters long")
print(type("1"+"1"))

# string formatting -- {} is the placeholder
print("string4 is {} characters long!".format(len(string4)))
print("{} {} {}".format(len(string4), 5.0, 0x12))

# specify the order of things
print("{0} {2} {1}".format(len(string4), 5.0, 0x12))
# specify the name of the place holder
print("{length}".format(length=len(string4)))

# formatted string
length = len(string4)
print(f"string4 is {length} characters long")
# as a float
print(f"string4 is {length:.2f} characters long")
# hex
print(f"string4 is {length:x} characters long")
# byte
print(f"string4 is {length:b} characters long")
# octal
print(f"string4 is {length:o} characters long")

# format strings
print("string4 is %d characters long!" % len(string4))
print("string4 is %f characters long!" % len(string4))
print("string4 is %x characters long!" % len(string4))

Booleans & Operators

valid = True
not_valid = False

print(valid)
print(not_valid)

# comparison
print(valid == True)
print(not_valid == True)

print(valid != True)
print(not_valid != True)

# the same as !valid in c -- NOT in python
print(not valid)
print(not not_valid)

# the same as ((10 < 9) == True)
print((10 < 9))
print((10 == 10))
print((10 != 10))
print((10 >= 10))
print((10 <= 10))
print((10 > 9))

print("-----")
# multiple comparisons
print(10 > 5 and 10 < 5)
print(10 > 5 or 10 < 5)

# referencing booleans as numbers -- 0:False -- 1:True
print(bool(0))
print(bool(1))

print(bool(0) == False)
print(bool(1) == True)

print("-----")
# arithmetic operations

print(10 + 10)
print(10 - 10)
print(10 / 10)
print(10 // 10)

print(10 / 3)
print(10 // 3)
print(10 % 3)

print(10 * 10)
print(10 ** 10)
print(10 % 10)

# short hand version of operations -- syntactic sugar
x = 10
print(x)
x = x + 1
print(x)
x += 1
print(x)
x -= 1
print(x)
x *= 5
print(x)
x /= 5
print(x)

# bitwise operators
print("-"*20)
x = 13
print(bin(x))
# better visibility -- remove 0b from front -- rjust
print(bin(x)[2:].rjust(4,"0"))
y = 5
print(bin(y)[2:].rjust(4,"0"))

# bitwise and/or
print(bin(x & y)[2:].rjust(4,"0"))
print(x & y)
print(bin(x | y)[2:].rjust(4,"0"))
print(x | y)

# bitwise shift -- bitshift
print(bin(x)[2:].rjust(4,"0"))
print(bin(x >> 1)[2:].rjust(4,"0"))
print(bin(x >> 2)[2:].rjust(4,"0"))
print(bin(x >> 4)[2:].rjust(4,"0"))
print(bin(x << 1)[2:].rjust(4,"0"))

Tuples

Tuples store multiple items in a single variable. They are immutable – they cannot be changed after creation – but they can be combined with other tuples.

tuple_items = ("item1", "item2", "item3")
print(tuple_items)
print(type(tuple_items))

tuple_numbers = (1, 2, 3)
print(tuple_numbers)
print(type(tuple_numbers))

tuple_repeat = ('Combine!', ) * 4
print(tuple_repeat)
print(type(tuple_repeat))

mixed_tuple = ("A", 1, ("A", 1))
print(mixed_tuple)
print(type(mixed_tuple))

# combine tuples
tuple_combined = tuple_items + tuple_numbers
print(tuple_combined)
print(type(tuple_combined))

# unpacking a tuple
item1, item2, item3 = tuple_items
print(item1)
print(item2)
print(item3)

# part of/exists or not
print("item2" in tuple_items)
print("item3" in tuple_items)
print("item4" in tuple_items)

# where to find it -- indexing
print(tuple_items.index("item2"))
print(tuple_items[0])
print(tuple_items[1])
print(tuple_items[2])
print(len(tuple_items))

# the last one
print(tuple_items[-1])
print(tuple_items[-2])

# slices -- to extract a number of elements
print(tuple_items[0:2])
print(tuple_items[:2])
print(tuple_items[-3:-1])

string1 = "I am a string!"
print(string1[0:4])
print(string1[-1])

Lists

Lists are ordered, changeable, and allow duplicates. When copying lists, always make a hard copy using list.copy() rather than simple assignment.

  • You cannot copy a list to another list simply by writing list1 = list2
  • This only creates a reference – all changes will propagate to both variables
  • Both variables point to the same underlying data in memory
  • Use list.copy() to create an independent copy
list1 = ["A", "B", "C", "D", "E", "F"]
print(list1)

list2 = ["A", 1, 2.0, ["A"], [], list(), ("A"), False]
print(list2)
print(type(list2))

# indexing
print(list1[0])
print(list1[-1])
print(list2[3][0])
print(list2[3][-1])

# change the list after it was defined
list1[0] = "X"
print(list1)

# delete -- insert -- append
del list1[0]
print(list1)

list1.insert(0, "A")
print(list1)

del list1[0]
print(list1)

list1 = ["A"] + list1
print(list1)

list1.append("G")
print(list1)

# built-in functions -- max;min;index;reverse;count
print(max(list1))
print(min(list1))

print(list1.index("C"))
print(list1[list1.index("C")])

list1.reverse()
print(list1)
# the same with syntactic sugar
list1 = list1[::-1]
print(list1)

print(list1.count("A"))
list1.append("A")
print(list1.count("A"))

# remove last element
list1.pop()
print(list1)

# extending lists
list3 = ["H", "I", "J"]
print(list3)

list1.extend(list3)
print(list1)

list1.clear()
print(list1)

# sorting
list4 = [8, 12, 5, 6, 17, 2]
print(list4)

list4.sort()
print(list4)

list4.sort(reverse=True)
print(list4)

# important note -- copying lists
list5 = list4
print(list4)
print(list5)

list5[2] = "X"
print(list5)
print(list4)
# make hard copy instead
list6 = list4.copy()
print(list4)
print(list6)
list6[2] = "A"
print(list4)
print(list6)

# map function
list7 = ["1", "2", "3"]
print(list7)
list8 = list(map(float, list7))
print(list8)

Dictionaries

Dictionaries store data in key-value pairs using efficient hash table structures. Duplicate keys are not allowed.

dict1 = {"a":1, "b":2, "c":3}
print(dict1)
print(type(dict1))
print(len(dict1))

# accessing data in a dict
print(dict1["a"])
print(dict1.get("a"))

print(dict1.keys())
print(dict1.values())
print(dict1.items())

# no duplicates are allowed
dict1["a"] = 1
print(dict1)

# adding items/values

dict1["d"] = 4
print(dict1)

dict1["a"] = 0
print(dict1)

dict1.update({"a": 1})
print(dict1)

# removing items
dict1.pop("d")
print(dict1)
del dict1['c']
print(dict1)

# nested dictionaries
dict1['c'] = {"a": 1, "b": 2}
print(dict1)

# creating empty dictionary
dict2 = {}
print(dict2)
dict3 = dict()
print(dict3)

Sets

Sets are unordered collections that do not allow duplicates. Because they are unordered, they do not support indexing.

set1 = {"a", "b", "c"}
print(set1)
print(type(set1))

# sets are not ordered -- no indexing
# print(set1[0])

# no duplicates
set2 = {"a", "a", "a"}
print(set2)
print(len(set2))

set3 = {"a", 0, True}
print(set3)
print(type(set3))

set4 = set(("b", 1, False))
print(set4)

# modifying items -- values
set1.add("d")
print(set1)

# no duplicates -- True==1;False==0
set3.update(set4)
print(set3)

# update sets with iterables
list1 = ["a", "b", "c"]
set4 = {4, 5, 6}
print(list1)
print(set4)
set4.update(list1)
print(set4)

# sets as mathematical constructs -- difference, union ...
set5 = {4, 5, 6}
set6 = set4.union(set4)
print(set6)

# only remove if sure that value is there -- otherwise use discard
set4.remove(4)
print(set4)

# will remove if there -- no errors if not
set4.discard(4)
print(set4)

# since no order -- pop will discard one value
print(set1)
set1.pop()
print(set1)

Conditionals

Conditionals are evaluated in linear order from top to bottom.

if True:
    print("True")

if False:
    print("False")

if not False:
    print("not false")

if 1 < 1:
    print("1 < 1")
elif 1 <= 1:
    print("1 <= 1")
else:
    print("else 1")

# only the first true evaluation will be executed
if 1 < 1:
    print("1 < 1")
elif 1 <= 1:
    print("1 <= 1")
elif 2 <= 2:
    print("2 <= 2")
else:
    print("else reached")

# combine comparisons
if 1 > 0 and 0 < 1:
    print("1 > 0 and 0 < 1")

if 1 < 0 and 0 < 1:
    print("1 < 0 and 0 < 1")

if 1 < 0 or 0 < 1:
    print("1 < 0 or 0 < 1")

if (1 < 0 or 0 < 1) or 1 == 0:
    print("1 < 0 or 0 < 1")

# short hand form -- syntactic sugar
if 0 < 1: print("0 < 1")

# inline ternary
print("1 >= 1") if 1 >= 1 else print("1 < 1")

# the same as
if 1 >= 1:
    print("1 >= 1")
else:
    print("1 < 1")

# more complicated ternaries
## long form expression
if 0 > 1:
    print("1")
elif 0 < 1:
    print("2")
else:
    print("3")

## ternary
print("1") if 0 > 1 else print("2") if 0 < 1 else print("3")

Loops

a = 1
print(a)

a += 1
a += 1
a += 1
print(a)

# while loop -- execute till condition is true
a = 1
while a < 5:
    a += 1
    print(a)

# for loop -- execute a number of times
for i in [0, 1, 2, 3, 4]:
    print(i+6)

print("----")

for i in range(5):
    print(i+6)

print("----")

# nested loops
for i in range(3):
    for j in range(3):
        print(i,j)


# loop control statements: break;continue
print("--break--")
for i in range(5):
    if i == 2:
        break
    print(i)

print("--continue--")

for i in range(5):
    if i == 2:
        continue
    print(i)

print("--pass--")

for i in range(5):
    if i == 2:
        pass
    print(i)

print("--looping-over-iterables-string--")

# loop over each character in a string
for c in "string":
    print(c)
    print("--looping-over-iterables-dict--")
    for key, value in {"a":1, "b":2, "c": 3}.items():
        print(key, value)

Reading and Writing Files

###### READING A FILE

# handle to the file -- default mode -- read and utf-8
f = open('top-100.txt')
print(f)

# specify mode -- read text
f = open('top-100.txt', 'rt')

# read the file
# print(f.read())

# read by lines -- 1 array, each item maps to a line
print(f.readlines())
# will read nothing
print(f.readlines())

# change the current file position back to the start
f.seek(0)
print(f.readlines())

# reach back again
f.seek(0)
for line in f:
    print(line.strip())

f.close()

###### WRITING TO A FILE
# will overwrite the file
f = open("test.txt", "w")
f.write("test line two!")
f.close()

# will NOT overwrite the file -- append mode
f = open("test.txt", "a")
f.write("test line three!")
f.close()

###### GETTING MORE INFORMATION ABOUT THE FILE HANDLE
print(f.name)
print(f.closed)
print(f.mode)

###### SPECIFY ENCODING
with open('rockyou.txt', encoding='latin-1') as f:
    for line in f:
        pass

User Input

# test = input()
# print(test)

# display a message before the input
test = input("Enter the IP:")
print(test)

# interacting with the user
while True:
    test = input("Enter the IP: ")
    print(">>> {}".format(test))
    if test == "exit":
        break
    else:
        print("exploiting..")

Exceptions and Error Handling

Python performs minimal error checking at compile time. It defers most checks until each line actually runs. An exception is an error that occurs during execution, typically representing a runtime problem that halts normal execution.

The try-except block:

  • try: Tests a block of code
  • except: Handles any errors that occur
  • finally: Executes regardless of whether an exception occurred – this always runs

Standard exceptions:

  • ArithmeticError: Errors during numeric calculations
  • SyntaxError: Errors in Python syntax
  • IndentationError: Incorrect indentation
# play around with commenting/uncommenting as you see fit

######### CATCHING ERRORS ########
print(1)

# indentation error
# print(2)

# FileNotFoundError -- example
try:
    f = open("adadsasdadasd")
except:
    print("the file does not exist!")


# previous example will catch ALL errors
try:
    fdglkjgkldfjgd
except:
    print("the file does not exist!")

# specify the error -- print it out
try:
    asdsadasdasd
    f = open("asdasdasdsa")
except Exception as e:
    print(e)

print("-"*10 + "granular" + "-"*10)

# specify the error -- more granular -- only first is printed
try:
    fsd;kfds;lkfsdf
    f = open("asdasdasdsa")
except FileNotFoundError:
    print("the file does not exist!")
except Exception as e:
    print(e)

print("-"*10 + "finally" + "-"*10)

# specify the error -- with finally block
# regardless of which is executed
# finally block will ALWAYS execute
# even when NO errors at all
try:
    f = open("asdasdasdsa")
except FileNotFoundError:
    print("the file does not exist!")
except Exception as e:
    print(e)
finally:
    print("this message!")

######### RAISING ERRORS ########
n = 100
if n == 0:
    raise Exception("n can't be 0!")
print(1/n)

# n = 0
# if n == 0:
#	raise Exception("n can't be 0!")
# print(1/n)

# exception is ONLY raised for that special case
# n = "asd"
# if n == 0:
# 	raise Exception("n can't be 0!")
# print(1/n)

# exception is ONLY raised for that special case
n = 100 # try 0;"asd"
if n == 0:
    raise Exception("n can't be 0!")
if type(n) is not int:
    raise Exception("n must be an int!")
print(1/n)

######### ASSERTIONS ########
n = 1 # try 0
assert(n != 0)
print(1/n)

Comprehensions

Comprehensions provide a shorter syntax for creating lists based on existing iterables.

list1 = ['a', 'b', 'c']
print(list1)

list2 = [x for x in list1]
print(list2)

# with conditionals
list3 = [x for x in list1 if x == 'a']
print(list3)

# with range
list4 = [x for x in range(5)]
print(list4)

# apply a function
list5 = [hex(x) for x in range(5)]
print(list5)

# with ternaries
list6 = [hex(x) if x > 0 else "X" for x in range(5)]
print(list6)

# numerical operations
list7 = [x * x for x in range(5)]
print(list7)

# multiple conditionals
list8 = [x for x in range(5) if x == 0 or x == 1]
print(list8)

# nested expressions
list9 = [[1,2,3],[4,5,6],[7,8,9]]
print(list9)
list10 = [y for x in list9 for y in x]
print(list10)

# other iterable datatypes --- not lists
set1 = {x + x for x in range(5)}
print(set1)

list11 = [c for c in "string"]
print(list11)

# join() -- to combine items in an iterable into a single string
print("".join(list11))
# specify delimiter
print("-".join(list11))

# written out in a longer format
list12 = []
for c in "string":
    list12.append(c)
print(list12)

Functions and Code Reuse

Global vs Local Variables:

  • Variables declared inside a function have local scope and can only be used by that function.
  • Variables declared in the main body of code are global variables and can be accessed by other functions.
def function1():
    print("hello from function!")

function1()
function1()

def function2():
    return "hello from function2!"

return_from_function2 = function2()
print(return_from_function2)

# not optional parameter
def function3(s):
    print("\t{}".format(s))

function3("parameter")

# multiple parameters
def function4(s1, s2):
    print("{} {}".format(s1, s2))

function4("any", "thing")
# specify by keyword arguments
function4(s1="thing", s2="any")
function4(s2="any", s1="thing")

# optional arguments
def function5(s1 = "default"):
    print("{}".format(s1))

function5()
function5("anything")

# unknown number of arguments --> *
def function6(s1, *more):
    print("{} {}".format(s1, " ".join([s for s in more])))

function6("function6")
function6("function6", "a")
function6("function6", "a", "b", "c")

# dictionary as argument
def function7(**ks):
    for a in ks:
        print(a, ks[a])

function7(a="1", b="2", c="3")
function7(a="1", b="2", c="3", d="4")

# any datatype
def function8(s, f, i, l):
    print(type(s))
    print(type(f))
    print(type(i))
    print(type(l))

function8("string", 1.0, 1, ['l', 'i', 's', 't'])

####### GLOBAL VS LOCAL VARIABLES
# global var
v = 100
print(v)

def function9():
# local var
v = 5
v += 1
print(v)

function9()
print(v)

# global var
v = 100
print(v)

def functionA():
    # reference the global var
    global v
    v += 1
    print(v)

    functionA()
    print(v)

##### functions calling functions
def function10():
    print("hello from function10")

def function11():
    function10()
    print("hello from function11")

function11()

###### RECURSION -- functions calling themselves
def function12(x):
    print(x)
    if x > 0:
        function12(x-1)

function12(5)

###### ITERATIVE FUNCTIONS
def function13(x):
    while x >= 0:
        print(x)
        x -= 1

function13(5)

Lambdas

A lambda is an anonymous function without a name. It can only contain one expression.

add_4 = lambda x: x + 4
print(add_4(4))

add = lambda x, y: x + y
print(add(10,4))
# the same as
def addf(x, y):
    return x + y

print(addf(10,4))

# ANONYMOUS EXPRESSIONS
# defined and evaluated all in one line
print((lambda x, y: x + y)(10, 4))

#### COMMON HACKING LAMBDAS
is_even = lambda x: x % 2 == 0
print(is_even(2))
print(is_even(3))

# breaking a string up into specified sized blocks
blocks = lambda x, y: [x[i:i+y] for i in range(0, len(x), y)]
print(blocks("string", 2))

to_ord = lambda x: [ord(i) for i in x]
print(to_ord("ABCD"))
# the same as
def to_ord2(x):
    ret = []
    for i in x:
        ret.append(ord(i))
    return ret

print(to_ord2("ABCD"))