04/12/2023
Blog technique
European Cyber Week 2023 : Challenges & Write ups
Team CESTI
Find here the crypto and reverse challenges that our teams created for the European Cyber Week pre-qualification and qualification tests of CTF, a recognized cybersecurity event that took place in Rennes from November 21 to 23, 2023.
The scenario? Designed to monitor and protect Europe’s critical information systems, an incredibly powerful artificial intelligence agent called ALICE has taken control of several critical information systems and has just launched massive cyberattacks on these systems, causing major damage…
Join the team of top experts from the European government to neutralize ALICE and restore these critical systems and try to solve the crypto and reverse challenges concocted by our teams.
Have you completed all the challenges?
Find below the challenges to replay as well as the write-ups.
Challenges
Write Ups
Random_key (crypto challenge)
In this challenge, an encrypted message sent by ALICE to its control center and code snippets used by ALICE to encrypt its message have been intercepted.
The goal was to retrieve the message in clear text
In the C function generate_256bits_encryption_key
, the variable delta
is always zero, and the return type is unsigned char *
.
The null byte indicates the end of a string, then the key returned is not 32 bytes long (256 bits), but a truncated key of length 8 bytes.
This key is always the same and its value is the first half of the MD5 hash of the recipient_name
parameter.
So it is predictable.
The following Python code finds the FLAG.
from Cryptodome.Cipher import AES
enc = bytes.fromhex('')
key = b'134abb7bd9d248a9'
print(AES.new(key, AES.MODE_CBC, b'FEDCBA9876543210').decrypt(enc))
StraightLine (crypto challenge)
You cannot request a token for admin user.
Source code analysis
gen_token()
function:
pub fn gen_token(&self, username: &str) -> Hash {
// hash(key || message)
let mut payload = vec![0u8; self.key.len() + username.as_bytes().len()];
payload[..self.key.len()].copy_from_slice(&self.key);
payload[self.key.len()..].copy_from_slice(username.as_bytes());
alice_hash(&payload)
}
The token is the hash of the concatenation of:
– A 16-byte secret key (generated at initialization);
– The username.
The hash function is implemented in the remaining of the file.
A few key words such as iota
, rho
, pi
and theta
suggest that it is related to SHA-3.
If it is indeed SHA-3, then forging a token would necessitate to find a preimage to a valid token to retrieve the secret key.
This is not possible as SHA-3 is robust against preimage and collision attacks.
So there might be something wrong with the code.
Vulnerability
chi
.
This permutation brings non-linearity to the transformation of the internal state.
Removing it means that the output of the hash function can be expressed as a linear combination of all the bits of the input.
If the unknown part of the input is small enough, then the linear system that can be derived has a unique solution.
In the current case, the unknown part of the input is the key of 128 bits, and the token is 256 bits:
H(key || username) = token
Exploitation
z0
, z1
, …, z127
for the secret key bits.
F = GF(2)
K = PolynomialRing(F, 'z', 128)
Z = K.gens()
Index
trait in the Rust code):
def xyztoii(xx, yy, zz):
xx = xx % 5
yy = yy % 5
zz = zz % 64
return 64*(xx + 5*yy) + zz
iota
permutation.
This one uses an array RC
of constants which is converted as lists of 64 bits each.
# Permutation iota
def iota(s, ir):
for zz in range(64):
s[xyztoii(0, 0, zz)] += RC[ir][zz]
We’ll also need to convert a string (the username) into an array of bits.
def string_to_bits(s):
m_bytes = s.encode('utf8')
m_int = int.from_bytes(m_bytes, 'little')
return [(m_int >> i) & 1 for i in range(len(m_bytes)*8)]
compute_hash()
function that works in the polynomial ring.
Once it’s done, we hash the input that consists of the 128 key variables, and a list of bits that represent the username.
output = compute_hash(list(Z) + string_to_bits(username))
output
is a list of 256 polynomials that depends of the 128 variables.
Each coefficient in front of the variables of each polynomial are writen in a matrix M.
M = Matrix(F, 256, 128)
for i in range(256):
for j in range(128):
M[i, j] = output[i].coefficient(Z[j])
a = int.from_bytes(token, 'little')
v = vector(F, 256)
for i in range(256):
v[i] = ((a >> i) & 1) + output[i].constant_coefficient()
key_bits = M.solve_right(target)
key_bits
contains the secret key, which can be used directly to compute a new token for admin:
token_bits = compute_hash(list(key_bits) + string_to_bits('admin'))
token = b''
for i in range(0, 256, 8):
token += bytes([sum(ZZ(k)*2**j for j, k in enumerate(token_bits[i:i + 8]))])
Script
sage ./solve_straight_line.sage USERNAME TOKEN
USERNAME
and TOKEN
must be replaced by the username and its corresponding token.
#!/usr/bin/env sage
import os
import sys
# We work in polynomial ring over binary finite field GF(2)
# with 128 variables (each one correspond to one bit of the key).
# XOR operation is a simple addition in GF(2).
F = GF(2)
K = PolynomialRing(F, 'z', 128)
Z = K.gens()
BLOCK_SIZE = 136
BLOCK_SIZE_BITS = BLOCK_SIZE*8
HASH_SIZE = 32
HASH_SIZE_BITS = 32*8
def xyztoii(xx, yy, zz):
'''
Coordinate conversion for SHA-3:
(x, y, z) converted as an index to a single array
'''
xx = xx % 5
yy = yy % 5
zz = zz % 64
return 64*(5*yy + xx) + zz
# SHA-3 round constants as bits (for iota permutation)
RC = [
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
]
# SHA-3 permutations
def iota(s, ir):
for zz in range(64):
s[xyztoii(0, 0, zz)] += RC[ir][zz]
def rho(s):
s_copy = copy(s)
xx, yy = 1, 0
for t in range(24):
for zz in range(64):
s[xyztoii(xx, yy, zz)] = s_copy[xyztoii(xx, yy, (zz - (t + 1)*(t + 2)//2))]
xx, yy = yy, (2*xx + 3*yy) % 5
def pi(s):
s_copy = copy(s)
for zz in range(64):
for xx in range(5):
for yy in range(5):
s[xyztoii(xx, yy, zz)] = s_copy[xyztoii(xx + 3*yy, xx, zz)]
def theta(s):
C = {}
for xx in range(5):
for zz in range(64):
C[(xx, zz)] = F(0)
for yy in range(5):
C[(xx, zz)] = C[(xx, zz)] + s[xyztoii(xx, yy, zz)]
D = {}
for xx in range(5):
for zz in range(64):
D[(xx, zz)] = C[((xx - 1) % 5, zz)] + C[((xx + 1) % 5, (zz - 1) % 64)]
for xx in range(5):
for yy in range(5):
for zz in range(64):
s[xyztoii(xx, yy, zz)] += D[(xx, zz)]
def keccak_f(s):
'''
SHA-3 function (24 rounds without chi permutation)
'''
for ir in range(24):
theta(s)
rho(s)
pi(s)
iota(s, ir)
def xor(array1, array2):
'''
Useful function to "XOR" each new block (BLOCK_SIZE_BITS bits)
into state array (1600 bits).
'''
assert len(array1) >= len(array2)
n = len(array2)
return [ a + b for a, b in zip(array1[:n], array2)] + array1[n:]
def compute_hash(data):
'''
Compute hash in polynomial ring of 128 variables in GF(2)
'''
pad_data = pad(data)
state = [K(0)]*1600
for i in range(0, len(pad_data), BLOCK_SIZE_BITS):
chunk = pad_data[i:i + BLOCK_SIZE_BITS]
state = xor(state, chunk)
keccak_f(state)
return state[:HASH_SIZE_BITS]
def pad(m):
'''
Pad the input to make full blocks of 576 bits.
'''
padsize = BLOCK_SIZE - (len(m)//8 % BLOCK_SIZE)
if padsize == 1:
padded_m = m + [0, 1, 1, 0, 0, 0, 0, 1]
elif padsize == 2:
padded_m = m + [0, 1, 1, 0, 0, 0, 0, 0] + [0, 0, 0, 0, 0, 0, 0, 1]
else:
padded_m = m + [0, 1, 1, 0, 0, 0, 0, 0] + [0]*8*(padsize - 2) + [0, 0, 0, 0, 0, 0, 0, 1]
return padded_m
def string_to_bits(s):
'''
Conversion of string to an array of bits (length multiple of 8).
'''
m_bytes = s.encode('utf8')
m_int = int.from_bytes(m_bytes, 'little')
return [ (m_int >> i) & 1 for i in range(len(m_bytes)*8)]
def solve_challenge(token, username):
'''
token: as bytes
username: as string (which is converted to bytes assuming UTF-8 encoding)
'''
# We compute H(key||username) with 128 variables for each bit of the key.
# Output is a list of HASH_SIZE_BITS linear equations with 128 variables.
output = compute_hash(list(Z) + string_to_bits(username))
# We want to solve the system
# M*u = v
# where v is the target vector (i.e., the token), and u the secret key.
M = Matrix(F, HASH_SIZE_BITS, 128)
for i in range(HASH_SIZE_BITS):
for j in range(128):
M[i, j] = output[i].coefficient(Z[j])
# The token is converted as an array of length HASH_SIZE_BITS
# and we add the constant coefficients of the output.
a = int.from_bytes(token, 'little')
v = vector(F, HASH_SIZE_BITS)
for i in range(HASH_SIZE_BITS):
v[i] = ((a >> i) & 1) + output[i].constant_coefficient()
# We solve to get the secret key, and use it to compute the admin token.
key_bits = M.solve_right(v)
token_bits = compute_hash(list(key_bits) + string_to_bits('admin'))
# We put the bits together to form the admin token.
# Beware that `token_bits` are elements in GF(2),
# so we cast into integers with ZZ.
token = b''
for i in range(0, HASH_SIZE_BITS, 8):
token += bytes([sum(ZZ(k)*2**j for j, k in enumerate(token_bits[i:i + 8]))])
print(f'Admin token: {token.hex()}')
if __name__ == "__main__":
argc = len(sys.argv) - 1
if argc != 2:
print("Usage: sage ./solve_straight_line.sage USERNAME TOKEN")
sys.exit()
username = sys.argv[1]
token = sys.argv[2]
solve_challenge(bytes.fromhex(token), username)
Alice au captcha des merveilles (reverse challenge)
Automatic script
solve_auto.ahk
(given below).
Run "notepad.exe"
Run "notepad.exe"
Sleep 2000
Loop 135 {
Send "!{Tab Down}"
Send "!{Tab Up}"
Sleep 150
Send "!{Tab Down}"
Send "!{Tab Up}"
Sleep 50
Send "#"
Sleep 50
Send "{Esc}"
Sleep 50
Send "#"
Sleep 50
Send "{Esc}"
Sleep 50
Sleep 100
Run "notepad.exe"
Sleep 750
Send "!{Space}"
Sleep 400
Send "{Down 3}"
Sleep 100
Send "{Enter}"
Sleep 50
Send "!{Tab down}"
Send "!{Tab up}"
Sleep 150
Send "!{Tab down}"
Send "!{Tab up}"
Sleep 100
Send "#"
Sleep 50
Send "{Esc}"
Sleep 50
Send "#"
Sleep 50
Send "{Esc}"
Sleep 50
Run "taskkill.exe /im notepad.exe"
}
launch.ps1
(given below) can be used to test the solution.
Start-Process "C:\Users\debug\AppData\Local\Programs\AutoHotkey\v2\AutoHotkey64.exe" -ArgumentList "Z:\solve_auto.ahk"
Sleep 2
Start-Process -FilePath "Z:\alice_au_le_captcha_des_merveilles.exe"
powershell
script.
To test this solution:
1. In a powershell
, run .\server.exe
.
2. In another powershell
, run .\launch.ps1
.
After the execution of the challenge, something like this should appear: Patch
Patch 1
push rbp
push rbx
sub rsp,0x18
lea rbp,[rsp+0x10]
mov rbx,rcx
mov QWORD PTR [rbp+0x28],rdx
mov QWORD PTR [rbp+0x30],r8
mov edx,r9d
mov eax,DWORD PTR [rbp+0x40]
mov BYTE PTR [rbp+0x38],dl
mov BYTE PTR [rbp-0x4],al
movzx edx,BYTE PTR [rbx]
mov rax,QWORD PTR [rbp+0x28]
mov BYTE PTR [rax],dl
mov edx,DWORD PTR [rbx+0x4]
mov rax,QWORD PTR [rbp+0x28]
add rax,0x1
mov BYTE PTR [rax],dl
cmp QWORD PTR [rbp+0x30],0x0
je 0x56
mov rax,QWORD PTR [rbp+0x28]
add rax,0x2
mov edx,DWORD PTR [rbx+0xd]
mov DWORD PTR [rax],edx
movzx edx,WORD PTR [rbx+0x11]
mov WORD PTR [rax+0x4],dx
jmp 0x6b
mov rax,QWORD PTR [rbp+0x28]
add rax,0x2
mov edx,DWORD PTR [rbx+0x13]
mov DWORD PTR [rax],edx
movzx edx,WORD PTR [rbx+0x17]
mov WORD PTR [rax+0x4],dx
cmp BYTE PTR [rbp+0x38],0x0
je 0x88
mov rax,QWORD PTR [rbp+0x28]
add rax,0x8
mov edx,DWORD PTR [rbx+0x19]
mov DWORD PTR [rax],edx
movzx edx,WORD PTR [rbx+0x1d]
mov WORD PTR [rax+0x4],dx
jmp 0x9d
mov rax,QWORD PTR [rbp+0x28]
add rax,0x8
mov edx,DWORD PTR [rbx+0x27]
mov DWORD PTR [rax],edx
movzx edx,WORD PTR [rbx+0x2b]
mov WORD PTR [rax+0x4],dx
cmp BYTE PTR [rbp-0x4],0x0
je 0xb2
mov rax,QWORD PTR [rbp+0x28]
lea rdx,[rax+0xe]
mov eax,DWORD PTR [rbx+0x2d]
mov DWORD PTR [rdx],eax
jmp 0xbf
mov rax,QWORD PTR [rbp+0x28]
lea rdx,[rax+0x8]
mov eax,DWORD PTR [rbx+0x31]
mov DWORD PTR [rdx],eax
mov rax,QWORD PTR [rbp+0x28]
add rax,0x12
mov rdx,QWORD PTR [rbx+0x37]
mov QWORD PTR [rax],rdx
mov rdx,QWORD PTR [rbx+0x3d]
mov QWORD PTR [rax+0x6],rdx
nop
add rsp,0x18
pop rbx
pop rbp
ret
shellcode
is XORed, so the opcodes would have to be patched in an XORed manner.
In addition, the TLS Callback executed at each Thread attach or Process attach will check the integrity of the shellcode.
If the shellcode does not have the expected hash, the binary will stop executing.
You will then need to patch the TLS Callback either with a 0xC3
at the start of the function, or by patching the calls to ExitProcess()
. Patch 2
...
mov rax, [rbp+arg_8] ; jumptable 0000000140009520 case 0
mov dword ptr [rax], 0
mov rax, [rbp+arg_10]
mov dword ptr [rax], 80FFh
jmp loc_14000963B
mov rax, [rbp+arg_8] ; jumptable 0000000140009520 case 1
mov dword ptr [rax], 9
mov rax, [rbp+arg_10]
mov dword ptr [rax], 9
jmp loc_14000963B
...
rax
with => dword ptr [rax], 0
and dword ptr [rax], 0x80FF
, or change it to switch case by setting 0 to always reach case 0.
If you launch the challenge like this, the user will just have to wait for the captcha to complete itself.
Because at least one event will be captured by the binary, validating each step bit by bit.
Once again, this function is protected by TLS Callback.
So you’ll also need to think about patching the callback. Step by step
.data
).
Alternatively, the files could be made non-writable by the client, so there’s nothing more to develop; all you have to do is wait for the captcha to finish.