Hoppa yfir í efnið

Bakhönnun Python-verkefna

Python-bakhönnunarverkefni henta oft byrjendum því frumkóðinn er yfirleitt læsilegur. Kóðanum kann þó að hafa verið breytt með torlæsingu eða hann getur notað umbreytingar sem við þurfum að snúa við.

Algengar tegundir

Tegund Lýsing
Samanburður Forritið ber inntak saman við rétt svar og við könnum hvernig samanburðurinn fer fram
XOR-dulritun Fáninn er dulritaður með XOR-aðgerð og við beitum sömu aðgerð aftur til að endurheimta hann
Sérsniðin kóðun Stöfum eða bætum er umraðað eða skipt út eftir fastri reglu, til dæmis með umskiptum
Torlæsing Kóðinn er vísvitandi gerður torlæs, til dæmis með óskiljanlegum breytunöfnum eða exec og Base64-kóðuðum texta

Dæmi 1: Beinn samanburður

# challenge.py
import sys

def check(flag):
    secret = [103, 103, 123, 115, 51, 99, 114, 51, 116, 125]
    if len(flag) != len(secret):
        return False
    for i in range(len(secret)):
        if ord(flag[i]) != secret[i]:
            return False
    return True

flag = input("Flag: ")
if check(flag):
    print("Correct!")
else:
    print("Wrong!")

Lausn: Breyta tölunum í stafi:

secret = [103, 103, 123, 115, 51, 99, 114, 51, 116, 125]
flag = ''.join(chr(c) for c in secret)
print(flag)   # gg{s3cr3t}

Dæmi 2: XOR-dulritun

# challenge.py
encrypted = bytes([0x25, 0x25, 0x39, 0x3a, 0x72, 0x30, 0x3f])
key = 0x42

flag = input("Flag: ").encode()
if len(flag) != len(encrypted):
    exit("Wrong length")

result = bytes(b ^ key for b in flag)
if result == encrypted:
    print("Correct!")

Lausn: XOR-aðgerðin er andhverfanleg. Við beitum henni aftur með sama lykli til að fá fánann:

encrypted = bytes([0x25, 0x25, 0x39, 0x3a, 0x72, 0x30, 0x3f])
key = 0x42
flag = bytes(b ^ key for b in encrypted)
print(flag.decode())   # gg{x0r}

Dæmi 3: Umskipti

# challenge.py
table = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def encode(s):
    return ''.join(table[alpha.index(c)] if c in alpha else c for c in s)

secret = "uu{itssg_vgksr}"
flag = input("Flag: ")
if encode(flag) == secret:
    print("Correct!")

Lausn: Búa til uppflettingartöflu:

table = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
secret = "uu{itssg_vgksr}"

reverse = {t: a for t, a in zip(table, alpha)}
flag = ''.join(reverse.get(c, c) for c in secret)
print(flag)   # gg{hello_world}

Dæmi 4: Torlæs kóði

Stundum lítur kóðinn svona út:

exec(__import__('base64').b64decode(
    b'ZmxhZyA9IGlucHV0KCk7IHByaW50KCdDb3JyZWN0IScgaWYgZmxhZyA9PSAnZ2d7dGVzdH0nIGVsc2UgJ1dyb25nIScpCg=='
))

Lausn: Við keyrum kóðann ekki strax heldur afkóðum Base64-gögnin og lesum niðurstöðuna fyrst:

import base64
code = base64.b64decode(b'ZmxhZyA9IGlucHV0KCk7IHByaW50KCdDb3JyZWN0IScgaWYgZmxhZyA9PSAnZ2d7dGVzdH0nIGVsc2UgJ1dyb25nIScpCg==')
print(code.decode())
# flag = input(); print('Correct!' if flag == 'gg{test}' else 'Wrong!')

Keyrum aldrei óþekktan kóða beint

Þegar við sjáum exec(...) eða eval(...) með kóðuðum strengjum afkóðum við og lesum kóðann áður en við keyrum hann.


Dæmi 5: Mörg lög umbreytinga

Stundum er nokkrum kóðunaraðferðum og umbreytingum beitt hverri á eftir annarri:

import base64, codecs

def encode(flag):
    step1 = flag.encode()
    step2 = base64.b64encode(step1)
    step3 = codecs.encode(step2.decode(), 'rot_13').encode()
    step4 = step3[::-1]   # Snúa við
    return step4

target = b'==DsmW3Z5EQo7q2M'
guess = input("Fáni: ")

if encode(guess) == target:
    print("Rétt!")
else:
    print("Rangt!")

target er úttakið sem rétti fáninn á að gefa. Það byrjar á == vegna þess að í síðasta skrefinu er strengnum snúið við og fyllingarstafir Base64 færast fremst.

Lausn: Snúa við í öfugri röð:

import base64, codecs

target = b'==DsmW3Z5EQo7q2M'

step3 = target[::-1]                              # Snúa við
step2 = codecs.decode(step3.decode(), 'rot_13')   # Afkóða ROT13
step1 = base64.b64decode(step2)                   # Afkóða Base64
flag  = step1.decode()                            # UTF-8
print(flag)   # gg{l4y3rs}

Gagnleg Python-tól

# Prenta alla stafi í streng
for c in b'\x41\x42\x43':
    print(chr(c), end='')

# Breyta á milli gagnagerða
int('ff', 16)          # sextándakerfi → heiltala: 255
hex(255)               # heiltala → sextándakerfi: '0xff'
bin(255)               # heiltala → tvíundakerfi: '0b11111111'
bytes([65, 66, 67])    # listi → bytes-gildi: b'ABC'
list(b'ABC')           # bytes-gildi → listi: [65, 66, 67]

# XOR allt á móti lykli
key = 0x42
result = bytes(b ^ key for b in encrypted)

# Finna lykil ef við þekkjum hluta ódulritaða textans
# "gg{" er oft upphaf fánans
for key in range(256):
    attempt = bytes(b ^ key for b in encrypted[:3])
    if attempt == b'gg{':
        print(f"Lykill: {hex(key)}")

Algengar aðgerðir og hvernig við snúum þeim við

Aðgerð Hvernig snúa við
XOR key Beita XOR-aðgerð aftur með sama lykli
base64.b64encode base64.b64decode
rot13 codecs.decode(..., 'rot_13') aftur
s[::-1] (reverse) s[::-1] aftur
chr(ord(c) + n) chr(ord(c) - n)
sorted(s) Yfirleitt ekki andhverfanlegt nema upplýsingar um upprunalegu röðina hafi varðveist
Þekkt umröðun Beita andhverfu umröðuninni

Föll, lokanir og fönguð gildi

Föll í Python eru hlutir. Innra fall getur haldið í breytur úr ytra fallinu, jafnvel eftir að ytra fallið hefur lokið keyrslu. Slíkt innra fall kallast lokun (e. closure).

def wrap(value, inside):
    def doll():
        return value, inside
    return doll

inner = wrap("B", None)
outer = wrap("A", inner)

Hér geymir outer bæði strenginn "A" og fallið inner. Hægt er að sjá nöfn fönguðu breytanna í outer.__code__.co_freevars og gildin í outer.__closure__:

captured = {
    name: cell.cell_contents
    for name, cell in zip(outer.__code__.co_freevars, outer.__closure__)
}
print(captured)

Þegar lokanir eru hreiðraðar getum við fylgt innra fallinu áfram eins og dúkku inni í annarri dúkku.

Z3 (SMT-leysir)

Fyrir flóknar skorður getur Python-bókasafnið Z3 leyst jöfnur sjálfkrafa:

from z3 import *
x = Int('x')
s = Solver()
s.add(x * 3 + 7 == 100)
s.check()
print(s.model())  # x = 31


Æfingar

Verkefni til að æfa Python-bakhönnun:

  • Matryoshka: Við rekjum gögn í gegnum nokkur lög af Python-föllum.
  • Pandora's Box: Við greinum torlæstan kóða á öruggan hátt án þess að keyra hann í blindni.
  • Fossil: Við komumst að því hvernig forritið virkar út frá þýddri Python-skrá þegar frumkóðinn fylgir ekki.
  • The Sphinx: Við leysum verkefni þar sem mörg skilyrði þurfa að standast samtímis.