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 16byte 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 [rbp4], 0
.text:4004D2 mov dword ptr [rbp8], 46544330h
.text:4004D9 mov dword ptr [rbp0Ch], 4E52BCE7h
.text:4004E0
.text:4004E0 main_switch: ; CODE XREF: .text:loc_7635AC
.text:4004E0 mov eax, [rbp0Ch]
.text:4004E3 mov ecx, eax
.text:4004E5 sub ecx, 8000DEEAh
.text:4004EB mov [rbp10h], eax
.text:4004EE mov [rbp14h], ecx
.text:4004F1 jz loc_728049
.text:4004F7 jmp $+5
.text:4004FC
.text:4004FC loc_4004FC: ; CODE XREF: .text:00000000004004F7
.text:4004FC mov eax, [rbp10h]
.text:4004FF sub eax, 8001EACAh
.text:400504 mov [rbp18h], eax
.text:400507 jz loc_73E38A
.text:40050D jmp $+5
.text:400512
.text:400512 loc_400512: ; CODE XREF: .text:000000000040050D
.text:400512 mov eax, [rbp10h]
.text:400515 sub eax, 8003CC64h
.text:40051A mov [rbp1Ch], 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 32bit integers. Also, during the case probing, some intermediate data is written to the stack (i.e. [rbp14h] 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.
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 crossreferences from cryptorelated 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 LLVMrelated code and see how exactly these values are used. But in the obfuscated binary there are not so many randomlooking 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.10ubuntu1~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?
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 8bit values to 32bit 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 4round 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 onebyte 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):
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 bytebybyte: guess byte of the key, decrypt through SBox, check difference tuple (note that the middle XK does not affect it). We are truncating the differences of 32bit values to differences of 8bit 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.
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 8bit nonlinear SBox. Nonce is constant and hardcoded (note that it is increased by 1 before calling encrypt, as a bigendian 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~}
]]>
http://mslc.ctf.su/wp/0ctf2017qualszer0llvm/feed/
0

0CTF 2017 Quals – OneTimePad 1 and 2
http://mslc.ctf.su/wp/0ctf2017qualsonetimepad1and2/
http://mslc.ctf.su/wp/0ctf2017qualsonetimepad1and2/#respond
Mon, 20 Mar 2017 07:54:15 +0000
http://mslc.ctf.su/?p=4095
Continue reading »]]>
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 LCGstyle 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' * ((32len(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 = "OneTime 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^{N1} + A^{N2} + \ldots + 1) = B(A^N1)/(A1)$.
Therefore, we have the following equations:
 $PRNG_1 = key$;
 $PRNG_2 = A^N \cdot PRNG_1 + B(A^N1)/(A1)$;
 $PRNG_3 = A^{N^2} \cdot PRNG_2 + B(A^{N^2}1)/(A1)$;
 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 $(A1)$:
$PRNG_2 \cdot (A1) = A^N \cdot PRNG_1 \cdot (A1) + B(A^N1),$
$\Leftrightarrow A^N = \frac{PRNG_2 \cdot (A1) + 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 128bit size is quite practical. Indeed, sage can do it in a few minutes:
from sage.all import *
plain = "OneTime 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**1282) # 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!!}
]]>
http://mslc.ctf.su/wp/0ctf2017qualsonetimepad1and2/feed/
0

33C3 CTF 2016 – beeblebrox (Crypto 350)
http://mslc.ctf.su/wp/33c3ctf2016beeblebroxcrypto350/
http://mslc.ctf.su/wp/33c3ctf2016beeblebroxcrypto350/#respond
Sun, 19 Mar 2017 22:30:30 +0000
http://mslc.ctf.su/?p=4083
Continue reading »]]>
Make bad politicians resign!
nc 78.46.224.72 2048
Summary: factorizationbased 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, n1, 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, n1, 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
]]>
http://mslc.ctf.su/wp/33c3ctf2016beeblebroxcrypto350/feed/
0

hack you spb @ 17 Oct 2016
http://mslc.ctf.su/wp/hackyouspb/
http://mslc.ctf.su/wp/hackyouspb/#respond
Sat, 15 Oct 2016 17:16:09 +0000
http://mslc.ctf.su/?p=4077
Continue reading »]]>
Remember hack you CTF? Yeah, that random event that we throw for our freshmen and everyone interested. We’re hosting a new one.
It’s fall already and that means the new CTF season is starting, and so is the new academic year in the universities.
This is the time when we want to attract more freshmen into our CTF tarpit. Specifically, to our SPbCTF meetups in our city.
So we are running — a CTF.
But it’s not just for the freshmen. Wouldn’t it be fun to allow the whole world to beat the shit out of our firstyear students, right? So we are opening hack you spb to everyone interested, just separating the scoreboards: one for the world and other just for confirmed SPbCTF fresh blood (bonus: if you manage to soceng our padawans for a verification string, you can compete in that special chart too).
Registration open: October 15th, 2016 — October 21st, 2016
Game starts: October 17th, 2016 15:00 UTC
Game ends: October 21st, 2016 15:00 UTC
Sign up: http://hackyou.ctf.su/
Don’t expect it to be challenging, it will be more of a speedhack contest.
So in just a few words: New hack you. Schoollevel tasks. October 17th.
]]>
http://mslc.ctf.su/wp/hackyouspb/feed/
0

HITCON CTF QUALS 2016 – Reverse (Reverse + PPC 500)
http://mslc.ctf.su/wp/hitconctfquals2016reversereverseppc500/
http://mslc.ctf.su/wp/hitconctfquals2016reversereverseppc500/#respond
Tue, 11 Oct 2016 18:13:16 +0000
http://mslc.ctf.su/?p=4060
Continue reading »]]>
At least our ETA is better than M$.
http://xkcd.com/612/
Summary: optimizing an algorithm using Treap data structure and CRC32 properties.
After reverseengineering 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 32bit 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 CRCrelated 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[i1], 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}.
]]>
http://mslc.ctf.su/wp/hitconctfquals2016reversereverseppc500/feed/
0

HITCON CTF QUALS 2016 – PAKE / PAKE++ (Crypto 250 + 150)
http://mslc.ctf.su/wp/hitconctfquals2016pakepakecrypto250150/
http://mslc.ctf.su/wp/hitconctfquals2016pakepakecrypto250150/#respond
Mon, 10 Oct 2016 17:06:59 +0000
http://mslc.ctf.su/?p=4041
Continue reading »]]>
pake1.rb
pake2.rb
Summary: attacking passwordbased key exchange schemes based on SPEKE with MITM.
In these two challenges we were given a service which simply sends a flag after a session of some Password Authenticated Key Exchange (PAKE) scheme.
PAKE (The first challenge)
fail unless is_safe_prime(p)
# each password is an integer in 1..16
passwords = IO.readlines('passwords').map(&:to_i)
fail unless passwords.grep_v(1..16).empty?
fail unless passwords.size == 11
passwords.map!{pass Digest::SHA512.hexdigest(pass.to_s).to_i(16)}
key = 0
puts "p = #{p}"
passwords.each.with_index(1) do password, i
puts "Round #{i}"
w = pow(password, 2, p) # NO to Legendre
b = 2 + SecureRandom.random_number(p  2)
bb = pow(w, b, p)
puts "Server send #{bb}"
aa = gets.to_i
if aa < 514  aa >= p  514
puts 'CHEATER!'
exit
end
k = pow(aa, b, p)
key ^= Digest::SHA512.hexdigest(k.to_s).to_i(16)
end
flag ^= key
puts "Flag is (of course after encryption :D): #{flag}"
The server has 11 very small password – integers from 1 to 16. For each password a simple DiffieHellman key exchange is done, however the generator depends on the password (this is SPEKE protocol). Then all the resulting keys for all passwords are xored.
Clearly, we can’t guess all the keys at once. On the other hand, if we don’t guess even a single key, the final key is xored with something “random” to us, something about what we don’t have any information at all. From this point of view this challenge looks unsolvable and I was stuck on it for the whole day. The challenge author even increased the points from 150 to 250 since nobody solved it for some time.
The key idea is that we can do a ManInTheMiddle attack here, between the server and… the server itself! Indeed, the server is the only “person” who knows the passwords and can pass the protocol in a meaningful way. We can maintain two connections and for each password $p$ we will exchange the $g_p^a$ and $g_p^b$ send to us by the server in two connections. Then they will obtain same secret for each of the passwords: $g_p^{ab}$ and the same final master key too!
It is cool that we managed to connect the server to itself, but.. the flag is still encrypted… However, now we can perform attacks on single passwords! Indeed, if we guess a password $p$ correctly, then we can compute the generator $g_p$ and perform the basic MITM attack against classical DiffieHellman. For example, we allow the servers to exchange keys related to the first 10 passwords, and then for the 11th password $p$ we try to guess it and send $g_p^1$ to both servers. Then the servers will compute 11th shared keys $g_p^{a}$ and $g_p^{b}$ which they have already sent to us. Now, the final xor of the keys will be different on two servers. However, if our password guess was correct, we can unxor the last shared keys and see that the new shared keys are now equal. Thus we have obtained a way to bruteforce each password separately.
Here is solution script (takes a while due to heavy PoW):
import os
from hashlib import sha512, sha1
from libnum import *
from sock import Sock
def getHashes(p1, p2):
# bruteforce proofofwork in C
data = os.popen("./sha1 '%s' '%s'" % (p1, p2)).read()
return data.split()
def numhash(n):
return s2n(sha512(str(n)).digest())
p = 285370232948523998980902649176998223002378361587332218493775786752826166161423082436982297888443231240619463576886971476889906175870272573060319231258784649665194518832695848032181036303102119334432612172767710672560390596241136280678425624046988433310588364872005613290545811367950034187020564546262381876467
pws = [8, 15, 9, 15, 7, 7, 13, None, 10, 15, None]
pws = [None] * 11
for pwi in xrange(11):
if pws[pwi] is not None:
continue
for pwc in xrange(1, 17):
print pwi, pwc, "getting prefixes"
f1 = Sock("52.197.112.79 20431")
f2 = Sock("52.197.112.79 20431")
prefix1 = f1.read_until_re(r"prefix: (\S+)").decode("base64")
prefix2 = f2.read_until_re(r"prefix: (\S+)").decode("base64")
sol1 = sol2 = getHashes(prefix1, prefix2)
f1.send_line(str(sol1.encode("base64").strip()))
f2.send_line(str(sol2.encode("base64").strip()))
recnum1 = None
recnum2 = None
for i in xrange(11):
num1 = int(f1.read_until_re(r"Server send (\d+)"))
num2 = int(f2.read_until_re(r"Server send (\d+)"))
if i != pwi:
f1.send_line(str(num2))
f2.send_line(str(num1))
else:
gp = pow(numhash(pwc), 2, p)
f1.send_line(str(gp))
f2.send_line(str(gp))
recnum1 = numhash(num1)
recnum2 = numhash(num2)
f1.read_until("Flag is")
f2.read_until("Flag is")
res1 = int(f1.read_until_re(r": (\w+)"))
res2 = int(f2.read_until_re(r": (\w+)"))
if res1 ^ recnum1 == res2 ^ recnum2:
print "MATCH!", "pw[%d] = %d" % (pwi, pwc)
pws[pwi] = pwc
print pws
break
After we have obtained the all passwords, we can implement the legitimate session and get the flag: hitcon{73n_w34k_p455w0rd5_c0mb1n3d_4r3_571ll_wE4k_QQ}
Pake++ (The second challenge)
In the second challenge, the scheme was slightly altered. There are now 8 different prime moduli, each time the prime to be used is chosen at random. Also there is only one password and it has very high entropy. Finally, we have two rounds at each time. Each round a different prime is chosen, but the password is always the same.
primes = <<EOS.lines.map(&:to_i)
285370232948523998980902649176998223002378361587332218493775786752826166161423082436982297888443231240619463576886971476889906175870272573060319231258784649665194518832695848032181036303102119334432612172767710672560390596241136280678425624046988433310588364872005613290545811367950034187020564546262381876467
298619967637074381179969535203891334279037109216429406440598651658759350405543564192161651312771530156893413427058175045425757160212464963471306913768112396967665485652095651332292521663596653434043497333804676303590560939622585332770724390627970615864007468306226942254398801398715678948641382895998651226267
298049748677563805780319663628819960854615659462424022507630185085633596966175010904970480952385372951172828141165347514534458425242085310886903778569556019799929063608007455863031014344586675081359600240391009588905845818495639778564642452193502269859322492791462878008970754999615280381704729202349939626979
328517416048692886037451935540065593512994462002198409216093662374867257380362145180594029938263095492728312598227855130627235361314817436633177786884494030007864152308901161140111969980403722704138376439893801083486571959976238463025246682645769458504940248785078868650651872930977987712522710703736396657387
282394389781374199382509925858094901774563649174745796339967395605116291562054020073955455338401167970080036287402587308354538500190221028476399923564634339732689941702464496890308354549764266506953808881910012117986860385319905877301417806557760524821229657147194856430018136428864202922733288830153277890659
323103830544117987011024048071694067643223166857443601288678625077550042396643402625692490043863582981782378717042298512462563264457088124223575174164921578128007273839310675925364961100802146846497562297376569657569712628347758371790366124768858903423043207000026049079160253301902404207920358137230602539643
302103350544659483531131684583742544907887033086695553935842864801685693649953339273310786890360791425112212069129996061391578244584149171226282625727635376367322205108426287980490462476857893899354488802419996817239780781622440165425711955277936322570946121224712118336303389972089563531346006960196427704219
274405967560432033789798999119890457360306712028734789799802515309420810630484361406114488871989143593759704555407742610118391422809479638606936598462341976320928990585877751200977318034499800109548890002507225944134922612505410751207789339655283747859299017222586400523098060723763539689025418276604564593619
EOS
fail unless primes.all?{p is_safe_prime(p)}
password = IO.read('password2').strip
fail unless password =~ /\A[azAZ09]{20}\z/
password = Digest::SHA512.hexdigest(password).to_i(16)
key = 0
2.times do i
puts "Round #{i + 1}"
p = primes[SecureRandom.random_number(primes.size)]
puts "p = #{p}"
w = pow(password, 2, p) # NO to Legendre
b = 2 + SecureRandom.random_number(p  2)
bb = pow(w, b, p)
puts "Server send #{bb}"
aa = gets.to_i
if aa < 514  aa >= p  514
puts 'CHEATER!'
exit
end
k = pow(aa, b, p)
key ^= Digest::SHA512.hexdigest(k.to_s).to_i(16)
end
flag ^= key
puts "Flag is (of course after encryption :D): #{flag}"
Now that we know the MITM idea, it is not much harder. If we manage to get the the same primes in two connections and simply exchange the values sent, we will get same key in both connections. Even if the keys are different, we can’t get the flag from having $flag \oplus stuff1$ and $flag \oplus stuff2$. For example, if we xor these two things together, the flag is cancelled. To avoid this we simply add the third connection! Then triple xor of $flag \oplus stuff$ will still have $flag$ and hopefully we can cancel all the stuff.
Indeed, we can try to make the following key exchanges between connections $a, b, c$:
$$\begin{split}
k_1 = key\_exchange(a, b), \\
k_2 = key\_exchange(a, c), \\
k_3 = key\_exchange(b, c).
\end{split}$$
Then we will get:
$$\begin{split}
flag \oplus k_1 \oplus k_2 ~\mbox{from}~ a, \\
flag \oplus k_1 \oplus k_3 ~\mbox{from}~ b, \\
flag \oplus k_2 \oplus k_3 ~\mbox{from}~ c.
\end{split}$$
Xoring everythin results in the flag!
There are some technicalities. We need to distribute these three exchanges between all 2 rounds in all 3 connections. This is easy, since the servers not necessarily need to exchange in the same round.
A slightly harder problem is to wait until the primes match in a right way. We can do the following:
 Open several connections until there is a pair of connections with the same prime $p_{ab}$ in the first round.
 Perform the exchange between them.
 See the second round primes $p_a$ and $p_b$.
 Find a client in the pool or make new connections until we get a connection with the first round prime equal to either $p_a$ or $p_b$.
 Perform the exchange between the corresponding connections.
 Now we have no choice and only one exchange left to do. If the primes do not match, we repeat the full algorithm.
 Otherwise, we get the flag.
The flag: hitcon{m17m_f0r_pr0f1t_bu7_S71ll_d035n7_kn0w_p455w0rd}
]]>
http://mslc.ctf.su/wp/hitconctfquals2016pakepakecrypto250150/feed/
0

TUM CTF 2016 – Shaman (Crypto 500)
http://mslc.ctf.su/wp/tumctf2016shamancrypto500/
http://mslc.ctf.su/wp/tumctf2016shamancrypto500/#comments
Mon, 03 Oct 2016 20:02:07 +0000
http://mslc.ctf.su/?p=4003
Continue reading »]]>
Oh great shaman!
Somehow the village idiot got his hands on this fancy control machine controlling things. Obviously, we also want to control things (who wouldn’t?), so we reverseengineered the code. Unfortunately, the machine is cryptographically protected against misuse.
Could you please maybe spend a few seconds of your inestimably valuable time to break that utterly simple cryptosystem and enlighten us foolish mortals with your infinite wisdom?
nc 104.155.168.28 31031
NOTE: Since I am really bad at math, the share received from the server won’t be accepted when sent back. Don’t get confused by this — the challenge is solvable nevertheless.
Summary: hash length extension, manipulation of secret shares.
The challenge server consists of a mix of secret sharing, authentication and command execution. Briefly, it works as follows:
 The server generates a command of the form
cmd = checksum + "echo Hello!#" + [32 random bytes]
,
and interprets it as an integer modulo 256bit prime $p$.
 The server splits the command into 3 shares with threshold equal to 2, meaning that knowing any 2 of the 3 shares is enough to recover the secret. Shamir’s secret sharing is used (using polynomials over $\mathbb{F}_p$).
 One of the shares is signed by prepending a MAC:
signed_share = SHA256(key + share) + share
.
 The signed share is sent to the client.
 Now the server listens to queries from the client, the number is limited by 0x123.
 In each query, the client cant send a signed share.
 The server combines it with another share.
 The server checks the checksum and if it is good, executes the command.
Modifying the share
First observation: if the MAC is good enough, we can’t generate any other signed share and therefore we can’t execute any command other than “echo Hello”. Therefore we have to attack the MAC. Luckily, it is vulnerable to the well known Hash Length Extension attack. There is a very convenient tool called hash_extender for performing the attack.
The attack allows us, given a hash of the form SHA256(key + share)
, to append some uncontrolled padding and, additionally, arbitrary amount of data to the hash input. This appending is done in such a way that we can compute the new hash value without knowing the key. That is, we can generate more signed shares of the form share + [padding] + [any data]
.
What does it give to us? The share has the following format: (32byte $x$, 32byte $y$) and the values are packed in the Little Endian order (least significant bytes first). When the values are unpacked, everything after $y$ will be considered as a part of $y$. Even if it’s more than 32 bytes, it will be taken modulo $p$. That is, we can modify the share’s $y$ by adding $(2^{256} \times padding + 2^{256+e} \times value)$ to it, where $e$ is determined by padding. Since it is reduced modulo $p$ afterwards, we can actually obtain arbitrary $y_{wanted}$. Indeed, setting $$value \equiv (y_{wanted} – y_{orig} – 2^{256} \times padding) / 2^{256+e} \pmod {p}$$ will do the job.
Here’s python code for modifying the signed share’s $y$ component to arbitrary value:
# hash_extender is a wrapper around the command line hash_extender
# trick: first we append nothing to obtain the base value
testdata, _sig = hash_extender(
data=share,
hash=sig,
append="",
hashname="sha256",
length=keylen
)
# y = 2^e * c + sometrash (mod p)
# c = (y  sometrash) / 2^e (mod p)
e = len(testdata) * 8
inv2e = invmod(2**e, p)
trash = from_bytes(testdata) % p
def gen_share_with_y(y):
c = (y  trash) * inv2e % p
newdata, newsig = hash_extender(data=share, hash=sig, append=to_bytes(c), hashname="sha256", length=keylen)
return newsig + newdata.encode("hex")
Digging into the secret sharing
Our final goal is to execute some evil command on the server. The command is obtained from combining the two shares: our share and one of the server’s shares. We can partially modify our share, but the server’s share stays untouched! Is it still possible to modify the command in a meaningful way? Hoping for lucky random commands (like “sh\n”) is a bad idea, since the command is prepended by a SHA256 checksum…
Let’s look closer at the sharing scheme. When splitting the shares, the server creates a random polynomial of degree 1 (it has (threshold) coefficients) with the constant coefficient equal to the secret being shared. Then it is evaluated on three random points, and the three $(x_i, y_i)$ pairs are the shares. For a polynomial of degree 1, any two different points are enough to recover the full polynomial, that’s how it works! The interpolation algorithm is implemented on the server and we actually don’t care about the implementation, we care only about semantics of it.
We can think about the combining step as follows: the server has two shares $(x_1, y_1)$ and $(x_2, y_2)$ and it wants to find two coefficients $a, b$ in the finite field $\mathbb{F}_p$ for which the equation $a*x + b = y$ holds for both shares. That is, it solves the following linear system with two unknowns $a, b$:
$$\begin{cases}
a x_1 + b = y_1,\\
a x_2 + b = y_2,\\
\end{cases}$$
Then $b$ is the constant coefficient in the polynomial and so is the initial secret.
Let’s say our share is number 2, so we control $y_2$. Let’s solve the system for $b$:
$$\begin{split}
a & = & \frac{y_1 – y_2}{x_1 – x_2}, \\
b & = & y_2 – a x_2 = y_2 – x_2\frac{y_1 – y_2}{x_1 – x_2} = y_2 – u + y_2 / v = w y_2 – u.
\end{split}$$
Note that we have done some variable replacements, since we don’t really care about all values $(x_1, y_1, x_2)$, but only about the minimum number of expressions involving them. We obtained that the algorithm the server uses to combine the shares is basically a linear function of $y_2$, which we control! So we have only two unknowns, the coefficients $w$ and $u$! How can we learn them? Obviously we need to get some information from the server, since we know nothing about the share #1.
Recovering the linear coefficients
For simplicity, let’s rename variables and assume that the server computes $secret \equiv a y_2 + b \pmod{p}$ ($a$ and $b$ are the new unknowns, unrelated to previous ones).
Since we have no information about $a, b$ we can’t set $secret$ in a meaningful way first. Therefore the checksum check will fail… and the server will leak a part of the $secret$ that he combined!
def unpack(msg, key = b''):
tag = msg[:SHA.digest_size]
if tag == SHA.new(key + msg[len(tag):]).digest():
return msg[len(tag):]
print('bad {}: {}'.format(('checksum', 'tag')[bool(key)], tohex(tag)))
Unluckily, the server leaks only 256 least significant bits of the secret (from 512). To sum up, we have the following oracle:
(Share with $y_2$) $\mapsto$ (256 LSBs of the resulting secret).
First, we can query $y_2 = 0$ and obtain the LSBs of $secret = (0y_2 + b) \mod{p} = b$. Then we can query $y_2 = 1$ and obtain LSBs of $(a + b) \mod{p}$, which quite often will be equal to just LSBs of $a + b$, therefore we can learn also LSBs of $a$. So, we can learn 256 LSBs of $a$ and $b$ in 2 queries.
Now, let’s try to shift $a$ down so that we learn the MSBs of it. To do this we set $y_2$ to $2^{256} \pmod{p}$. However this is not exactly the shift due to modulo $p$. What we obtain is:
$$(y_2 a + b) \mod{p} = (2^{256} a + b) \mod {p} = ((a + kp) / 2^{256} + b) \mod {p},$$
where $k$ is such that $a + kp \equiv 0 \pmod{2^{256}}$ and the last division is done in integers. Luckily, we know LSBs of $a$, that is, we know $a \mod{2^{256}}$, we can predict LSBs of $k$:
$$k \equiv a/p \pmod{2^{256}}.$$
Now comes an interesting point. The smallest such $k = k_0$ is less than $2^{256}$. All other $k$ satisfying this condition can be described as $k = k_0 + t2^{256}$ for some integer $t$. Note that:
$$\begin{split}
((a + kp) / 2^{256} + b) \mod{p} & = & ((a + k_0p + 2^{256}tp) / 2^{256} + b) \mod{p}\\
& = & ((a + k_0p) / 2^{256} + b + tp) \mod{p},
\end{split}$$
…and $tp$ goes away due to modulo! This means that we need to know only $k \equiv a/p \mod{2^{256}}$, which we can compute as mentioned before.
Moreover, we can guess whether the last addition of $b$ overflowed the modulo or not. Let’s assume that it did not, it is quite probable. Then for the query $y_2 = (2^{256} \mod{p})$ we get (note the modulo change):
$$r = (2^{256}a + b) \mod{p} \equiv (a + kp) / 2^{256} + b \pmod{2^{256}}.$$
Then using known LSBs of $b$ we get:
$$2^{256} (r – b) \equiv a + kp \pmod {2^{512}}.$$
$$a \equiv 2^{256} (r – b) – kp \pmod {2^{512}}.$$
We learned the full $a$! Recall that we assumed that addition of $b$ does not overflow the modulo. We can guess this and try to add $p$, or choose first $y_0=2^{128}$ instead of $y_0=2^{256}$ and learn full $a$ in two steps. This trick allows us to notice the additional subtraction of $p$ since in the first query half of the obtained bits should match the known bits of $a$.
Ok, how to learn full $b$ now? Sadly, we can’t shift it and the effect of it’s high bits is quite limited. We now can exploit the effect of $b$ on overflowing the modulo: for specially crafted queries we will check how many times we overflow the modulo and deduce some information about $b$. Recall that we can query
$$(ay_0 + b) \mod {p} = ay_0 + b kp$$
for some $k$. Note that since $b < p$, its value may change $k$ only by $1$. Let's perform a binary search of the real value of $b$. Assume that we know that $b_l \le b \le b_r$ for some known $b_l, b_r$. Let $mid = (b_l + b_r) / 2$. Then we can craft such $y_0$ that for fall $b_l \le b < mid$ we will get known $k = k_0$ and for $mid \le b \le b_r$ we will get $k = k_0  1$. Such $y_0$ can be obtained as $y_0 \equiv mid / a \pmod{p}$, since then
$$(y_0 a + b) \mod{p} = (b  mid) \mod{p}$$
and then we will get either $b  mid$ or $b  mid + p$. Since we know LSBs of $b$ and $mid$, we can easily distinguish between two cases and divide the search space for $b$ by 2. Note that we need to learn only 256 MSBs of $b$, so we are good with around 256 queries for this binary search.
To sum up, we need 2 queries to learn LSBs of $a$ and $b$, 2 queries to learn full $a$ and at most 256 queries to learn full $b$.
Here’s python code for recovering $a, b$:
def oracle(y):
assert 'what have you got' in f.read_line()
f.send_line(gen_share_with_y(y))
res = f.read_line()
assert "bad checksum" in res, "oracle failed: %r" % res
return from_bytes(res.split()[1].decode("hex"))
MOD = 2**256
blow = oracle(0) % MOD
alow = (oracle(1)  blow) % MOD
i2 = invmod(2, p)
a = alow
for e in (128, 256):
mod2 = 2**e
k = invmod(p, mod2) * (a) % mod2
assert (a + k * p) % mod2 == 0
res = (oracle(i2**e)  blow) % MOD
shifted = ((res << e)  k * p) % (MOD << e)
# did we get additional p because of b?
if shifted % MOD != a % MOD:
res = (oracle(i2**e)  blow + p) % MOD
shifted = ((res << e)  k * p) % (MOD << e)
assert shifted % MOD == a % MOD
a = shifted
print "Learned full a:", tohex(a)
if a >= p:
assert 0, "Failed, seems in the second query there was an overflow."
ia = invmod(a, p)
bl = 0
br = p  a  1
while bl < br:
mid = (bl + br) // 2
x = ia * (mid) % p
assert (x*a + mid) % p == 0
res = oracle(x) % MOD
if res == (blow  mid) % MOD:
bl = mid
elif res == (blow  mid + p) % MOD:
br = mid  1
else:
assert 0, "Failed, seems in the second query there was an overflow."
if bl >> 256 == br >> 256:
break
b = br  br % MOD + blow
print "Learned full b:", tohex(b)
if b >= p:
assert 0, "Failed, seems in the second query there was an overflow."
Forging the Evil Command
Finally, when we have learned $a$ and $b$, we can forge and execute arbitrary commands:
def pack(msg, key=''):
return SHA.new(key + msg).digest() + msg
packed = from_bytes(pack(r"echo PWNED; bash"))
assert packed < p
y = (packed  b) * ia % p
assert (a * y + b) % p == packed
print "Command executing:"
assert 'what have you got' in f.read_line()
f.send_line(gen_share_with_y(y))
f.interact()
In half of the runs we will get a shell!
]]>
http://mslc.ctf.su/wp/tumctf2016shamancrypto500/feed/
1

TUM CTF 2016 – Tacos (Crypto 400)
http://mslc.ctf.su/wp/tumctf2016tacoscrypto300/
http://mslc.ctf.su/wp/tumctf2016tacoscrypto300/#comments
Sun, 02 Oct 2016 17:50:57 +0000
http://mslc.ctf.su/?p=3986
Continue reading »]]>
All my fine arts and philosophy student friends claim discrete logarithms are hard. Prove them wrong.
nc 104.198.63.175 1729
Summary: bypassing Fermat primality test with Carmichael numbers and solving discrete logarithm using PohligHellman algorithm.
The source code is quite simple:
def is_prime(n):
for _ in range(42):
if pow(random.randrange(1, n), n  1, n) != 1:
return False
return True
q, p = int(input(), 0), int(input(), 0)
assert 2 ** 1024 < p < q < 2 ** 4096
assert is_prime(q)
assert not (q  1) % p
assert is_prime(p)
g = random.randrange(1, q)
y = pow(g, random.randrange(q  1), q)
print(hex(g), hex(y))
signal.alarm(60)
# good luck!
if pow(g, int(input(), 0), q) == y:
print(open('flag.txt').read().strip())
We need to supply a large prime modulo $q$ with large multiplicative subgroup of order $p$. And then the server will ask us to perform a discrete logarithm in this group. There are no known algorithms for such setting. However, here we can bypass some conditions  the primality test. is_prime uses Fermat test to check for primality. It is well known that Carmichael numbers bypass such test easily. Note that Carmichael numbers do not bypass Fermat test in 100% of cases! They bypass if only when witness (base) is coprime with $p$, otherwise the base yields a divisor of $p$. Nonetheless, for nonCarmichael and nonprime numbers Fermat test declines composite numbers with much higher probability, therefore Carmichael numbers are our only hope here.
Bypassing primality test
Assume that we can generate Carmichael numbers of ~3000 bits with lots of small prime factors. There are two paths here:
 Generate Carmichael $q$ (false prime) until we can find a large real prime factor $p$ of $q1$. In such case we will be able to do discrete log modulo each prime factor of $q$ and then combine them using CRT.
 Generate Carmichael $p$ (false prime) until we can find a large real prime $q$ for which $p  (q  1)$. In this case we will have to do discrete log modulo large prime $q$, but separately for each of the small subgroups (obtained from factorization of $p$).
For both ways we would need PohligHellman method, but in the second case the arithmetic will be much heavier (since all small logs must be done modulo large $q$). Also it is easier to find small prime $p$ than large prime $q$. Therefore, the first way is preferrable.
The way we will find $p$ is simple: first we obtain some Carmichael number $q$, then we remove small factors from $q$ by let's say trial division. Then we check if the result is prime. It should happen quite often!
Now the problem is  how to generate Carmichael numbers? One of the basic algorithms is Erdos algorithm, described for example in this paper:
Here is my implementation of its simplest randomized version in Sage:
from sage.all import *
import operator
first_primes = list(primes(10**7))
# it is important to find good lambda
# the algorithm highly depends on it
# this one is from some paper
factors = [2**5, 3**2, 5, 7, 11, 13, 17]
lam = reduce(operator.mul, factors)
# lam = /\ = 24504480
P = []
for p in primes(min(10000000, lam)):
# do not include large primes so that Fermat test
# has higher probability to pass
if p < 400:
continue
if lam % p and lam % (p  1) == 0:
P.append(p)
print "P size", len(P)
prodlam = reduce(lambda a, b: a * b % lam, P)
prod = reduce(lambda a, b: a * b, P)
# faster prime checks
proof.arithmetic(False)
while 1:
numlam = 1
num = 1
# we are building random subset {p1,...,p20}
# and checking the subset at each step
for i in xrange(20):
p = choice(P)
numlam = numlam * p % lam
num = num * p
if numlam != prodlam or prod % num != 0:
continue
q = prod // num
print "candidate", q
print factor(q)
print
ps = [p for p, e in factor(q)]
is_carm = ( (q  1) % lcm([p1 for p in ps]) == 0 )
if not is_carm:
continue
# now check if q  1 = small primes * large prime p
# since we need to know such p
# should happen by chance quite often
t = q  1
for p in first_primes:
while t % p == 0:
t //= p
if is_prime(t):
print "Good!"
print "q =", q, "#", len(q.bits()), "bits"
print "p =", p, "#", len(p.bits()), "bits"
print
open("candidates", "a").write("q = %d\n" % q)
open("candidates", "a").write("p = %d\n\n" % p)
# solution:
q = 59857999685097510058704903303340275550835640940514904342609260821117098340506319476802302889863926430165796687108736694628663794024203081690831548926936527743286188479060985861546093711311571900661759884274719541236402441770905441176260283697893506556009435089259190308034118717196693029323272007089714272903225216389846915864612112381878100108428287917605430965442572234711074146363466926780699151173555904751392997928289187479977403795442182731620805949932616667193358004913424246140299423521
p = 62524500763431441748481642690708441489776386892126187254070724779722504054292898512943722029745633023670685560530873804922086432332770562190282869379418979194485569141426416326281184435782910942683630763055432514713734358605440032411156894024431274583468338817956399407807741136790938228633811230955007519469063632837378284757301519562677312080310137628454021752642170957113443979650111115572728879157653822412120689975685302219422272533094042684647476957834486986793341
It takes around 3060 minutes to find a solution, for example:
q = 409 * 421 * 443 * 463 * 521 * 613 * 617 * 631 * 661 * 673 * 859 * 881 * 911 * 937 * 953 * 991 * 1021 * 1123 * 1171 * 1249 * 1321 * 1327 * 1361 * 1429 * 1531 * 1871 * 1873 * 2003 * 2081 * 2143 * 2311 * 2381 * 2731 * 2857 * 2861 * 3061 * 3169 * 3361 * 3433 * 3571 * 3697 * 4421 * 4621 * 5237 * 5281 * 6007 * 6121 * 6553 * 6733 * 7481 * 8009 * 8191 * 8581 * 8737 * 9241 * 9283 * 10711 * 12377 * 13729 * 14281 * 16831 * 17137 * 17681 * 18481 * 19891 * 20021 * 20593 * 21841 * 23563 * 24481 * 25741 * 26209 * 27847 * 29173 * 29921 * 30941 * 34273 * 36037 * 42841 * 43759 * 46411 * 48049 * 52361 * 53857 * 55441 * 63649 * 65521 * 72073 * 72931 * 74257 * 78541 * 79561 * 87517 * 92821 * 96097 * 97241 * 110881 * 116689 * 117811 * 131041 * 145861 * 148513 * 157081 * 180181 * 185641 * 209441 * 235621 * 269281 * 291721 * 314161 * 371281 * 388961 * 445537 * 471241 * 680681 * 700129 * 816817 * 1633633 * 8168161.
Solving the discrete log
After finding good $p$ and $q$, it is easy to solve the rest of the problem. First, since we can't bypass the test in 100% of the times, we have to do a few iterations. Then, if the tests pass, we need to solve the discrete logarithm problem. Luckily, Sage automatically factors the modulus (and all the group orders) and applies PohligHellman method. Since the factors are small, we don't need to supply our factorization to Sage, it finds it quickly automatically. We only have to ask Sage to compute $discrete\_log(v, g)$!
from sage.all import *
from sock import *
q = 59857999685097510058704903303340275550835640940514904342609260821117098340506319476802302889863926430165796687108736694628663794024203081690831548926936527743286188479060985861546093711311571900661759884274719541236402441770905441176260283697893506556009435089259190308034118717196693029323272007089714272903225216389846915864612112381878100108428287917605430965442572234711074146363466926780699151173555904751392997928289187479977403795442182731620805949932616667193358004913424246140299423521
p = 62524500763431441748481642690708441489776386892126187254070724779722504054292898512943722029745633023670685560530873804922086432332770562190282869379418979194485569141426416326281184435782910942683630763055432514713734358605440032411156894024431274583468338817956399407807741136790938228633811230955007519469063632837378284757301519562677312080310137628454021752642170957113443979650111115572728879157653822412120689975685302219422272533094042684647476957834486986793341
itr = 0
while 1:
itr += 1
print "Try #%d" % itr
f = Sock("104.198.63.175 1729")
f.send_line(str(q))
f.send_line(str(p))
ans = f.read_line()
print `ans`
if "Traceback" in ans:
print ans
print f.read_all()
continue
print "Primes are accepted! Getting discrete log"
g, y = [int(ss[2:], 16) for ss in ans.split()]
R = IntegerModRing(q)
x = discrete_log(R(y), R(g))
print "x", x
print "y", y
print "correct ?", pow(g, x, q) == y
f.send_line(str(x))
print f.read_all()
break
After a few iterations, we get the flag: hxp{5cHr0eD1n9eR's_Pr1m3z}
]]>
http://mslc.ctf.su/wp/tumctf2016tacoscrypto300/feed/
1

TUM CTF 2016 – ndis (Crypto 300)
http://mslc.ctf.su/wp/tumctf2016ndiscrypto300/
http://mslc.ctf.su/wp/tumctf2016ndiscrypto300/#respond
Sun, 02 Oct 2016 16:47:55 +0000
http://mslc.ctf.su/?p=3979
Continue reading »]]>
We have a HTTPS server and client talking to each other with you right in the middle! The client essentially executes
curl –cacert server.crt https://nsa.gov
with some magic to redirect the transmitted data to your socket, to which the server responds with a lovely Germanlanguage poem.
NOTE: There is nothing else hosted on the server; no need to bruteforce filenames. Moreover, it may behave untypically due to hackiness.
Your task is to make the client receive a CTFthemed adaption of another German poem instead; to be precise, the HTTP response must consist of the following bytes:
5761 6c6c 6521 2057 616c 6c65 0a4d 616e Walle! Walle.Man
6368 6520 5374 7265 636b 652c 0a44 6173 che Strecke,.Das
7320 7a75 6d20 5a77 6563 6b65 0a46 6c61 s zum Zwecke.Fla
6767 656e 2066 6c69 65c3 9f65 6e2c 0a55 ggen flie..en,.U
6e64 206d 6974 2072 6569 6368 656d 2c20 nd mit reichem, 
766f 6c6c 656d 2053 6368 7761 6c6c 650a vollem Schwalle.
5a75 2064 656e 2050 756e 6b74 656e 2073 Zu den Punkten s
6963 6820 6572 6769 65c3 9f65 6e2e 0a ich ergie..en..
Upon receiving this response from the server, the client sends the flag to you through the same connection used to intercept the HTTPS traffic, so make sure not to overlook it!
Server: https://130.211.200.153:4433
Client: nc 130.211.200.153 9955
(If you just forward everything from one of those ports to the other, the connection succeeds and everything works fine. Then hack.)
NOTE: The setup for this challenge is not entirely trivial, so if you’re confused about unexpected things happening, please contact yyyyyyy on IRC. There is also a good chance something’s broken.
EPIC HINT published six hours before the end: The server’s ciphersuites have been carefully chosen to allow this attack. (Plus the server was patched a little bit.)
Summary: attacking noncerepeating TLS server using AESGCM cipher.
Here we have to implement a maninthemiddle attack against custom TLS server. The ciphersuite used is ECDHEECDSAAES128GCMSHA256 which is normally secure and all other ciphersuites are disabled. After the hint was given we concentrated on this ciphersuite and looked for possible attacks. AESGCM is known to be very weak if the tag length is small. So googling for “tls aes gcm tag length” yielded the recent paper NonceDisrespecting Adversaries: Practical Forgery Attacks on GCM in TLS. It also matches the task name “ndis”.
So the idea of the attack is that when nonces are repeated (due to weak random, for example), then it is possible to recover authentication key from the GCM and make forgeries.
There is a proofofconcept tool by the paper authors: https://github.com/noncedisrespect/noncedisrespect. In order to compile it, we need the latest NTL library.
The main tool there is gcmproxy. This proxy captures TLS packets and waits until nonces are repeated. Then the key is reconstructed and we can modify packets. Note that we can’t decrypt packets, only modify by xor! So one of the problems is to figure out which fragments contain which data to know what to xor and where. The hard part is that each time we debug a forgery, we have to wait until nonces collide on the server. Nonces were 1byte values and they must to collide in the first ~5 packets.
After debugging it a lot, we arrived at the following modification to the forgery function:
func forgeRecord(rec1 *TLSRecord, key *GCMAuthKey) (rec2 *TLSRecord) {
fragment := make([]byte, rec1.Header.FragmentLength)
copy(fragment, rec1.Fragment)
fmt.Println(rec1)
# modify contentlength: 256 > 127
if len(fragment) == 45 {
payload, err := hex.DecodeString("030701")
if err != nil {panic(err)}
xor(fragment[16+8:], payload)
}
# modify poem
if len(fragment) == 280 {
payload, err := hex.DecodeString("3815180316014d38111f665837705c535e55581d6e7e780a171f0a5f2a290e0300000e07025420036f0c1f11657c4c070815114e4d091c1a45a5f0171a2665211a0b534d041b500145010c1816190c46191d18660a19543c5948040e1f036f003501540b45064f3c014e001b0e1d2a1c1d1707000d1d0b1d45acfd161a246574746f20686f726368740a6f74746f3a206d6f7073206d6f70730a6f74746f20686f6666740a0a6f74746f73206d6f7073206b6c6f7066740a6f74746f3a206b6f6d6d206d6f7073206b6f6d6d0a6f74746f73206d6f7073206b6f6d6d740a6f74746f73206d6f7073206b6f747a740a6f74746f3a206f676f74746f676f74740a")
if err != nil {panic(err)}
xor(fragment[8:], payload)
}
rec2 = &TLSRecord{rec1.Header, rec1.SeqNo, fragment}
...
Then we ran it as follows (a few times):
# run proxy
$ ./gcmproxy l=127.0.0.1:5001 r=130.211.200.153:4433 w=127.0.0.1:5002
# connect client to proxy
$ socat v tcp:130.211.200.153:9955 tcp:127.0.0.1:5001 2>&1
...
..............g..4Dp...:&f...[.> 2016/10/02 15:49:28.750616 length=28 from=529 to=556
hxp{NTw1C3:_n0t_ev3n_0nce.}
The flag: hxp{NTw1C3:_n0t_ev3n_0nce.}
PS: the challenge is quite obviously relying on using the PoC tool, because coding the full attack from scratch would take way more time (and is a bit boring).
]]>
http://mslc.ctf.su/wp/tumctf2016ndiscrypto300/feed/
0

CSAW Quals 2016 – Broken Box (Crypto 300 + 400)
http://mslc.ctf.su/wp/csawquals2016brokenboxcrypto300400/
http://mslc.ctf.su/wp/csawquals2016brokenboxcrypto300400/#comments
Sun, 18 Sep 2016 22:19:04 +0000
http://mslc.ctf.su/?p=3966
Continue reading »]]>
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 blackbox access to a RSA signing (decryption oracle). We need to decrypt a given flag, but the oracle allows only to sign values in range 09999. 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 RSACRT, where by using gcd we can factor the modulus. However we tried to apply them and they failed. Therefore, it is probably not RSACRT 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 LLLbased 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 bitbybit 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}.
]]>
http://mslc.ctf.su/wp/csawquals2016brokenboxcrypto300400/feed/
2

Tokyo Westerns/MMA CTF 2016 – Backdoored Crypto System (Reverse+Crypto 400)
http://mslc.ctf.su/wp/tokyowesternsmmactf2016backdooredcryptosystemreversecrypto400/
http://mslc.ctf.su/wp/tokyowesternsmmactf2016backdooredcryptosystemreversecrypto400/#respond
Mon, 05 Sep 2016 16:02:44 +0000
http://mslc.ctf.su/?p=3878
encrypt asd Encrypted: 6c4bb9114b4db65c2a0e7043f7693c2d > decrypt asd command not found > flag OK. I'll give you the flag. Encrypted: 97ee6e2e2824906e9f4870553c7b4f49 1. ReverseEngineering Reversing the… Continue reading »]]>
Get the flag.
bcs.7z
$ nc bcs.chal.ctf.westerns.tokyo 3971
Summary: recovering AES key from partial subkey leaks.
The challenge allows to encrypt any text and to encrypt the flag:
$ ./bcs
> encrypt asd
Encrypted: 6c4bb9114b4db65c2a0e7043f7693c2d
> decrypt asd
command not found
> flag
OK. I'll give you the flag.
Encrypted: 97ee6e2e2824906e9f4870553c7b4f49
1. ReverseEngineering
Reversing the binary was pretty easy. The decompiled encryption function:
int __fastcall sub_400B20(const __m128i *a1, const __m128i *a2, __m128i *a3)
{
...
v3 = a3;
v4 = 0LL;
v11 = _mm_loadu_si128(a2);
_XMM3 = _mm_xor_si128(v11, _mm_loadu_si128(a1));
for ( i = _mm_cvtsi32_si128(1u); ; i = _mm_cvtsi32_si128(dword_400EC0[v4]) )
{
__asm { aeskeygenassist xmm0, [rsp+58h+var_58], 0 }
v8 = _mm_xor_si128(_mm_srli_si128(_XMM0, 12), v11);
v9 = _mm_xor_si128(v8, _mm_slli_si128(v8, 4));
v11 = _mm_xor_si128(_mm_xor_si128(_mm_shuffle_epi32(i, 0), _mm_slli_si128(v9, 8)), v9);
if ( dword_6013AC != 322376503 )
break;
if ( v4 > 1 )
{
v12 = _XMM3;
v14 = _mm_load_si128(&v11);
v13 = v14;
result = printf("%02x%02x", v11.m128i_u8[0], v14.m128i_u8[1], v11.m128i_i64[0]);
_XMM3 = _mm_load_si128(&v12);
break;
}
LABEL_3:
++v4;
__asm { aesenc xmm3, [rsp+58h+var_58] }
if ( v4 == 10 )
goto LABEL_9;
}
if ( v4 != 9 )
goto LABEL_3;
__asm { aesenclast xmm3, [rsp+58h+var_58] }
LABEL_9:
dword_6013AC = 0;
*v3 = _XMM3;
return result;
}
It uses AESNI instructions so most likely it is AES. By debugging the binary and dumping the random key, we can easily verify that it is vanilla AES128.
Interestingly, the global variable dword_6013AC triggers some leakage (recall the task name). To trigger it, we have to send command l34k1nf0 with value 322376503=0x13371337:
v7 = memcmp(v13, "l34k1nf0 ", 9uLL) == 0;
if ( v7 )
{
v6 = 0LL;
dword_6013AC = strtol(&nptr, 0LL, 10);
}
But what does it leak? It prints the first two bytes of some things, maybe subkeys or intermediate values. Let’s activate it:
$ gdb ./bcs
gdbpeda$ b*0x400C7E # calling encryption function
Breakpoint 1 at 0x400c7e
gdbpeda$ r
Starting program: /home/.../bcs
> l34k1nf0 322376503
> encrypt 123
Breakpoint 1, 0x0000000000400c7e in ?? ()
gdbpeda$ x/16xc 0x6013b0 # the global array with random key
0x6013b0: 0x4a 0x93 0x64 0x71 0xf6 0x9d 0x55 0xb3
0x6013b8: 0xf 0x5f 0xc4 0xa5 0x80 0xec 0xef 0x8f
gdbpeda$ continue
Continuing.
ed22d3a6863bb017b97b6bb676dfd003631e849d4c109ee797708d576f4ac457
We now know that:
key = 0x4a,0x93,0x64,0x71,0xf6,0x9d,0x55,0xb3,0xf,0x5f,0xc4,0xa5,0x80,0xec,0xef,0x8f
leak = ed22d3a6863bb017b97b6bb676dfd003
enc("123") = 631e849d4c109ee797708d576f4ac457
Here’s the full expanded key (source code):
plaintext: 31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00
roundkey 0: 4a 93 64 71 f6 9d 55 b3 0f 5f c4 a5 80 ec ef 8f
roundkey 1: 85 4c 17 bc 73 d1 42 0f 7c 8e 86 aa fc 62 69 25
roundkey 2: 2d b5 28 0c 5e 64 6a 03 22 ea ec a9 de 88 85 8c
roundkey 3: ed 22 4c 11 b3 46 26 12 91 ac ca bb 4f 24 4f 37
roundkey 4: d3 a6 d6 95 60 e0 f0 87 f1 4c 3a 3c be 68 75 0b
roundkey 5: 86 3b fd 3b e6 db 0d bc 17 97 37 80 a9 ff 42 8b
roundkey 6: b0 17 c0 e8 56 cc cd 54 41 5b fa d4 e8 a4 b8 5f
roundkey 7: b9 7b 0f 73 ef b7 c2 27 ae ec 38 f3 46 48 80 ac
roundkey 8: 6b b6 9e 29 84 01 5c 0e 2a ed 64 fd 6c a5 e4 51
roundkey 9: 76 df 4f 79 f2 de 13 77 d8 33 77 8a b4 96 93 db
roundkey 10: d0 03 f6 f4 22 dd e5 83 fa ee 92 09 4e 78 01 d2
roundkey 11: 00 7f 43 db 22 a2 a6 58 d8 4c 34 51 96 34 35 83
roundkey 12: c0 e9 af 4b e2 4b 09 13 3a 07 3d 42 ac 33 08 c1
roundkey 13: a8 d9 d7 da 4a 92 de c9 70 95 e3 8b dc a6 eb 4a
roundkey 14: c1 30 01 5c 8b a2 df 95 fb 37 3c 1e 27 91 d7 54
roundkey 15: da 3e 21 90 51 9c fe 05 aa ab c2 1b 8d 3a 15 4f
ciphertext: 63 1e 84 9d 4c 10 9e e7 97 70 8d 57 6f 4a c4 57
Clearly, the leak contains first two bytes from subkeys for rounds 310 inclusive.
2. Attempt with z3
Let’s first try to use z3py to solve it. We can use code from Belluminar’s ahyes challenge solution as a basis:
from z3.z3 import *
from aes import AES
AES = AES()
s = Solver()
# make AES sbox z3friendly
sbox = AES.sbox[::]
AES.sbox = Array("sbox", BitVecSort(8), BitVecSort(8))
for x in xrange(256):
s.add(AES.sbox[x] == sbox[x])
# symbolical key expansion almost for free :)
master = [BitVec("master%d" % i, 8) for i in xrange(16)]
exp = AES.expandKey(master, 16, 11*16)
leak = "ed22d3a6863bb017b97b6bb676dfd003".decode("hex")
leaks = map(ord, leak)
for i in xrange(0, len(leaks), 2):
a, b = leaks[i:i+2]
s.add(exp[(3+i/2)*16+0] == a)
s.add(exp[(3+i/2)*16+1] == b)
print s.check()
key = ""
model = s.model()
for m in master:
key += chr(model[m].as_long())
print key.encode("hex")
Running the script and it immediately spits out the key: 4a0820f57cd5983afb70581b22ec324f. However, it is the wrong key (though few bytes match). By iterating over solutions we can see that there are many, around 2^32. For z3 it is a lot to iterate. So we have to figure out the structure of those solutions by ourselves.
3. AES128 Key Schedule
The AES128 key schedule works as follows: $subkey[0]=(a_0,b_0,c_0,d_0)$ is the master key, and all the consequent subkeys are obtained by applying iteratively the following function:
Here $a,b,c,d$ are 32bit words (think of them as 4 bytes), $S$ applies four 8bit SBoxes in parallel and rotates the word by 8 bits. Here is what we know from the leak (“+” are known bytes, “?” – unknown):
Note that we have leakage for 8 subkeys and the situation from picture occurs only 7 times (both $a_i$ and $a_{i+1}$ are leaked). This number will reduce in a funny way. Now let’s compute other bytes:
 the output of $S$ is equal to $a_i \oplus a_{i+1}$.
 by inverting $S$ we learn two middle (remember rotation) bytes of $d_i$.
By applying the same thing at next two rounds, we obtain $d_{i+1}$ too. Note that these reduces the number of positions and now we only have 6 such positions.
By continuing these simple computations we obtain that for $i = 8$ we know 12/16 subkey bytes.
Here are all computations:
# d[i] = S^1[a[i] ^ a[i+1]]
for i in reversed(xrange(3, 10)):
known[i][13] = isbox[known[i+1][0] ^ known[i][0] ^ rcon[i+1]]
known[i][14] = isbox[known[i+1][1] ^ known[i][1]]
# c[i+1] = d[i] ^ d[i+1]
for i in reversed(xrange(4, 10)):
known[i][10] = known[i][14] ^ known[i1][14]
known[i][9] = known[i][13] ^ known[i1][13]
# b[i+1] = c[i] ^ c[i+1]
for i in reversed(xrange(5, 10)):
known[i][6] = known[i][10] ^ known[i1][10]
known[i][5] = known[i][9] ^ known[i1][9]
# a[i+1] = b[i] ^ b[i+1]
for i in reversed(xrange(6, 10)):
known[i][2] = known[i][6] ^ known[i1][6]
# reusing same equations but for different bytes
# d[i] = S^1[a[i] ^ a[i+1]]
for i in reversed(xrange(7, 10)):
known[i1][15] = isbox[known[i][2] ^ known[i1][2]]
# c[i+1] = d[i] ^ d[i+1]
for i in reversed(xrange(7, 9)):
known[i][11] = known[i][15] ^ known[i1][15]
# b[i+1] = c[i] ^ c[i+1]
for i in reversed(xrange(8, 9)):
known[i][7] = known[i][11] ^ known[i1][11]
for i, k in enumerate(known):
s = "".join("+" if x is not None else "?" for x in k)
print "%2d" % i, s, "%d/16" % s.count("+")
The result:
0 ???????????????? 0/16
1 ???????????????? 0/16
2 ???????????????? 0/16
3 ++???????????++? 4/16
4 ++???????++??++? 6/16
5 ++???++??++??++? 8/16
6 +++??++??++??+++ 10/16
7 +++??++??+++?+++ 11/16
8 +++??+++?+++?+++ 12/16
9 +++??++??++??++? 9/16
10 ++?????????????? 2/16
It is quite interesting that in the end we arrived at 12/16 bytes known only for one subkey (#8). It happened because at each step the first and last subkeys did not have good neighbours. That is, if the backdoor would have leaked 2 bytes for subkeys 02, we would have known some almost full subkey. And it seems that with one round less leak the problem would become much harder.
Anyway, we can now bruteforce the 4 unknown bytes of the 8th subkey, revert the AES key schedule, compute the master key and verify it against the known encryption.
We again reuse the Belluminar’s ahyes implementation in C, here’s the main code (full code):
void undo_key_schedule(uint8_t subkeys[11][16]) {
uint8_t tmp[4];
for(int i = 8; i >= 1; i) {
FORN(j, 4) subkeys[i1][12+j] = subkeys[i][12+j] ^ subkeys[i][8+j];
FORN(j, 4) subkeys[i1][8+j] = subkeys[i][8+j] ^ subkeys[i][4+j];
FORN(j, 4) subkeys[i1][4+j] = subkeys[i][4+j] ^ subkeys[i][j];
tmp[0] = sbox[subkeys[i1][12+1]] ^ fexp2(i1);
tmp[1] = sbox[subkeys[i1][12+2]];
tmp[2] = sbox[subkeys[i1][12+3]];
tmp[3] = sbox[subkeys[i1][12+0]];
FORN(j, 4) subkeys[i1][j] = subkeys[i][j] ^ tmp[j];
}
}
int main(int argc, char *argv[]) {
uint8_t src[16] = "123";
uint8_t dst[16] = {};
uint8_t ciphertext[16] = "\xe2\xa7\x3d\xd0\xd1\x3f\x83\x54\x49\xeb\x3a\x5b\x65\xe7\x08\xb1";
int subkey8[16] = {0xaa,0x42,0x29,1,1,0xa4,0x4c,0x86,1,0xac,0x4a,0x7d,1,0x81,0x4b,0x19};
int indices[4] = {};
int ni = 0;
uint8_t subkeys[11][16];
FORN(i, 16) {
if (subkey8[i] == 1)
indices[ni++] = i;
subkeys[8][i] = subkey8[i];
}
for(uint64_t vals = (atoi(argv[1])*1ll) << 24; vals < 1ll << 32; vals++) {
if ((vals & 0xffffff) == 0) fprintf(stderr, "%08llx\n", vals);
FORN(i, 4) subkeys[8][indices[i]] = (vals >> (i*8)) & 0xff;
undo_key_schedule(subkeys);
encrypt(dst, src, 16, subkeys[0], 0, 10);
if (!memcmp(dst, ciphertext, 16)) {
printf("FOUND! %08llx\n", vals);
FORN(i, 16)
printf("%02x ", subkeys[0][i]);
puts("");
}
}
return 0;
}
In roughly an hour we obtain the correct master key and decrypt the flag: TWCTF{Why_doesn’t_he_leak_the_key_directly}
]]>
http://mslc.ctf.su/wp/tokyowesternsmmactf2016backdooredcryptosystemreversecrypto400/feed/
0

Tokyo Westerns/MMA CTF 2016 – Pinhole Attack (Crypto 500)
http://mslc.ctf.su/wp/tokyowesternsmmactf2016pinholeattackcrypto500/
http://mslc.ctf.su/wp/tokyowesternsmmactf2016pinholeattackcrypto500/#comments
Mon, 05 Sep 2016 16:00:52 +0000
http://mslc.ctf.su/?p=3880
Continue reading »]]>
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:
 Set possible range for $M = [0,N1]$.
 Set small $d$.
 Loop:
 If $oracle_{LSB}(dM \mod{N}) = ?$ then try another $d$.
 For each possible interval for $M$:
 Deduce possible range for $k$.
 Iterate over all $k$ with parity different from $d % 2$ and obtain union of possible intervals for $M$.
 Intersect these intervals with the previous intervals for $M$.
 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.
]]>
http://mslc.ctf.su/wp/tokyowesternsmmactf2016pinholeattackcrypto500/feed/
1

CODEGATEgate
http://mslc.ctf.su/wp/codegategate/
http://mslc.ctf.su/wp/codegategate/#comments
Sat, 07 May 2016 01:51:01 +0000
http://mslc.ctf.su/?p=3867
Continue reading »]]>
Final Scoreboard as captured by manhluat (l4w)
TL;DR
CTF team LC↯BC has been banned and stripped of the first place at CODEGATE CTF 2016 Finals.
The fact has been announced after competition ended and even after they announced the winners. Disqualification decision was made in the most unprofessional and biased way possible, and the CTF organizers (Black Perl Security) and CODEGATE ignore our emails starting this week, so we are making it public to avoid gossip and speculation.
Also, there is a bit of technical details.
Timeline
1. PPP 3570 2. LC↯BC 3440 3. 0daysober 2658
30 min before the end of CTF, we hold second place behind PPP. Distance to the first place is couple hundred points: we need any of the two remaining challenges to make it to the top.
T−10min. We submit hulkbox challenge to the gameboard, getting 1st.
1. LC↯BC 3761 2. PPP 3570 3. 0daysober 2658
T−9min. CTF organizers’ team leader starts to demand aggressively to show him the exploit for the challenge. Task was pwned by one of us who couldn’t make it to Korea and was swapped with someone who could; the guy is asleep in Russia at the moment when the flag has been submitted (3:50 a.m. in UTC+3 timezone), so we propose to show orgs the exploit later once he’s up.
T=. CTF ends, organizers announce the winners. Press begins to interview us.
T+15min. Organizers interrupt the press session, make everyone leave except the teams. It is announced that one of teams was using remote assistance during the competition. A poll is thrown whether remote help is good or bad. “It’s good” 3 : 7 “it’s bad”
T+20min. It’s announced that LC↯BC is disqualified. Scoreboard shifts one row up. We ask why did we get disqualified. — Remote assistance. — But it’s not in the rules?? — It’s not in the rules, and you’re disqualified.
1. PPP 3570 2. 0daysober 2658 3. 217 2632
T+30min. Orgas announce the winners: PPP, 0daysober, 217.
T+4days. None of our emails to either CODEGATE or CTF orgas get answered.
Fun bits
 Rules don’t mention anything regarding remote players. We will not speculate whether other teams asked their folks who couldn’t come to Seoul for help, but since the voting result was 3 : 7, this rule was not an inherent knowledge. If it is forbidden and important to the point of taking away team’s result, rules should state it openly.
 The orgas team leader circled at the PPP table, talking Korean in a low voice throughout the entire game. We know that sounds stupid, but that’s at least amusing. Made us even think orgas might be biased in their decision when we surpassed PPP. Now that the orgs don’t answer our attempts to communicate, they’re clearly not cooperative to us.
 Last two hours the orgas stared at our displays assertively. Apart from that it’s pretty uncomfortable and breaks the focus when you try to RE, they totally had more than enough time to tell us if they didn’t like anything about us.
 Well, we pwned the infrastructure.
VMs that hosted the services were running this:
Linux codegate 4.2.027generic #32~14.04.1Ubuntu SMP Fri Jan 22 15:32:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
which is a quarteryear old kernel vulnerable to this unpopular local privilege escalation strategy (shoutout to halfdog.net).
We were able to get UID 0 on hackerlife and get free flag (which we didn’t submit until we pwned the challenge) as well as /etc/shadow
as our trophy.
Some time later payload challenge was released, running the exact same kernel, so why not try escalating there too?
OK, this is interesting. codegate user has the exact same hash. Let’s attach strace
to SSH server and wait for password to fly by in plaintext.
root@codegate:/# strace ff p 17809 e read & fgrep ', "\f\0\0'
[pid 17865] read(6, "\f\0\0\0\21qkqojrr******lek!", 22) = 22
And yeah, it turns out that every game box does have a sudoer codegate account with the exact same password (which is qkqojrr******lek!
). Game over!
After paying a friendly visit to every vulnbox and scoreboard server, we stopped wasting time on this fun “side challenge” and proceeded to play as usual.
Treat this as a fun piece.
Facts
 LC↯BC had 4 people onsite and 2 remote connected via VPN.
 Onsite guys played 100% of the CTF time, remote guys joined and left at their leisure: noone has free 20 hours during a workday.
 LC↯BC got root privileges on every game server. No attempts were made to destabilize the infrastructure or ruin the fun of the game.
 Security specialists who organize a CTF with eightyear history and 55K USD in prizes did:
 use old Linux kernel version with a public exploit available;
 use the same password for a sudo user on the entire infrastructure;
 use passwords to access the servers;
 spend their time on petty tyranny and annoying players.
 Our solutions for all the tasks that we have submitted:
https://www.dropbox.com/sh/oruwb6f7v6p4pdx/AACF0EE82_cfnVJeMQbDOlea

/home/g0est/flag_tlqkf.txt
from payload vulnbox that we have not submitted:
PAYLOADISNOT_PAYLOADUNLOAD_}
Q & A
Questions related to rules of the competition:
Screenshot of the rules taken during competition
Q: Did LC↯BC submit writeup to CODEGATE organizer after the qualification?
A: Yes
Q: Did LC↯BC join both CODEGATE CTF 2016 and CODEGATE CTF Junior 2016?
A: No
Q: Did LC↯BC share the solution or key of any challenge while competition?
A: No
Q: Did LC↯BC attack operating server for everyone :)?
A: No
Q: Was there a rule effective regarding immediately showing your exploit to event organizer?
A: No
Q: Was there a rule effective regarding remote team members?
A: No
General questions
Q: Were there remote players playing for us?
A: Yes
Q: Were we warned before or during the game that remote help can lead to disqualification?
A: No
Q: Have we traded any flags with other teams or organizers?
A: Of course not!
Q: Have we compromised CTF infrastructure (serverside) ?
A: Yes
Q: Did we interfere with infrastructure in any way that ruins CTF gameplay for anyone?
A: No
Q: Did we have the ability to score every single flag available in the competition (incl. Junior CTF) ?
A: Yes
Q: Were all flags submitted to the scoreboard solved by LC↯BC in a proper way?
A: Yes
Hope this shares our vision reasonably and ends any rumors and conjectures about what happened to our team in Seoul this year. We’d like to add that CODEGATE organizers were friendly and helpful every year (we’re visiting the event for 6th year in a row), and hope to hear their vision of current situation.
Current stance of the orgas really does surprise and irritate us and makes us wonder what to expect from future CODEGATEs.
Guys, please don’t hide. We’re up to discuss it peacefully.
]]>
http://mslc.ctf.su/wp/codegategate/feed/
16

Google CTF – Woodman (Crypto 100)
http://mslc.ctf.su/wp/googlectfwoodmancrypto100/
http://mslc.ctf.su/wp/googlectfwoodmancrypto100/#respond
Tue, 03 May 2016 19:06:13 +0000
http://mslc.ctf.su/?p=3811
Continue reading »]]>
How honest are you?
Running here
Summary: breaking a weak PRNG
On the main page we see the text:
You are coming back home from a hard day sieving numbers at the river.
Unfortunately, you trip and all your numbers fall in a nearby lake.
Continue.
We click Continue and then:
From the lake a god emerged carrying a number on each hand.
He looked at you and asked the following question…
Continue.
Again, click Continue:
Did you drop 4452678531 or 754311689?
If we enter one of the numbers, we either get another question or start from the beginning. So it seems that we need to guess the correct numbers many times.
The numbers look random and the challenge looks like pure guessing. But after looking around, we can find a snippet of code hidden in the html source of the second page. It is hidden in a html comment and padded down with 500 empty lines. Here’s the snippet:
class SecurePrng(object):
def __init__(self):
# generate seed with 64 bits of entropy
self.p = 4646704883L
self.x = random.randint(0, self.p)
self.y = random.randint(0, self.p)
def next(self):
self.x = (2 * self.x + 3) % self.p
self.y = (3 * self.y + 9) % self.p
return (self.x ^ self.y)
It is a quite simple PRNG: it consists of two LCG combined with xor.
We can guess the first few values and then attack the PRNG to recover the seed and predict next outputs.
The simplest solution is to bruteforce all candidates for $x$, deduce $y$ as xor of PRNG output with $x$ and check if the numbers match. But I will describe another solution, which exploits the fact that the multipliers are very small (2 and 3). This solution would work for much larger $p$.
The idea is to reconstruct $x$ bitbybit from least significant to most significant bits. Since we also know $x \oplus y$, we immediately obtain value of the same bits of $y$. Then, to account the modulus $p$ we simply guess how many $p$ we subtract on overflow. This number is not greater the multiplier constants and since they are small, there are quite few possible values. So we compute least significant bits of $x’ = 2x + 3 – k_xP$, then we obtain least significant bits of $y’$ from known $x’ \oplus y’$ and we check if for some $k_y$ the congruence $y’ \equiv 3y + 9 k_yP\pmod{2^t}$ works.
Note that when we guess a bit of $x$, it possible that both bit values pass the test, leading to exponential explosion. One option is to compute next values (the same LSBs) and check if they match the third generated value (and for this we need to guess the modulus reductions again). But it seems that when one of the multipliers is even, there are at most one candidate per $k_x,k_y$ guess. I haven’t proved this, just observed experimentally. So for the multipliers 2,3 it works perfectly.
Here’s POC:
import random
from itertools import product
P = 2**256 + 7
NBITS = P.bit_length()
Ax, Cx = 2, 5
Ay, Cy = 3, 7
def next(x, y):
x = (Ax*x + Cx) % P
y = (Ay*y + Cy) % P
return x, y
# generate two values
X0, Y0 = random.randint(0, P1), random.randint(0, P1)
print "X0", hex(X0)
print "Y0", hex(Y0)
realkx = (Ax*X0 + Cx) / P
realky = (Ay*Y0 + Cy) / P
print "REAL KX KY", realkx, realky
X1, Y1 = next(X0, Y0)
X2, Y2 = next(X1, Y1)
prng = [X0 ^ Y0, X1 ^ Y1, X2 ^ Y2]
# guess modulo reductions
for kx, ky in product(range(Ax), range(Ay)):
xs = {0}
# go from LSB to MSB
for b in xrange(NBITS):
if not xs:
break
xs2 = set()
mask = 2**(b+1)  1
mod = 2**(b+1)
for x, bx in product(xs, range(2)):
x = bx << b
y = (prng[0] ^ x) & mask
if x >= P or y >= P:
continue
x1 = (Ax*x + Cx  kx * P) % mod
y1 = (Ay*y + Cy  ky * P) % mod
if (x1 ^ y1) & mask == prng[1] & mask:
xs2.add(x)
xs = xs2
else:
print kx, ky, ":", len(xs), "candidates"
for x0 in xs:
y0 = prng[0] ^ x0
assert x0 < P
if y0 >= P:
continue
x1, y1 = next(x0, y0)
if x0 ^ y0 == prng[0] and x1 ^ y1 == prng[1]:
print "GOOD", hex(x0), hex(y0)
The flag: CTF{_!_aRe_y0U_tH3_NSA_:?_!_}
]]>
http://mslc.ctf.su/wp/googlectfwoodmancrypto100/feed/
0

Google CTF – Spotted Wobbegong (Crypto 100)
http://mslc.ctf.su/wp/googlectfspottedwobbegongcrypto100/
http://mslc.ctf.su/wp/googlectfspottedwobbegongcrypto100/#comments
Sun, 01 May 2016 19:47:18 +0000
http://mslc.ctf.su/?p=3807
Continue reading »]]>
Are you able to defeat 1024bit RSA?
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
PublicKey: (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://spottedwobbegong.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^{k8}$. 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://spottedwobbegong.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.
]]>
http://mslc.ctf.su/wp/googlectfspottedwobbegongcrypto100/feed/
3

Google CTF – Jekyll (Crypto)
http://mslc.ctf.su/wp/googlectfjekyllcrypto/
http://mslc.ctf.su/wp/googlectfjekyllcrypto/#respond
Sun, 01 May 2016 19:45:17 +0000
http://mslc.ctf.su/?p=3809
Continue reading »]]>
Can you access the admin page? You can look at the crypto here.
Summary: finding a preimage for a simple 64bit ARXbased 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' * (12len(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 32bit 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 32bit 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 32bit 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
// 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?}
]]>
http://mslc.ctf.su/wp/googlectfjekyllcrypto/feed/
0

Google CTF – Wolf Spider (Crypto 125)
http://mslc.ctf.su/wp/googlectfwolfspidercrypto125/
http://mslc.ctf.su/wp/googlectfwolfspidercrypto125/#comments
Sun, 01 May 2016 19:44:32 +0000
http://mslc.ctf.su/?p=3814
Continue reading »]]>
Continuing on from Eucalypt Forest – can you break Message Authentication in Wolf Spider
Summary: forging signatures by exploiting CBC padding oracle and hash length extenstion
This challenge is a harder version of the Eucalypt Forest from the same CTF. On the website, we can get an encrypted cookie for any user except “admin” and we need to forge such cookie for username “admin” to get the flag. We are also given the code of creating a cookie. Here’s the main part:
def make(dct):
tcd = urllib.urlencode(dct)
# Use RFC1321 to hash our data, so it can't be tampered with.
h = SHA.new()
h.update(Storage.mac_key)
h.update(tcd)
s = h.digest()
# AESCBC
coded = CookieCutter.encode(tcd)
return s.encode('hex') + "." + coded.encode('hex')
CookieCutter.encode({ "username": "blah" })
So what we have here is EncryptandMAC, which is insecure. Moreover the MAC is vulnerable to hash length extension attack. Also, there is a padding oracle:
def unmake(st):
pieces = st.split(".")
if len(pieces) != 2:
return None
s = CookieCutter.decode(pieces[1].decode('hex'))
if s == None:
return None
h = SHA.new()
h.update(Storage.mac_key)
h.update(s)
f = h.hexdigest()
if pieces[0] != f:
# print "hash comparasion failed :("
return None
kv = urlparse.parse_qsl(s)
ret = {}
for k, v in kv:
ret[k] = v
return ret
def decode(string):
if len(string) < 16:
return ValueError, "bad string"
iv, string = string[:16], string[16:]
algo = AES.new(Storage.aes_key,
AES.MODE_CBC,
IV=iv)
plaintext = str(algo.decrypt(string))
pad = ord(plaintext[1])
if pad > CookieCutter.KEY_SIZE:
raise ValueError, "pad error  pad is %d" % (pad)
expected = chr(pad) * pad
piece = plaintext[pad:]
if piece != expected:
raise ValueError, "padding is corrupted"
raw = plaintext[:pad]
# print "raw is %r" % raw
return raw
When padding is incorrect, CookieCutter.decode raises an Exception and the web server returns code 500. If the MAC is incorrect, the function returns None. If everything is correct, the server says that we need username “admin”.
Let’s start with hash length extension. Assume that we register username “user”. Then the MAC is:
$sha1(key  username=user)$.
By extending it, we can obtain:
$sha1(key  username=user  \text{\x80\x00…}  arbitrary\_data)$,
where “\x80\x00…” is the sha1 padding. In particular, we can set the arbitrary data to “;username=admin” and the signature will be correct. But we won’t have the respective AES ciphertext. And we can’t register user “user\x80\x00…”, because the server uses urlencode and will corrupt our special characters.
What should we do? Usually CBC padding oracle is used to decrypt messages. But actually we can use it to encrypt arbitrary plaintexts! How will we do it?
Let’s set the last cipher block $C_{1}$ to arbitrary value, let’s say all zeros. Then we use the padding oracle to decrypt it into $P_{1}$. Now we can exploit the CBC chaining and prepend such IV that the ciphertexts decrypt to our value $M_{1}$: $iv = P_{1} \oplus M_{1}$. We now have that $iv  C_{1}$ decrypts to $M_{1}$. Since we want to encrypt more blocks, we can repeat the trick. But now, instead of fixing the cipher block to arbitrary value, we fix it to the last $iv$, and compute a new $iv’ = decrypt(iv) \oplus M_{2}$. Now we have that $iv’  iv  C_{1}$ decrypts to $M_{2}  M_{1}$. Obviously, we can extend the process for arbitrarily long messages.
But we need to keep in mind that decryption oracle using CBC padding oracle is quite slow. So we can start with a known plaintextciphertext block instead of allzero block.
The only thing left is to code this attack. The code follows. Note how the blocks look like:
BLOCK 0: 'username=aaaaaaa'
BLOCK 1: 'aaaaaaa\x80\x00\x00\x00\x00\x00\x00\x01\xb8'
BLOCK 2: ';username=admin\x01'
I also use very nice tool hash_extender, which supports many hashes. (The github page also explains the attack nicely!).
import sys
import requests
import hashlib
import subprocess
def decrypt(todec):
"""
Decrypt block using padding oracle
Written by Yngve
"""
hash = "a" * 40
# valid query
pref = hash + ".b1d3ba9e9ec44cfb92bc8910907d6be46c0d482fe9d46e7966161e9a17368eef"
url ="https://wolfspider.ctfcompetition.com/admin"
todec = todec.encode("hex")
known = ""
while len(known) < 16:
s = ""
for i in xrange(len(known)):
c = known[::1][i]
n = ord(c) ^ (len(known)+1)
s = "%02x%s" % (n, s)
for i in xrange(0, 256):
s2 = "%02x%s" % (i, s)
s2 = s2.rjust(32, "0")
res = requests.get(url, verify=False, cookies={"UID": pref + s2 + todec})
if res.status_code == 500:
sys.stdout.write(".")
sys.stdout.flush()
continue
known = chr(i ^ (len(known)+1)) + known
break
print
print "known", known.encode("hex")
return known
def getcookiefor(name):
url = "https://wolfspider.ctfcompetition.com/createaccount"
data = {"username": name}
sess = requests.session()
r = sess.post(url, verify=False, data=data)
h, ct = sess.cookies["UID"].split(".")
return h, ct.decode("hex")
def check(h, ct):
url ="https://wolfspider.ctfcompetition.com/admin"
uid = h + "." + ct.encode("hex")
res = requests.get(url, verify=False, cookies={"UID": uid})
return res.content
def hash_extender(data, hash, append, hashname, length=16):
'''https://github.com/iagox86/hash_extender'''
s = subprocess.check_output([
"hash_extender",
"d", data.encode("hex"), "dataformat=hex",
"s", hash,
"a", append.encode("hex"), "appendformat=hex",
"l", str(length),
"f", hashname
])
lines = s.splitlines()
sig = lines[2].strip().split()[1]
s = lines[3].strip().split()[1].decode("hex")
return s, sig
def to_blocks(s):
return [s[i:i+16] for i in xrange(0, len(s), 16)]
def xor(a, b):
return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in xrange(len(a))])
MAC_LEN = 32
# 9 bytes padding: \x80 + 8 bytes size
username = "a" * (64  MAC_LEN  len("username=")  9)
hash, ct = getcookiefor(username)
print "HASH0", hash
data_wanted, sig = hash_extender(
data="username=" + username,
hash=hash,
append=";username=admin",
hashname="sha1",
length=MAC_LEN
)
print "FORGED SIG", sig
blocks_wanted = to_blocks(data_wanted)
# tricky: we need pad but don't need to sign it
blocks_wanted[1] += "\x01"
for b in blocks_wanted:
print "BLOCK NEED:", len(b), `b`
# use known pair to make fewer decryption calls
last_plain = xor(ct[:16], "username=aaaaaaa")
ciphertext = ct[16:32]
for wanted in reversed(blocks_wanted):
if len(ciphertext) == 16:
plain = last_plain
else:
print "DECRYPTING", ciphertext[:16].encode("hex")
plain = decrypt(ciphertext[:16])
ciphertext = xor(plain, wanted) + ciphertext
print "NEW CIPHERTEXT", ciphertext.encode("hex")
print check(sig, ciphertext)
The flag: CTF{++How+do+you+fix+a+cracked+pumpkin+++With+a+pumpkin+patch++}
]]>
http://mslc.ctf.su/wp/googlectfwolfspidercrypto125/feed/
3

PlaidCTF 2016 – sexec (Crypto 300)
http://mslc.ctf.su/wp/plaidctf2016sexeccrypto300/
http://mslc.ctf.su/wp/plaidctf2016sexeccrypto300/#respond
Sun, 24 Apr 2016 12:51:15 +0000
http://mslc.ctf.su/?p=3769
Continue reading »]]>
If you need to securely grant execution privileges, what better way to do it than sexec?
This is running on sexec.pwning.xxx:9999
Summary: attacking a small instance of RingLWE based cryptosystem with Babai’s Nearest Vector algorithm.
In this challenge we are given a source code of a service which allows command execution. However, the session is encrypted using a hardcoded public key. Let’s look at the main part:
def connectionMade(self):
self.process = None
self.secret = uniform(4, 0, 255).astype(np.uint8)
self.secret_bits = np.unpackbits(self.secret)
self.aes_key = ''.join([chr(x) for x in self.secret]) +\
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B'
sk = uniform(self.factory.param.N)
pub = op(self.factory.param.a, sk) \
+ uniform(self.factory.param.N)
enc = op(self.factory.pk, sk) \
+ uniform(self.factory.param.N) \
+ (self.secret_bits * (self.factory.param.Q / 2))
pub %= self.factory.param.Q
enc %= self.factory.param.Q
ct = asn1.Ciphertext()
ct.setComponentByName('pub',
asn1.ints_to_bitstring(pub, self.factory.param.Q))
ct.setComponentByName('enc',
asn1.ints_to_bitstring(enc, self.factory.param.Q))
self.sendString(asn1.encoder.encode(ct))
self.iv1 = os.urandom(12)
self.iv2 = 0
self.sendString(self.iv1)
def stringReceived(self, data):
if len(data) < 4:
self.transport.loseConnection()
return
tag = data[:4]
data = data[4:]
decryptor = Cipher(
algorithms.AES(self.aes_key),
modes.GCM(self.iv, tag, min_tag_length=4),
default_backend()).decryptor()
self.iv2 += 1
try:
data = decryptor.update(data) + decryptor.finalize()
except InvalidTag:
self.transport.loseConnection()
return
if self.process is None:
self.process = ShellProcessProtocol(
self.sendString,
self.transport.loseConnection)
reactor.spawnProcess(self.process, data, [data], {})
self.process_timer = reactor.callLater(5,
lambda: self.connectionLost(None))
return
else:
self.process.transport.write(data)
return
We see that the AES key has only 4 random bytes. But we are not given any plaintextciphertext pair, we can only send ciphertext + tag to check it. Thus, we can bruteforce only via network which is unfeasible. We have to attack the asymmetric crypto here.
Mysterious $op(a, b)$
The cryptosystem is based on the op(a, b) method, which uses Discrete Fourier Transform:
def op(a, b):
c = np.fft.ifft(
np.fft.fft(a, a.size * 2)
* np.fft.fft(b, b.size * 2)
).real
return (c[0:a.size]  c[a.size:]).astype(np.int)
If you don't know the first construction, it is multiplication of polynomials using DFT. Here's a short explanation: DFT can be seen as transforming an array of a polynomial coefficients into an array of values of that polynomial on particular points. These points are powers of the $n$th root of unity (which is a complex number $e^{i \frac{2\pi}{n}}$). A nice property is that $DFT(p(x)*q(x)) = DFT(p(x))\cdot DFT(q(x))$, where $*$ is multiplication of polynomials and $\cdot$ is componentwise product. Therefore to multiply two polynomials we can compute $DFT^{1}(DFT(p(x))\cdot DFT(q(x)))$. There exist algorithms for DFT and inverse DFT with complexity $O(n\log{n})$, and this is the final complexity of the described algorithm. Note that naive polynomial multiplication has complexity $O(n^2)$. But here I guess this approach was used for easier coding, since $O(n^2)$ is good enough for the challenge parameters.
In the second part, the most significant half of the coefficients is subtracted from the least significant half. It correspongs to the relation $x^n = 1$, which means that it is a reduction modulo $x^n + 1$.
To sum up, $op(a,b)$ is multiplication of two polynomials $a$ and $b$ of degree $n1$ modulo $x^n + 1$. Note that numpy.fft uses real and complex numbers and therefore is not precise.
The cryptosystem
First we extract the public parameters:
$N = 32,$
$Q = 929,$
$a = [592, 476, 894, 411, 843, 634, 904, 322, 424, 368, 164, 47, 698,
778, 222, 680, 683, 384, 102, 161, 243, 224, 768, 137, 659, 28,
189, 335, 167, 67, 517, 701].$
They mean that we work with polynomials of degree 31 over $GF(929)$ and the polynomials are taken modulo $x^{32}+1$. Here are the equations from the code:
 $sk,r_0,r_1$ are random "small" polynomials: coefficients are from $[5,5]$.
 $pub = a*sk + r_0$.
 $enc = pk*sk + r_1 + \lfloor Q/2 \rfloor secret$.
We are given $pub$ and $enc$, and we need to recover $secret$. This is Ring Learning with Errors (RingLWE) based cryptosystem. Notably, such cryptosystems are expected to be secure against quantum computers.
Note that in usual publickey cryptosystems on finite fields the norm (magnitude) of the values usually does not matter. While here it plays very important role. The small polynomials added are small errors adding some noise to the values. Also the remainders are taken to be $q/2,\ldots,1,0,1,\ldots,q/2$ instead of $0,1,\ldots,q1$.
But it should be possible to recover the $secret$ (encrypted message) at least by the owner of the private key! How? There is no explanation in the code, but actually there exist small polynomials $s_0$ and $s_1$, such that $pk = a*s_0 + s_1$! These small polynomials form a private key and its owner can decrypt the message as follows:
$pub*s_0 = a*s_0*sk + r_0*s_0 = pk * sk  s_1*sk + r_0*s_0,$
$m = enc  pub*s_0 = r_0*s_0  s_1*sk + r_1 + \lfloor Q/2 \rfloor secret.$
Note that multiplication of two small polynomials is not large too: it is bounded by $5*5*32 = 800$ but the expected value is much smaller. Therefore if the $i$th secret bit is 1, we expect $m$ to be closer to $q/2$ or to $q/2$ than to 0. And with high probability the full message can be recovered without errors.
Attacking the cryptosystem
How can we attack it? First direction is to attack the equation $pub = a*sk + r_0$ to recover $sk$. Then we could compute $pub  pk*sk = r_0 + \lfloor Q/2 \rfloor secret$ which is enough to recover the $secret$. A good thing is that we have randomization ($sk$ is always random) and can get different instances. But we have to mount the attack online, which may take some time. So, let's try to recover the private key.
That is, we attack the equation $pk = a*s_0 + s_1$. We know $a$ and $pk$ and we need to find suitable small $s_0$ and $s_1$. Finding "small" values usually refers to LLL and RingLWE is called "latticebased" also.
Multiplication by $a$ in the polynomial ring can be described by $32\times32$ matrix $M_a$ over $GF(Q)$. That is, in matrix form we have: $pk = M_a \times s_0 + s_1$. Note that $s_0$ specifies a linear combination of columns from $M_a$. Now we can see how the lattice should look like: columns of $M_a$ form a basis and we want to find a vector which is very close to $pk$. But we also want the coefficients from $s_0$ to be small! To ensure this we can use the following trick: extend the $i$th basis vector with $n$ zeroes and set $1$ into position $n+i$. Then, in the final vector these positions will be equal to coefficients from $s_0$. Since we want them to be small, we extend the target vector (which is $pk$) with zeroes. Let $m_i$ be the $i$th column of $M_a$. Then the basis of the lattice looks like this:
$m_0  1, 0, \ldots, 0$
$m_1  0, 1, \ldots, 0$
$\ldots$
$m_n  0, 0, \ldots, 1$
However, we forgot about reductions modulo $Q$! Since they are free, we can simply add $n$ vectors where $i$th vector has $Q$ in position $i$ and zeroes elsewhere. In the matrix form, the lattice is spanned by the rows of the following matrix:
$\begin{bmatrix}
Q[I] & [0] \\
[M_A^T] & [I] \\
\end{bmatrix},$
where $[I]$ is the $n\times n$ identity matrix.
That is, we have a lattice and a target vector. We now need to solve the Closest Vector Problm (CVP). It is hard but feasible because of small $n=32$. Sage has a closest_vector method but it is very slow. I decided to use Babai's Nearest Plane algorithm, which calls LLL several times. It is approximation algorithm but it is enough for our case.
Here's the full code for computing private key in Sage:
from sage.all import *
from sexec import op
import numpy as np
Q = 929
R. = PolynomialRing(GF(Q))
S. = R.quotient(x**32 + 1)
# numpy multiplication
def myop(a, b):
a = np.array(list(a), dtype=np.int)
b = np.array(list(b), dtype=np.int)
return vector(ZZ, op(a, b))
# precise multiplication
def mymul(a, b):
a = list(a)
b = list(b)
return vector(ZZ, list(S(a)*S(b)))
# Babai's Nearest Plane algorithm
def Babai_closest_vector(M, G, target):
small = target
for _ in xrange(1):
for i in reversed(range(M.nrows())):
c = ((small * G[i]) / (G[i] * G[i])).round()
small = M[i] * c
return target  small
a = [592, 476, 894, 411, 843, 634, 904, 322, 424, 368, 164, 47, 698, 778, 222, 680, 683, 384, 102, 161, 243, 224, 768, 137, 659, 28, 189, 335, 167, 67, 517, 701]
pk = [558, 630, 505, 864, 186, 509, 81, 752, 167, 254, 907, 484, 279, 762, 200, 810, 640, 402, 549, 850, 310, 376, 48, 306, 158, 178, 497, 254, 21, 259, 329, 564]
# Multiplication by a
mulA = matrix(ZZ, 32, 32)
for i in range(32):
tst = [0]*32
tst[i] = 1
mulA.set_row(i, list(S(tst) * S(a)))
# Basis for the lattice
B = matrix(ZZ, 64, 64)
B[:32,:32] = Q * identity_matrix(ZZ, 32, 32)
B[32:,:32] = mulA
B[32:,32:] = identity_matrix(ZZ, 32, 32)
for itr in xrange(100):
print "Try #%d" % itr
# randomize lattice
for i in range(50):
ia = randint(0, 63)
ib = randint(0, 63)
if ib == ia:
ib = (ib + 1) % 64
val = randint(10, 10)
B[ia] += val * B[ib]
print "= LLL + Gram Schmidt"
M = B.LLL() # delta=0.9999999, eta=0.5)
G = M.gram_schmidt()[0]
print "= Done"
target = vector(ZZ, list(pk) + [0] * 32)
res = Babai_closest_vector(M, G, target)
delta = res  target
s0 = vector(ZZ, delta[32:])
s1 = vector(ZZ, target[:32]  res[:32])
print "= s0 =", s0
print "= s1 =", s1
assert list( (mymul(s0, a) + s1) % Q ) == pk
assert list( (s0 * mulA + s1) % Q ) == pk
# try encrypt/decrypt
# random messages to see how good are s0 and s1
def rnd():
return vector(ZZ, [randint(5, 5) for _ in xrange(32)])
secret = vector(ZZ, [randint(0, 1) for _ in range(32)])
guessed = [0] * 32
for i in xrange(2000):
sk = rnd()
pub = myop(a, sk) + rnd()
enc = myop(pk, sk) + rnd() + secret * (Q // 2)
res = (enc  mymul(pub, s0)) % Q
recovered = [1  int(v < Q // 4 or v > Q  Q // 4) for v in res]
if list(recovered) == list(secret):
print "Full recovery!"
quit()
for j in range(32):
guessed[j] += (recovered[j] == secret[j])
print "= guesses", " ".join("%.2f" % (v / 2000.0) for v in guessed)
print
After a few tries we recover the correct private key (the absoulte values are bounded by 5):
$s_0 = (2, 4, 0, 5, 2, 4, 4, 5, 0, 2, 0, 1, 1, 4, 2, 3, 4, 4, 5, 5, 4, 2, 5, 4, 0, 2, 5, 4, 4, 3, 1, 2),$
$s_1 = (1, 3, 2, 3, 3, 3, 3, 3, 5, 0, 0, 2, 2, 2, 4, 2, 0, 2, 2, 1, 5, 1, 2, 5, 0, 2, 3, 4, 0, 5, 4, 1).$
Note that even for wrong keys (which have values larger than 5) the decryption yields correct values more often than wrong. (With probability around 55%).
Executing Commands
Now that we have the private key, we can execute arbitrary commands on server side. After figuring out the protocol and Twisted API, the following script was written:
from sage.all import *
import asn1
import numpy as np
from struct import pack
from sock import Sock
from sexec import op, Cipher, algorithms, modes, default_backend
Q = 929
R = PolynomialRing(GF(Q), name='x')
x = R.gen()
S = R.quotient(x**32 + 1)
a = [592, 476, 894, 411, 843, 634, 904, 322, 424, 368, 164, 47, 698, 778, 222, 680, 683, 384, 102, 161, 243, 224, 768, 137, 659, 28, 189, 335, 167, 67, 517, 701]
pk = [558, 630, 505, 864, 186, 509, 81, 752, 167, 254, 907, 484, 279, 762, 200, 810, 640, 402, 549, 850, 310, 376, 48, 306, 158, 178, 497, 254, 21, 259, 329, 564]
s0 = [2, 4, 0, 5, 2, 4, 4, 5, 0, 2, 0, 1, 1, 4, 2, 3, 4, 4, 5, 5, 4, 2, 5, 4, 0, 2, 5, 4, 4, 3, 1, 2]
s1 = [1, 3, 2, 3, 3, 3, 3, 3, 5, 0, 0, 2, 2, 2, 4, 2, 0, 2, 2, 1, 5, 1, 2, 5, 0, 2, 3, 4, 0, 5, 4, 1]
def read_string():
n = f.read_until_re(r"(\d+):")
n = int(n)
s = f.read_nbytes(n)
assert f.read_nbytes(1) == ","
return s
def mymul(a, b):
a = list(a)
b = list(b)
return vector(ZZ, list(S(a)*S(b)))
f = Sock("sexec.pwning.xxx 9999")
# f = Sock("127.1 9999")
s = read_string()
iv0 = read_string()
print "s", s.encode("hex")
print "iv0", iv0.encode("hex")
res = asn1.decoder.decode(s, asn1Spec=asn1.Ciphertext())
pub, enc = res[0]
pub = vector(ZZ, asn1.bitstring_to_ints(pub, Q))
enc = vector(ZZ, asn1.bitstring_to_ints(enc, Q))
print "pub", pub
print "enc", enc
res = (enc  mymul(pub, s0)) % Q
recovered = [1  int(v < Q//4 or v > Q  Q//4) for v in res]
secret = "".join(map(chr, np.packbits(np.array(recovered))))
print "SECRET", recovered, secret.encode("hex")
aes_key = secret + '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B'
iv1 = 0
def send_enc_string(s):
global iv1
iv = iv0 + pack(">I", iv1)
encryptor = Cipher(
algorithms.AES(aes_key),
modes.GCM(iv, min_tag_length=4),
default_backend()).encryptor()
cipher = encryptor.update(s) + encryptor.finalize()
tag = encryptor.tag
tosend = tag[:4] + cipher
f.send("%d:%s," % (len(tosend), tosend))
iv1 += 1
send_enc_string("/bin/bash")
send_enc_string("id\n")
send_enc_string("cat flag.txt\n")
f.interact()
Run it:
51:uid=1001(sexec) gid=1001(sexec) groups=1001(sexec)
,20:quantum_was_so_2015
So, the flag is "quantum_was_so_2015".
Links:
On Ideal Lattices and Learning With Errors Over Rings (slides)
The Learning with Errors Problem (survey)
]]>
http://mslc.ctf.su/wp/plaidctf2016sexeccrypto300/feed/
0

PlaidCTF 2016 – Radioactive (Crypto 275)
http://mslc.ctf.su/wp/plaidctf2016radioactivecrypto275/
http://mslc.ctf.su/wp/plaidctf2016radioactivecrypto275/#comments
Sun, 17 Apr 2016 22:20:06 +0000
http://mslc.ctf.su/?p=3767
Continue reading »]]>
We just got this fancy new cryptographic device and it seems to work great… for the most part. But sometimes the values it gives me are wrong. Maybe you could take a look for me.
Summary: fault attack on RSA signature (not RSACRT)
Inside we have three files: flag.enc, log.gz and README. Here’s the README:
So I have this key signing service, but it's next to this big.. glowy thing.
If I ask it to sign the same thing twice, I sometimes get different results!
I don't think I have a bug in my code.. all I do is
d = radioactive_storage.load("d")
n = radioactive_storage.load("n")
g = int(raw_input())
return pow(g, d, n), n
I attached a log of what I mean, maybe it will help you diagnose the issue.
We can interpret it like this: $d$ or $n$ may be corrupted before the computations, but the computations themselves are correct. Note that there is no separaion on $p$ and $q$ here, hence the well known fault attacks on RSACRT will not work here. That is, taking a few gcd‘s won’t do the job.
The flag.enc file obviously contains an encrypted flag and log.gz contains 1500 triples of the form (sent $g$, retrieved $g^d \mod{n}$, retrieved $n$).
How can we exploit corruption of $d$? Assume that we know that the fault consists in flipping some bit of $d$, resulting in $d’$. Then we know that $g^{d’} = g^{d’\pm 2^k}=g^{2^k}g^d$. We can check this for all $k$ less than size of $N$. If we find that $d’ = d + 2^k$, then we know that $d$ had zero bit in position $k$, otherwise there will be more changes due to carry propagation. Similarly, if we see that $d’ = d – 2^k$, then we know that $k$’th bit is $1$, otherwise carry would touch more than one bit. But this way requires assumption about bit flipping faults and we can recover only ~50 bits of $d$ from different positions. If we had an access to the faulty oracle, then we could recover all the bits in such way. But from the given log we don’t have enough triples.
Then, how can we exploit corruption of $n$? Consider an edge case – what if we learn $g^d \mod{p}$ for some small prime $p$? We can compute the discrete logarithm modulo $p$ and recover $d \mod ord_p(g)$. That is, we gain some information about $d$.
Obviously, it is very unprobable that $n$ is corrupted into a small prime. But it is very possible that corrupted $n$ has a small prime factor! Thus, we can try to factor out small primes from the corrupted modulos and recover lots of information about $d$.
And then we can join all this information using Chinese Remainder Theorem to obtain $d \mod{lcm(m_1,m_2,\ldots)}$, where $m_1,m_2,\ldots$ are the previous modulos (orders of respective $g$). Let’s see how much we can get. Here’s a Sage script:
ps = list(primes(10**7))
f = open("slog") # sort u log >slog
N = 128252385158999798650478619915390348219127455402754689089261593737448863301830850674971570556378141585421543279992130969111964989833589473502693007444433776166136517668072138209463294411651789727827877583581930984842125108249744730094086909309185568218796494490149921734990907802684240383885623446995532878367
E = 0x10001
curd = 0
curmod = 1
while True:
l = f.readline().strip()
if not l:
break
g, gd, n = map(int, l.split(", "))
if n == N:
continue
for p in ps:
if n % p:
continue
mod = 1
while n % p == 0:
n //= p
mod *= p
# lazy to handle other cases
if gcd(g, mod) != 1:
continue
z = Zmod(mod)
newmod = z(g).multiplicative_order()
newrem = discrete_log(z(gd), z(g))
print "%d mod %d" % (newrem, newmod)
curd = crt([curd, newrem], [curmod, newmod])
curmod = lcm(curmod, newmod)
print "Finished", "mod size", curmod.nbits()
In 30 seconds we get 582bit modulus (let’s call it $M$)! Recall that size of $n$ is 1024 bits. There exists an attack which allows to factor $n$ when quarter of the bits of $d$ is known and we have even more than a half! Actually, we can make a quite simple attack.
Consider the equation $ed – k(n – p – q + 1) = 1$. We can reduce it modulo $M$ and use the knowledge of $d \mod {M}$. Also note that $k$ is less than $e = 65537$, therefore we can check all candidates for $k$! As a result, we can recover the value of $p+q$ modulo $M$, but since $M$ is larger than 513 bits, that value will be equal to $p+q$ without any reductions. Then we can solve a simple quadratic equation over integers to recover $p$ and $q$. Here’s the script:
N = 128252385158999798650478619915390348219127455402754689089261593737448863301830850674971570556378141585421543279992130969111964989833589473502693007444433776166136517668072138209463294411651789727827877583581930984842125108249744730094086909309185568218796494490149921734990907802684240383885623446995532878367
E = 0x10001
drem = 111548247099426273389948507953833349793581006224434222496170188807812542432248571343054254439167042364761271083138602731106930981638865930562124831007293288177676704938822593
dmod = 10274028805726112705913850295896303264348652981631836133118572817617200621634370718771916760166640218221053248064436048608818372150752407015086987656246659129922719605007864000
for k in xrange(1, E):
if k % 1000 == 0:
print k
kpq = (k * (N + 1)  E*drem + 1) % dmod
g = gcd(k, dmod)
if kpq % g:
continue
k /= g
kpq /= g
mod = int(dmod / g)
k = int(k)
pq_sum = (kpq * inverse_mod(k, mod)) % mod
# solve p^2  pq_sum*p + N = 0
b = pq_sum
c = N
dsc = b**2  4*c
sqrtD = int(sqrt(dsc))
if sqrtD**2 != dsc:
continue
p = (b + sqrtD) / 2
p = int(p)
if N % p == 0:
print "Factored:"
print p
print N / p
print
break
And in 40 seconds we get $k = 22408$ and factorization of $n$.
Then we decrypt the flag.enc: its_okay_luckily_bits_never_flip_in_the_real_world.
]]>
http://mslc.ctf.su/wp/plaidctf2016radioactivecrypto275/feed/
4

0CTF 2016 Quals – Equation (Crypto 2 pts)
http://mslc.ctf.su/wp/0ctf2016qualsequationcrypto2pts/
http://mslc.ctf.su/wp/0ctf2016qualsequationcrypto2pts/#comments
Mon, 14 Mar 2016 09:07:35 +0000
http://mslc.ctf.su/?p=3747
Continue reading »]]>
Here is a RSA private key with its upper part masked. Can your recover the private key and decrypt the file?
Summary: recovering RSA key from part of the private key.
In the picture we see a part of a private RSA key. By using some OCR tool and fixing errors, we can obtain the text. We can replace a part of a valid key by this, and use openssl to parse the information. As a result, we see that the following parts are leaked:
prime2:
*:d2:cf:66:84:e4:
11:76:a5:b6:73:05:6b:9c:d2:3b:d8:32:dc:01:7a:
57:50:9d:47:1b
exponent1:
00:d5:a2:25:c0:d4:1b:16:69:9c:44:71:57:0e:ec:
d3:dd:77:59:73:6d:57:81:aa:77:10:b3:1b:4a:46:
e4:41:d3:86:da:13:45:bc:97:d1:aa:91:3f:85:3f:
85:0f:6d:46:84:a8:0e:60:67:fb:71:cf:21:3b:27:
6c:2c:ba:ed:59
exponent2:
13:38:c5:93:d3:b5:42:8c:e9:78:be:d7:a5:53:52:
71:55:b3:d1:38:ae:ac:08:40:20:c0:c6:7f:54:b9:
53:01:5e:55:f6:0a:5d:31:38:65:05:e0:2e:61:22:
da:d7:db:0a:05:ec:b5:52:e4:48:b3:47:ad:c2:c9:
17:0f:a2:f3
coefficient:
00:d5:c8:d6:dc:58:3e:cd:f3:c3:21:66:3b:a3:2a:
e4:ab:1c:9a:2d:ed:67:02:69:19:93:18:42:09:e9:
39:14:f0:d5:ad:f4:15:63:47:88:d5:91:9d:84:a8:
d7:74:29:95:9d:40:fb:a4:7b:29:cf:70:b9:43:12:
42:17:c9:a4:31
So if $n = pq$, $e$ and $d$ are public and private exponents respectively, then we know:
 200 least significant bits of $p$ (less then half, Coppersmith attack does not work).
 $d_p = d \mod{p1}$.
 $d_q = d \mod{q1}$.
 $q_i \equiv q^{1} \mod{p}$.
Since we know LSBs of $p$, we can try to use the equations modulo $2^{200}$:
$$d_p e \equiv 1 + k_p(p1) \pmod{2^{200}},$$
$$d_q e \equiv 1 + k_q(q1) \pmod{2^{200}},$$
where $k_p$ and $k_q$ are some integers. Note that $k_q = (d_q e – 1) / (q1)$ and $d_q < q1$. Therefore $k_q \le e$. Assuming $e = 65537$ (default exponent), we have few candidates for $k_q$. We can use the equation modulo $2^{200}$ to check the candidates:
q = 0x3acf6684e41176a5b673056b9cd23bd832dc017a57509d471b
dp = 0x00d5a225c0d41b16699c4471570eecd3dd7759736d5781aa7710b31b4a46e441d386da1345bc97d1aa913f853f850f6d4684a80e6067fb71cf213b276c2cbaed59
dq = 0x1338c593d3b5428ce978bed7a553527155b3d138aeac084020c0c67f54b953015e55f60a5d31386505e02e6122dad7db0a05ecb552e448b347adc2c9170fa2f3
q_inv_mod_p = 0x00d5c8d6dc583ecdf3c321663ba32ae4ab1c9a2ded6702691993184209e93914f0d5adf415634788d5919d84a8d77429959d40fba47b29cf70b943124217c9a431
e = 0x10001
mod2 = 2**200
for kq in xrange(e):
if (dq * e) % mod2 == (1 + kq * (q – 1)) % mod2:
print “Good”, kq
break
We immediately get $k_q = 5277$. Then we can recover $q$: $(q1) = (d_q e – 1) / k_q$.
Now we need to recover $p$. Let’s use the “coefficient”:
$$q q_i = 1 + k p,$$
where $k$ is some integer. Therefore we can get $kp$ as $kp = q q_i – 1$. To remove $k$, we can use the relation $a^{e d_p} \equiv a \pmod{p}$: we will compute $gcd$ of $kp$ with $a^{e d_p} – a$ which is some multiple of $p$, for different random $a$:
cp = q_inv_mod_p * q  1
for a in range(5, 1000):
cp = gcd(cp, pow(a, e * dp, cp)  a)
print "cp =", cp
Not all the factors are removed, but the only factor left is 10, so $cp / 10$ is prime and is actually the prime $p$.
Having $p$ and $q$, we can easily extract the flag:
from libnum import *
n = p * q
c = s2n(open("flag.enc").read())
d = invmod(e, (p1)*(q1))
m = pow(c, d, n)
print `n2s(m)`
Result:
'\x02\xf6\xd0K\xd4\x9e\xa1\xf4\x90f\xc0z\xb9\xa4d^5\x04\x9e*\xda\xeb\xec\xf5
\xa6h\\\x88\xd1\x19\x8d\xdf\x13\xc8\x9f\x9e3\xea}\x90v?\xc7n\xc0\xe5\xf8j\x0b
\xca\xb4\x11\xa6!EF\xb1~\xed\x13\x11K:\x03/6\xc0\x08!\x1b<\x92\x1a}=
\x9a&\x000ctf{Keep_ca1m_and_s01ve_the_RSA_Eeeequati0n!!!}\n'
Note the PKCSv1.5 random padding. The flag is: 0ctf{Keep_ca1m_and_s01ve_the_RSA_Eeeequati0n!!!}
]]>
http://mslc.ctf.su/wp/0ctf2016qualsequationcrypto2pts/feed/
4

0CTF 2016 Quals – RSA? (Crypto 2 pts)
http://mslc.ctf.su/wp/0ctf2016qualsrsacrypto2pts/
http://mslc.ctf.su/wp/0ctf2016qualsrsacrypto2pts/#comments
Sun, 13 Mar 2016 19:28:23 +0000
http://mslc.ctf.su/?p=3737
Continue reading »]]>
It seems easy, right?
rsa.zip
Tip: openssl rsautl encrypt in FLAG inkey public.pem pubin out flag.enc
Summary: factoring 300bit modulus into 3 primes, extracting cube roots.
$ openssl rsa in public.pem pubin text
PublicKey: (314 bit)
Modulus:
02:ca:a9:c0:9d:c1:06:1e:50:7e:5b:7f:39:dd:e3:
45:5f:cf:e1:27:a2:c6:9b:62:1c:83:fd:9d:3d:3e:
aa:3a:ac:42:14:7c:d7:18:8c:53
Exponent: 3 (0x3)
writing RSA key
BEGIN PUBLIC KEY
MEEwDQYJKoZIhvcNAQEBBQADMAAwLQIoAsqpwJ3BBh5Qflt/Od3jRV/P4Seixpti
HIP9nT0+qjqsQhR81xiMUwIBAw==
END PUBLIC KEY
314 bits only! Let’s factor it! For example, using CADONFS. It takes only 30 minutes on my laptop.
Interestingly, there are three factors:
N = 32581479300404876772405716877547 x 27038194053540661979045656526063 x 26440615366395242196516853423447.
Moreover, $gcd(3, \phi(N)) = 3$, meaning that we can’t invert the decryption! We have to compute all possible cube roots of the ciphertext. All such algorithms work modulo prime, but that is not a problem, since we have the factorization: we will compute all cube roots modulo each prime and then use Chinese Remainder Theorem to reconstruct the answer modulo $N$. There are some algorithms described in the following paper: New Cube Root Algorithm Based on Third Order Linear Recurrence Relation in Finite Field. The shortest one is the CipollaLehmer algorithm, so I decided to implement it. Here’s Sage function:
def cube_root(a, q):
F = GF(q)
R. = PolynomialRing(F)
while 1:
a = F.random_element()
b = F.random_element()
fx = x**3  a*x**2 + b*x  c
fc = list(factor(fx))
if len(fc) <= 1:
root = pow(x, (q**2+q+1)/3, fx)
root %= x
return int(root)
Note that it yields some cube root. We need to get all of them (at most 3) modulo each prime. We can either extract random cube roots with cube_root until we get all of them, or we can simply multiply a single root by 3rd roots of unity. We can get these roots by finding a multiplicative generator and raising it to power $(p1)/3$.
Here's the code:
p = 26440615366395242196516853423447
q = 27038194053540661979045656526063
r = 32581479300404876772405716877547
c = int(open("flag.enc").read().encode("hex"), 16)
mods = [p, q, r]
rems = []
for mod in mods:
rems.append([])
if gcd(mod  1, 3) == 1:
d = inverse_mod(3, mod  1)
rems[1].append( int(pow(c, d, mod)) )
else:
g = GF(mod).multiplicative_generator()
u = int(g ** ((mod1)/3))
r1 = int(cube_root(c, mod))
for i in xrange(3):
rems[1].append( int(r1 * pow(u, i, mod) % mod) )
print rems[1]
from itertools import product
for testrems in product(*rems):
m = crt(list(testrems), mods)
assert pow(m, 3, p*q*r) == c % (p*q*r)
h = hex(m)
if len(h) & 1:
h = "0" + h
print `h.decode("hex")`
And result:
[13374868592866626517389128266735L, 7379361747422713811654086477766L, 5686385026105901867473638678946L]
[19616973567618515464515107624812L]
[13028011585706956936052628027629L, 6149264605288583791069539134541L, 13404203109409336045283549715377L]
'\x01\x93\x83\x7f\xc1\xe6/\xadr{B\x07\x16\x9ev\x13\xb5"$?S%\x96\xea\xc6}?>0^\x87+\x05~\tk\\\xe8R<'
'\x01c\xb9;\x0c\x87\r\x00\xf3/X\x90\xf2\xec\xa7o[\xb4\xf2\xaao9\x97\xe6\x87~\x1a`i~\xd8\xcc\x19PtP920H'
'\x01\xbf4\xa0O\xe0\x01Z\xd8\xcb\x00\xf1\t\xb6\xc0\x83\x04\x82\xa22\xa2\x02\x88"\x19\xd5\xf2\xb4+\xf3\x1f\x03\x8b\x1d\x90l\xd3\xc9e\xa5'
"\x02BRD:\xd703\xf3\xbe\xc7\xf4\x85\xc7\x88nf\x9a\xc9'f\xbb\xb4\xd3\x1b\xeb\x0e\xe9\x04\x0f\x15\xad f\xaa\x02y\x04\x04\xfc"
'\x02\x12\x87\xff\x85x\r\x87tr\xde~b\x15\xb9\xca\r\x97\x92\x82\xcf\xb5\xce\xdc\xeb\xea\x0b=/gN49\x14\xe7UM\xe3\x08'
'\x02n\x03d\xc8\xd1\x01\xe1Z\x0e\x86\xdex\xdf\xd2\xdd\xb5\xfbG\x1a\xb5\x98\xa6\noC\xc2^\xff\xa3\xad\x85\xa6\x061\x03\xef\xe5\x18e'
'\x02\xd1^\xcb\x84\x84RC\xf3J\x000ctf{HahA!Thi5_1s_n0T_rSa~}\n'
'\x02\x9d\xb0\xda\xb3\xe6g\xc4\x15%\xbc\tF\x8f\x89\x07\x81\xab\x10\xfa\xff\xfb\xf0\xc6F\xba7\xf0\xe9\xbej\x0c\x14s\xf1\xb5\x14\xe0\xe7i'
'.\x82\x7fY~U\xff\xaaC\x08\xea#{\xbe\xd5\xca\xa8\xdf[\x8f\xfeE\x9f\xbc\x8e\x12\xa7n\xf4\x06\x08\xd9\xfe\xf9T\xd8_\x90s'
Don't miss the flag! 0ctf{HahA!Thi5_1s_n0T_rSa~}
]]>
http://mslc.ctf.su/wp/0ctf2016qualsrsacrypto2pts/feed/
1

Boston Key Party CTF 2016 – Feistel (Crypto 5pts)
http://mslc.ctf.su/wp/bostonkeypartyctf2016feistelcrypto5pts/
http://mslc.ctf.su/wp/bostonkeypartyctf2016feistelcrypto5pts/#comments
Sun, 06 Mar 2016 22:00:42 +0000
http://mslc.ctf.su/?p=3675
Continue reading »]]>
feistel – 5 – 15 solves : crypto: I just made a brand new cipher! Can you recover the key?
52.86.232.163:32785
feistel.go
Summary: slide with a twist attack
In this challenge we have access to an encryption and decryption oracle. The cipher is 48bit 34round Feistel Network with 2 alternating keys and simple round function:
func f(key uint32, input uint32) uint32 {
sum := (key + input) & 0x00FFFFFF
return ((sum & 0x001FFF) << 11)  ((sum & 0xFFE000) >> 13)
}
func encrypt(lkey uint32, rkey uint32, left uint32, right uint32) (uint32, uint32) {
for i := 0; i < 17; i++ {
left ^= f(lkey, right)
right ^= f(rkey, left)
}
return left, right
}
Note that the inner cycle body can be alternatively written as two usual Feistel rounds:
left ^= f(lkey, right)
left, right = right, left
left ^= f(rkey, right)
left, right = right, left
Note that bruteforcing the two 24bit keys is quite realistic. Here we will describe more clever attack. For such many rounds with a nonlinear round function it's hard to mount linear or differential attack. There is an attack, which is independent on number of rounds  Slide Attack. Note that straightforward application of this attack is not possible, because there are 2 alternating round keys. However, there are advanced variations of the attack (Advanced Slide Attacks).
The variation which we will use is called Slide Attack with a Twist. The idea is that when the two keys $k_0, k_1$ are alternating and we do the first round encryption, all the next rounds can be seen as partial decryption: indeed, while decrypting, the round functions use keys $k_1, k_0, k_1, \ldots$ instead of $k_0, k_1, k_0, \ldots$. Thus, a slid pair $(P_1, C_1), (P_2, C_2)$ we are looking for can be described as following:
$$P_1 \xrightarrow{K_0} C_2 \xrightarrow{swap} \xrightarrow{K_1} \ldots \xrightarrow{K_1} \xrightarrow{swap} C_1 \xrightarrow{K_0} P_2$$.
We also need to be careful with swaps.
Let $(a, x) = P_1, (b, x) = C_2, (c, y) = C_1, (d, y) = P_2$. To find a correct slid pair we fix some constant $R$, encrypt $2^{12}$ random plaintexts of form $(?, R)$ and decrypt $2^{12}$ random ciphertexts of the same form. These pools correspond to $P_1$ and $C_2$ respectively (or $C_1$ and $P_2$). Among $2^{24}$ pairs of p/c pairs, by birthday paradox with good probability we will find a pair such that $a \oplus b = f(x)+K_0$ for the correct key $K_0$. If the pair is correct, then the same must hold for another side: $c \oplus d = f(y)+K_0$. Thus, we can filter $2^{24}$ pairs quickly and find a correct slid pair. Such slid pair will yield the correct $K_0$. We can then bruteforce $K_1$.
Here's the code:
Script for collecting pairs using gevent
Sample of collected pairs (~11000 pairs).
from itertools import *
from random import randint
def rol(x, n, bits):
mask = (1 << bits)  1
n %= bits
x &= mask
return ((x << n)  (x >> (bits  n))) & mask
def split(p):
l, r = int(p[:6], 16), int(p[6:], 16)
return l, r
spairs = []
for line in open("known.txt"):
p, c = line.strip().split(":")
spairs.append((split(p), split(c)))
print "Known:", len(spairs)
def f(c, k):
res = (c + k) & 0x00FFFFFF
return rol(res, 11, 24)
def f_extract_key(res, inp):
res = rol(res, 13, 24)
return (res  inp) & 0x00FFFFFF
def encrypt(k0, k1, l, r):
for i in range(17):
l ^= f(k0, r)
l, r = r, l
l ^= f(k1, r)
l, r = r, l
return l, r
for ((a, x), (c, y)), ((d, y2), (b, x2)) in combinations(spairs, r=2):
if x != x2:
continue
if y != y2:
continue
ktop = f_extract_key(a ^ b, x)
kbot = f_extract_key(c ^ d, y)
if ktop != kbot:
continue
print "Candidate K0", hex(ktop)
k0 = ktop
for k1 in xrange(1 << 24):
for p, c in spairs[:10]:
ct = encrypt(k0, k1, p[0], p[1])
if c != ct:
break
else:
print "Flag BKPCTF{%06x%06x}" % (k0, k1)
quit()
The flag is BKPCTF{32dfa57b9ef0}.
]]>
http://mslc.ctf.su/wp/bostonkeypartyctf2016feistelcrypto5pts/feed/
1

Boston Key Party CTF 2016 – GCM (Crypto 9pts)
http://mslc.ctf.su/wp/bostonkeypartyctf2016gcmcrypto9pts2/
http://mslc.ctf.su/wp/bostonkeypartyctf2016gcmcrypto9pts2/#comments
Sun, 06 Mar 2016 22:00:34 +0000
http://mslc.ctf.su/?p=3673
Continue reading »]]>
[8] : gsilvis counting magic – 9 – 4 solves : crypto: Here’s a verification/decryption server: gcm.ctf.bostonkey.party:32768 . Get the GCM MAC key (the thing the server prints out on startup). We’ve given you one valid ciphertext to get you started. It has iv: [102 97 110 116 97 115 116 105 99 32 105 118] and tag: [119 179]
gcmtask.tar
Summary: breaking AESGCM with 2byte tag
In this challenge we have access to AESGCM decryption oracle. Our goal is to recover $H = AES_K(0)$. This value is used in the GCM routines.
AESGCM is authenticated cipher. It means that decrypting modified ciphertexts yields no information except that the ciphertext is incorrect. In GCM authentication is guaranteed by appending a tag to the ciphertext. The tag depends on IV, key, plaintext and possibly additional authentication data. In this challenge tag length is very short: 2 bytes, which makes forgeries possible. Since our goal is to recover some intermediate value of GCM decryption, we will use oracle to find correct tags for our specially crafted ciphertexts by trial. This will leak us about 16 bits of information. Note that to find each tag we need roughly $2^{16}$ queries.
The AESGCM decryption scheme:
$mult_H$ is multiplication in $GF(2^{128})$ defined by irreducible polynomial $X^{128} + X^7 + X^2 + X^1 + 1$ in little endian bit order.
Let’s consider oneblock ciphertexts and fix everything except the block itself. Let the block value be $C$. Then the tag has the following form:
$$T(C) = MSB_{16}\left[ ((f(A) + C) \times H + g(len(A), len(C))) \times H + E_k(IV  0) \right] =$$
$$ = MSB_{16}\left[ C\times H^2 + t \right],$$
where $f, g$ are some functions and $t$ is some value which does not depend on value of $C$ (but depends on the length); all operations are done in the finite field. Consider the following differential:
$$T(C) + T(C + \Delta) = MSB_{16}(\Delta \times H^2)$$.
Thus, we can recover 16 most significant bits of $\Delta \times H^2$ for arbitrary $\Delta$. Note that multiplication by a constant in finite field is linear and can be written as multiplication by a binary matrix. Therefore we obtain 16 linear equations because of truncation. If we obtain enough equations, we will be able to recover $H^2$ and then recover $H$ by exponentiation.
The algorithm is as follows:
 Fix $C = 0$.
 Obtain several tags $T(C + \Delta)$ for random $\Delta$.
 Construct the system of linear equations and solve it.
 Recover $H$.
Here’s Sage code:
from sage.all import *
def s2n(s):
if not len(s):
return 0
return int(s.encode("hex"), 16)
def tobin(x, n):
x = Integer(x)
nbits = x.nbits()
assert nbits <= n
return [0] * (n  nbits) + x.bits()[::1]
def frombin(v):
return int("".join(map(str, v)), 2 )
def mat_from_linear_func(m, n, func):
mat = matrix(GF(2), n, m)
for i, e in enumerate(reversed(range(m))):
x = 1 << e
res = tobin(func(x), n)
mat.set_column(i, res)
return mat
X = GF(2).polynomial_ring().gen()
poly = X**128 + X**7 + X**2 + X**1 + 1
F = GF(2**128, name='a', modulus=poly)
def toF(x):
# Little endian, so need bit reverse
x = frombin(tobin(x, 128)[::1])
return F.fetch_int(x)
def fromF(x):
# Little endian, so need bit reverse
x = x.integer_representation()
x = frombin(tobin(x, 128)[::1])
return x
def field_mult(a, b):
return fromF(toF(a) * toF(b))
# tags obtained by trial queries to the decryption oracle
texts = [
"66616e74617374696320697600000000000000000000000000000000e1e9",
"66616e7461737469632069764d5c97eb7c1db17c26802261d8a0f8ce0ab6",
"66616e74617374696320697609c0d6d145e19ca06d6396559e9d28bc6d86",
"66616e746173746963206976cab6bb9ab23006666338a6b0f10cf77d410b",
"66616e746173746963206976fb562207bfa33dd32075dacbaa5b8998ec5e",
"66616e74617374696320697671baa8fb9275320eae9f5bc534d71129a8d0",
"66616e74617374696320697647f195cfc16961a29cf0e6b51ff34cb2d505",
"66616e7461737469632069764d5c97eb7c1db17c26802261d8a0f8ce0ab6",
"66616e746173746963206976661fb2b4a720debaec3054f2641bf6157cb9",
"66616e7461737469632069766f0367a3863ffed6c6abeb66d133d6136fa7",
"66616e746173746963206976ad07f9965c4d3397b6f3c7d121b6036e2e92",
]
ca_lst = []
def blocktag(t):
return int(t[24:4], 16), int(t[4:], 16)
b0, t0 = blocktag(texts[0])
for t in texts[1:]:
block, tag = blocktag(t)
ca_lst.append((block ^ b0, tag ^ t0))
fullmat = matrix(GF(2), 16 * len(ca_lst), 128)
fullvec = vector(GF(2), 16 * len(ca_lst))
for i, (c, a) in enumerate(ca_lst):
print i, hex(c), hex(a)
m = mat_from_linear_func(128, 128, lambda x: field_mult(x, c))
fullmat[i*16:i*16+16, :] = m[:16,:]
fullvec[i*16:i*16+16] = vector(GF(2), tobin(a, 16))
print "RANK", fullmat.rank()
sol = fullmat.solve_right(fullvec)
assert fullmat * sol == fullvec
H2 = frombin(sol)
# invert squaring
d = inverse_mod(2, 2**1281)
h = fromF(toF(H2)**d)
print "BKPCTF{%032x}" % h
The flag is BKPCTF{40db7f1b3eecb5ae763b1f125f844793}.
]]>
http://mslc.ctf.su/wp/bostonkeypartyctf2016gcmcrypto9pts2/feed/
1

Boston Key Party CTF 2016 – HMACCRC (Crypto 5pts)
http://mslc.ctf.su/wp/bostonkeypartyctf2016hmaccrccrypto5pts/
http://mslc.ctf.su/wp/bostonkeypartyctf2016hmaccrccrypto5pts/#comments
Sun, 06 Mar 2016 22:00:25 +0000
http://mslc.ctf.su/?p=3671
Continue reading »]]>
[3] : hmac_crc – 5 – 36 solves : crypto: We’re trying a new mac here at BKP—HMACCRC. The hmac (with our key) of “zupe zecret” is ‘0xa57d43a032feb286’. What’s the hmac of “BKPCTF”?
Summary: breaking HMACCRC (again)
CRC_POLY = to_bits(65, (2**64) + 0xeff67c77d13835f7)
CONST = to_bits(64, 0xabaddeadbeef1dea)
def crc(mesg):
mesg += CONST
shift = 0
while shift < len(mesg)  64:
if mesg[shift]:
for i in range(65):
mesg[shift + i] ^= CRC_POLY[i]
shift += 1
return mesg[64:]
def hmac(h, key, mesg):
return h(xor(key, OUTER) + h(xor(key, INNER) + mesg))
This challenge is similar to MMACTF Motto Mijikai Address. Given one plaintext and its HMACCRC, we need to forge HMACCRC for another plaintext. Recall that
$$HMAC{}CRC(m, k) = CRC( [K \oplus opad]  CRC ( [K \oplus ipad]  m)),$$
where $opad$ and $ipad$ are some constant strings. Recall also that CRC is a multiplication of polynomials modulo the CRC polynomial $P(x)$ and is affine over $GF(2)$. Note that when the message $m$ is fixed, HMACCRC is affine function of $k$. Moreover, composition of two CRCs is still a multiplication by a polynomial (plus some independent terms, which we consider constant when $m$ is fixed). We can write
$$HMAC{}CRC(m, k) = (q_m(x) \oplus r_m(x)k(x)) \mod P(x),$$
where $q_m(x),r_m(x)$ are some polynomials depending on $m$. We could find the polynomials by carefully writing down the equation for HMAC. But we can do it easier using the following differentials:
$$HMAC{}CRC(m, 0) \oplus HMAC{}CRC(m, 1) = r_m(x) \mod P(x).$$
Then we can use known $HMAC{}CRC(m, k)$:
$$HMAC{}CRC(m, 0) \oplus HMAC{}CRC(m, k) = (r_m(x) k(x)) \mod P(x).$$
And then we can hopefully invert $r_m(x)$ and recover $k$.
Here's Sage code for that:
from sage.all import *
def ntopoly(npoly):
return sum(c*X**e
for e, c in enumerate(Integer(npoly).bits()))
def polyton(poly):
return sum(int(poly[i])*(1 << i)
for i in xrange(poly.degree() + 1))
X = GF(2).polynomial_ring().gen()
N = 64
poly = ntopoly(0xeff67c77d13835f7 + 2**64)
m = "zupe zecret"
hmac = 0xa57d43a032feb286
# obtain from running hmactask on m = "zupe secret"
hmac0 = 0xef6bbf832c7eced2
hmac1 = 0xf39e6a4125801b84
r = ntopoly(hmac1 ^^ hmac0)
kr = ntopoly(hmac ^^ hmac0)
g = gcd(poly, r)
assert g == 1
K = (kr * inverse_mod(r, poly)) % poly
print "K: 0x%x" % polyton(K)
We obtain K = 0xae5617703acedc88
. Plugging it in hmactask.py, we get the flag: BKPCTF{0xd2db2b8b9002841f}.
]]>
http://mslc.ctf.su/wp/bostonkeypartyctf2016hmaccrccrypto5pts/feed/
1

MMA CTF 2015 – Motto Mijikai Address (Crypto/Web 100+300)
http://mslc.ctf.su/wp/mmactf2015mottomijikaiaddresscryptoweb100300/
http://mslc.ctf.su/wp/mmactf2015mottomijikaiaddresscryptoweb100300/#comments
Mon, 07 Sep 2015 14:40:29 +0000
http://mslc.ctf.su/?p=3607
Continue reading »]]>
Login as admin and get the flag1.
mmaddress.7z
Summary: breaking HMACCRC512
In this challenge we can register users, login and create shortened urls. Our first goal is to login as admin. Session data is stored in cookies, signed with some HMAC. Let’s look at the hash function used in HMAC:
def hash(str, v = 0)
hash = MASK ^ v
str.unpack("C*").each do v
hash = (table[((hash) >> (N  8)) ^ v] ^ hash << 8) & MASK
end
hash ^ MASK
end
This construction should be familiar: it is CRC code (together with table generation). Though here we don't even know the polynomial. We can exploit linearity of CRC  it is known that CRC(a^b^c) = CRC(a) ^ CRC(b) ^ CRC(c). Easy to check that this property applies to HMAC too:
CRC[ K^opad  CRC(K^ipad  m1^m2^m3) ] =
CRC[ K^opad  CRC(K^ipad  m1) ] ^
CRC[ K^opad  CRC(K^ipad  m2) ] ^
CRC[ K^opad  CRC(K^ipad  m3) ]
We can use this property to make valid HMAC for admin session. The exploit is very easy: register two user with random 5char names, register third user with name equal to xor of two previous usernames and string "admin". Then xor all session datas and xor all hmacs! Here's python code:
import requests, os
from urllib import unquote, quote
from base64 import b64decode, b64encode
def xor(a, b):
return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in xrange(len(a))])
def register(u, p):
data = {"user": u, "password": p}
r = requests.post("http://mmaddress.chal.mmactf.link/register", data=data)
# print r.content
return True
def login(u, p):
data = {"user": u, "password": p}
sess = requests.session()
r = sess.post("http://mmaddress.chal.mmactf.link/login", data=data)
a, b = unquote(list(sess.cookies)[0].value).split("")
a = b64decode(a)
b = b64decode(b)
return a, b
r = os.urandom(2).encode("hex")
p = "1"
users = [r + "z", "z" + r]
users.append(xor("admin", xor(users[0], users[1])))
lsta = []
lstb = []
for u in users:
print "Reg", u, p
register(u, p)
a, b = login(u, p)
lsta.append(a)
lstb.append(b)
aa = xor(xor(lsta[0], lsta[1]), lsta[2])
bb = xor(xor(lstb[0], lstb[1]), lstb[2])
print `aa`, `bb`
cook = quote(b64encode(aa)) + "" + quote(b64encode(bb))
print cook
Run and get cookie:
Reg 7313z 1
Reg z7313 1
Reg ,`ok' 1
{"user":"admin","password":"626cc181800cc661fe06600000033000000d5b567fe60006601983c560a60cc0192bab06635aa607f8000000aa00660d3d30000000660000019819864a8cbe2b"}'
eyJ1c2VyIjoiYWRtaW4iLCJwYXNzd29yZCI6IjYyNmNjMTgxODAwY2M2NjFmZTA2NjAwMDAwMDMzMDAwMDAwZDViNTY3ZmU2MDAwNjYwMTk4M2M1NjBhNjBjYzAxOTJiYWIwNjYzNWFhNjA3ZjgwMDAwMDBhYTAwNjYwZDNkMzAwMDAwMDA2NjAwMDAwMTk4MTk4NjRhOGNiZTJiIn0%3D5iLUP9hkSfI4/6inAtrceRLBkQflTLd%2BJz1gTgQXliwnWRE6k94kcOwMAIUYoSVZmWGxxrmut3csQl7e5aEOVw%3D%3D
Use cookie and see recent url https://gist.github.com/uecmma/25c38a6b18b41854cd68. The first flag is MMA{8f07ea4e3f79f483e3a81656b196af7d}.
The second flag
The second flag is the HMAC key used:
HMAC.hmac(json, FLAG, 512)
Learning the polynomial
The first thing we should do is to extract the CRC polynomial. CRC is basically polynomial modulo reduction in $\mathbb{F}_2[x]$ with additional masking before and after. We can write:
$$MyHash(m) = CRC_{512}(m) = (MASK \cdot (x^M + 1) + mx^{512}) \mod P(x).$$
, where $M = len(m)$ and $P(x)$ is the unknown polynomial.
Here $MASK \cdot x^M$ corresponds to the initial mask, $MASK \cdot 1$ corresponds to the final mask. $+$ and XOR are the same since we work in $\mathbb{F}_2[x]$. Multiplying by $x^M$ is the same as shifting value left by $M$ bit.
To learn $P(x)$ we need to obtain a few pairs $(m,CRC_{512}(m))$. A good thing is that besides HMAC we have direct access to the $CRC_{512}$ function  the hash of password is stored in the session json in the cookie. For each message we can compute $CRC_{512}$ without modulo reduction. From it we can subtract the reduced value (which we've got from the server) and get a multiple of $P(x)$:
$$(MASK \cdot (x^M + 1) + mx^{512})  CRC_{512}(m) = R(x)P(x).$$
Finally we can compute $P(x)$ by running $gcd$ on several such values, until we stop at polynomial of degree 512:
$$P(x) = gcd(R_1(x)P(x), R_2(x)P(x), \ldots).$$
Let's do it:
from sage.all import *
def ntopoly(npoly):
return sum(c*X**e for e, c in enumerate(Integer(npoly).bits()))
def polyton(poly):
return sum(int(poly[i])*(1 << i) for i in xrange(poly.degree() + 1))
X = GF(2).polynomial_ring().gen()
N = 512
MASK = (1 << N)  1
pMASK = ntopoly(MASK)
data = {
"password": 0x4910b1bb284eee2f49f41043c3a9e3d4f2deaa5319d689d4fa8c04732bc8d46a84b54ef3c52aa79883e56b225c87f68181522b28b71c7be29a98b528a791374f,
"passwore": 0x4e18a1b9084efe274b741843c3a9e7d4f2deb857997609d4f28c247672d8547a8494ccb3cd2e371889e56b225d07f60193d62b28b71cfbe29a9ab508aff127ec,
}
multiples = []
for msg, crc in data.items():
M = len(msg) * 8
msgnum = int(msg.encode("hex"), 16)
m = ntopoly(msgnum)
rem = ntopoly(crc)
mult = pMASK * (X**M + 1) + m * X**N  rem
multiples.append(mult)
g = reduce(gcd, multiples)
print "Degree", g.degree()
print "G: 0x" + hex(polyton(g))
The poly is: 0x1070810022000100802800800000004000000120480a08000080020055910801000218240080490800a00000001800080128400000000800000020020086010a3.
Learning the key
Now we go back to HMAC and recover the key.
$$HMAC(m, K) = CRC_{512}( [K \oplus opad]  CRC_{512}( [K \oplus ipad]  m)).$$
Since everything is done modulo $P(x)$ which we know and XOR is the same as addition, we can work out a single nice formula for HMAC ($\mathbb{I}$ is ipad, $\mathbb{O}$ is opad).
$CRC_{512}( [K \oplus \mathbb{I}]  m) = $
$ = \quad(MASK \cdot (x^{512+M}+1) + mx^{512} + Kx^{512+M} + \mathbb{I} x^{512+M}) \mod P(x)$
$HMAC(m, K) = (MASK \cdot (x^{1024+M} + x^{1024} + x^{512})$
$+\quad mx^{1024} $
$+\quad \mathbb{O} x^{1024} $
$+\quad \mathbb{I} x^{1024+M} $
$+\quad K (x^{1024+M} + x^{1024}) $
$ ) \mod P(x)$
Since only $K$ is unknown now, we can solve for it::
$$K = (HMAC(m, K)  (...)) / (x^{1024+M} + x^{1024}) \pmod{P(x)}$$.
Here comes a problem: $(x^{1024+M} + x^{1024})$ is not invertible modulo $P(x)$! It is possible because $P(x)$ is not irreducible, more precisely, $x+1$ divides both $P(x)$ and $(x^{1024+M} + x^{1024})$ for all $M$. Luckily, this is the only common divizor. It means that we have lost information about $K \mod (x+1)$. It's not a problem, there are only two possibilities: 0 and 1. So let's drop $(x+1)$ and add it later using Chinese Remainder Theorem.
Note: even if the factor was large and there were many possibilities, any value will work for HMAC. Moreover, there is no way to distinguish which $K$ is actually used. However we know that real $K$ is a flag so we can check all candidates and check if any of them looks like flag.
# ... first part continued
poly = g
I = ntopoly(int(("36"*64), 16))
O = ntopoly(int(("5C"*64), 16))
msg = '{"user":"qwef7a6dbda","password":"4e18a1b9084efe274b741843c3a9e7d4f2deb857997609d4f28c247672d8547a8494ccb3cd2e371889e56b225d07f60193d62b28b71cfbe29a9ab508aff127ec"}'
real_hmac = 0xe13060504b419a311d271833b188e2479ba64c5f1b639897980f64670e4e4142d5b9045c1a20f91156ca9997eae47f4f6171849e0f00776ab5bb6984917a1667
M = len(msg) * 8
m = ntopoly(int(msg.encode("hex"), 16))
full = ntopoly(real_hmac)
part = pMASK * (X**(1024+M) + X**1024 + X**512 + 1)
part += O * X**1024
part += I * X**512 * X**(512+M)
part += m * X**512 * X**512
# part += K * X**1024 * (X**M + 1)
part %= poly
kc = full  part
c = X**1024 + X**(1024+M)
g = gcd(poly, c) # (x + 1)
redK = (kc // g) * inverse_mod(c // g, poly // g)
redK %= poly // g
for guess in xrange(2): # mod (x + 1)
K = crt([redK, ntopoly(guess)], [poly // g, g])
print "guess", guess, ":", hex(polyton(K))
print "t", `hex(polyton(K)).decode("hex")`
And with guess=1 we have the second flag: MMA{CRC_HMAC_IS_NOT_SECURE}.
PS: If you want to try these tricks with the usual crc32, bear in mind that it is a bit weird: bits in each byte are processed from LSB to MSB, and also the polynomial (and so the result) is represented in reversed way (e.g. $x^{31}$ corresponds to the 1st LSB).
]]>
http://mslc.ctf.su/wp/mmactf2015mottomijikaiaddresscryptoweb100300/feed/
1

DEFCON CTF Survival Guide (2014)
http://mslc.ctf.su/wp/defconctfsurvivalguide2014/
http://mslc.ctf.su/wp/defconctfsurvivalguide2014/#comments
Sat, 05 Sep 2015 18:08:16 +0000
http://mslc.ctf.su/?p=3587
vos and snk from MSLC share their basic view of AttackDefence CTFs and tell random stories in their twohour talk at Chaos Constructions 2014.
With English subtitles
]]>
http://mslc.ctf.su/wp/defconctfsurvivalguide2014/feed/
1

CONFidence CTF 2015 – RSA2 (Crypto 500)
http://mslc.ctf.su/wp/confidencectf2015rsa2crypto500/
http://mslc.ctf.su/wp/confidencectf2015rsa2crypto500/#comments
Wed, 27 May 2015 19:10:08 +0000
http://mslc.ctf.su/?p=3567
Continue reading »]]>
Find the flag
data
Summary: cube attack + recover python’s MersenneTwister state + leak 320/520 LSBs of one of the primes
In the second part of the challenge, we have this code:
def generate_40_bytes_random_number():
return int(open('/dev/urandom').read(80).encode('hex'), 16)
def generate_random_number_bytes(n):
res = 0
while res < 2**(8*n):
res += generate_40_bytes_random_number()
res *= 2**(40*8)
return res
def get_prime(n):
while (not check_prime(n)) or (((n1) % 3) == 0):
n += random.getrandbits(512)
return n
r = generate_random_number_bytes(1024)
p = get_prime(r % (2**512))
q = get_prime((r >> 512) % (2**512))
n = p*q
open('out/n1','w').write(str(n))
e=3
flag_index = random.getrandbits(10)
for i in range(0, 1024):
if i <> flag_index:
encryptedFlag = pow(random.getrandbits(32), e, n)
open('out/flag'+str(i),'w').write(str(encryptedFlag))
r = generate_random_number_bytes(1024)
p = get_prime(r % (2**512))
q = get_prime((r >> 512) % (2**512))
n = p*q
open('out/n2','w').write(str(n))
e=3
encryptedFlag = pow(FLAG, e, n)
open('out/flag'+str(flag_index),'w').write(str(encryptedFlag))
Note that generate_random_number_bytes uses secure random generator, while get_prime uses standard Python’s MersenneTwister. Also note implementation bug in generate_random_number_bytes: 320 LSBs of the result are always zero. This means one of the primes will have 320 LSBs from MT random.
Also 1023 MT random values are given, so we can easily recover the state and predict 320 LSBs of one of the primes. This is enough to factor modulus.
The first stage is to recover the MT random state. Note that due to small public exponent (e=3) and small 32bit values we can recover them by simply extracting cube root over integers. Script for MersenneTwister::untemper
#!/usr/bin/env python
#* coding:utf8 *
import gmpy
import random
from mt import untemper
n1 = int(open("out/n1").read())
values = []
for i in xrange(1024):
c = int(open("out/flag%d" % i).read())
r, is_cube = gmpy.root(c, 3)
if not is_cube:
print "secret index:", i
continue
values.append(int(r))
mt_state = tuple(map(untemper, values[:624]) + [0])
random.setstate((3, mt_state, None))
for i in xrange(1023):
r = random.getrandbits(32)
assert r == values[i], "Fail 1"
predicted = [random.getrandbits(512) for _ in xrange(5000)]
open("out/predicted", "w").write(str(predicted))
The script found the secret index: 826. Now we can predict 320 LSBs of the prime. Actually we don’t know how many times a random number was generated in get_prime, but the experiments show that it’s usually less than 3000 so we can check them all.
To factor the modulus given LSBs we can use the Coppersmith’s method for finding small roots of polynomials as described in the paper by BonehDurfeeFrankel. Here’s the outline:
Assume we know t and r such that p = rx + t. In our case r is 2^{320}. We multiply both sides by inverse of r mod N: r^{1}p = x + r^{1}t (mod 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 code (Sage):
from sage.all import *
N = int(open("out/n2").read())
C = int(open("out/flag826").read())
predicted = eval(open("out/predicted").read())
PR. = PolynomialRing(Zmod(N))
mod = 1 << 320
imod = inverse_mod(mod, N)
t = 0
for i, rnd in enumerate(predicted):
t += rnd
t %= mod
f = x + t * imod
roots = f.small_roots(X=2**(540320), beta=0.4)
print "#%d:" % i, roots
if roots:
root = int(roots[0])
kq = root + t * imod
q = gcd(N, kq)
assert q != 1, "Fail"
assert q != N, "Fail"
p = N / q
d = inverse_mod(3, (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")`
On the 151st iteration the flag is revealed: DrgnS{ThisFlagMustBeEnteredVerballyW_Szczebrzeszynie_chrzaszcz_brzmi_w_trzcinie_I_Szczebrzeszyn_z_tego_slynie}
]]>
http://mslc.ctf.su/wp/confidencectf2015rsa2crypto500/feed/
3

CONFidence CTF 2015 – RSA1 (Crypto 400)
http://mslc.ctf.su/wp/confidencectf2015rsa1crypto400/
http://mslc.ctf.su/wp/confidencectf2015rsa1crypto400/#respond
Wed, 27 May 2015 19:09:58 +0000
http://mslc.ctf.su/?p=3565
512) % (2**512)) Flag is also encrypted under another random modulus (n2) but… Continue reading »]]>
Find the flag
data
Summary: Coppersmith’s short pad attack
In this challenge we are given a python script and a set of files generated by it. Here’s the main part:
r = generate_random_number_bytes(1024)
p = get_prime(r % (2**512))
q = get_prime((r << 512) % (2**512))
n = p*q
open('out/n1','w').write(str(n))
e=3
flag_index = random.getrandbits(10)
for i in range(0, 1024):
if i <> flag_index:
encryptedFlag = pow(FLAG * 2**32 + random.getrandbits(32), e, n)
open('out/flag'+str(i),'w').write(str(encryptedFlag))
r = generate_random_number_bytes(1024)
p = get_prime(r % (2**512))
q = get_prime((r >> 512) % (2**512))
Flag is also encrypted under another random modulus (n2) but it is not relevant for this challenge (the author confirmed it was added to make the tasks rsa1 and rsa2 look similar).
Here we have 1023 messages encrypted. Each message is the flag padded with 32 random least significant bits. There is a classic attack for this setting – Coppersmith’s Short Pad attack. The description on Wikipedia is enough to implement the attack in Sage, though there is a little problem: Sage doesn’t want to compute the so called resultant of two polynomials modulo composite number:
ValueError: Resultants require base fields or integer base ring.
We can overcome this issue by switching the ring to ZZ and back.
The first phase of the attack is to take some two of the encrypted messages and recover the small difference between them:
from sage.all import *
n1 = long(open("out/n1").read())
e = 3
C1 = long(open("out/flag0").read())
C2 = long(open("out/flag1").read())
PRxy. = PolynomialRing(Zmod(n1))
PRx. = PolynomialRing(Zmod(n1))
PRZZ. = PolynomialRing(Zmod(n1))
g1 = x**e  C1
g2 = (x + y)**e  C2
q1 = g1.change_ring(PRZZ)
q2 = g2.change_ring(PRZZ)
h = q2.resultant(q1)
# need to switch to univariate polynomial ring
# because .small_roots is implemented only for univariate
h = h.univariate_polynomial() # x is hopefully eliminated
h = h.change_ring(PRx).subs(y=xn)
h = h.monic()
roots = h.small_roots(X=2**40, beta=0.3)
assert roots, "Failed1"
diff = roots[0]
if diff > 2**32:
diff = diff
C1, C2 = C2, C1
print "Difference:", diff
The second stage is to apply FranklinReiter related messages attack to recover the message:
x = PRx.gen() # otherwise write xn
g1 = x**e  C1
g2 = (x + diff)**e  C2
# gcd
while g2:
g1, g2 = g2, g1 % g2
g = g1.monic()
assert g.degree() == 1, "Failed 2"
# g = xn  msg
msg = g[0]
# convert to str
h = hex(int(msg))[2:].rstrip("L")
h = "0" * (len(h) % 2) + h
print `h.decode("hex")`
The flag: Drgns{She’s_playin’_dumb_all_the_timeJust_to_keep_it_funTo_get_you_on_like_ahh!Be_careful_amigoShe_talkin’_and_walkin}
]]>
http://mslc.ctf.su/wp/confidencectf2015rsa1crypto400/feed/
0

ASIS CTF Quals 2015 – Cross Check (Crypto 350)
http://mslc.ctf.su/wp/asisctfquals2015crosscheckcrypto350/
http://mslc.ctf.su/wp/asisctfquals2015crosscheckcrypto350/#comments
Mon, 11 May 2015 08:03:02 +0000
http://mslc.ctf.su/?p=3527
The flag is encrypted by this code, can you decrypt it?
crosscheck.tar.xz
Summary: breaking RSA modulos with related primes.
In this challenge we have 3 RSA modulos and 3 parts of flag encrypted. Here’s how the keys are generated:
p = random.randint(1 << 510, 1 << 511)
q = random.randint(1 << 510, 1 << 511)
for i in range(0, 3):
p = gmpy.next_prime(p)
q = gmpy.next_prime(q)
P.append(p)
Q.append(q)
for i in range(0, 3):
N.append(P[i] * Q[2  i])
All the primes are very close to each other. Each modulus is related to any other: one factor is slightly increased, another one is slightly decreased.
Note: if both factors were slightly increased (e.g. N[i] = P[i] * Q[i]), then it would be easy to break with Fermat method for factorization for N[0] * N[1]: it would yield us P[0]*Q[1] and P[1]*Q[0] and then GCD would reveal all the factors.
Let's look closer at some pair of modulos, let's say N[0] and N[1] (p and q redefined for simplicity):
We want to exploit the fact that and are small (we can bruteforce them). Consider linear combination of this modulos:
If we consider this modulo (a + b) then we will have:
Let M be some large prime,a=1 and b=M1. Then
And if M is large enough, modulo reduction can be dropped and we will learn the exact value of
UPD: I just realized that modulo M is not needed at all. Simply
.
Having this linear combination of p and q we can easily bruteforce and and factor all the modulos!
(quadratic equation over integers with unknown q).
The code:
M = gmpy.next_prime(1 << 1000)
C = (N[0] + (M  1) * N[1]) % M
if C > M / 2:
# p s_q  q s_p can be negative
C = (M  C)
p_sp = None
for sp in xrange(1, 2000):
for sq in xrange(1, 2000):
# solve quadratic equation over ZZ
a = sp
b = C + sp * sq
c = sq * (C  N[0])
D = b ** 2  4 * a * c
if D < 0:
continue
sqrD = D.root(2)[0]
if sqrD ** 2 != D:
continue
for add in (sqrD, sqrD):
q = (b + add) / (2 * a)
if N[1] % q == 0:
p_sp = N[1] / q
break
if p_sp:
break
assert p_sp, "no solution?"
P = [None] * 3
Q = [None] * 3
P[2] = gmpy.next_prime(p_sp)
Q[0] = N[2] / P[2]
Q[1] = gmpy.next_prime(Q[0])
Q[2] = gmpy.next_prime(Q[1])
P[0] = N[0] / Q[2]
P[1] = gmpy.next_prime(P[0])
assert P[0] * Q[2] == N[0]
assert P[1] * Q[1] == N[1]
assert P[2] * Q[0] == N[2]
cts = [
"DjXmcsw0QXBRBiUOx2Uu4kS/gFvIYyf6MSJLWlwy8i7WjVB8vOtUb90GTFSuHDX6iawvUg/XVjU7DVAi9EhMGSS2LyvgMH4nD4gjlVlC2PkxkNDILuUq//5DMoTUyootReoke1jXDnmHg1y0KglkIylL2dufsHc74cAKnciNU9U=",
"DkYN4JwQU+EstIvIs662BISkzXxqqfb62DrJFV5efVuXtkLSQrzHgLczgC1blF8e29x46Jz2yjqb1eb2IJX59yuDBHiQ13ckId+E732Bpu00ee9UqYtSNNnQFIo8LIBWFxIUK3+XjNynDD9ArcF5OkLvk8o8AU1DcAdusQtsURo=",
"CuMo9lJNex64Wh63DORfMPkcwX7inwNd3QEi/OKb2vbh69v4iF46/4tCz8ZF7FEKfNxmXuyPREdS7YPqNGi9hQT21CmeiXe/AbDCITrhYVMWJi4A6wjZjkdslHklnmJFnULRkSLVCYAgVQAbPGO3CmQ+3y9QSAhZM5qi8NQnoOo="
]
for i in range(0, 3):
d = gmpy.invert(long(65537), (P[i]  1) * (Q[2  i]  1))
priv = RSA.construct((long(N[i]), long(65537), long(d), long(P[i]), long(Q[2  i])))
priv = PKCS1_v1_5.new(priv)
ct = cts[i].decode("base64")
print priv.decrypt(ct, None)
$ time py cross_check.py
hi all the flag is
ASIS{a0c8f997d5cdd6
99d336b0f2f12af326}
real 0m1.110s
user 0m1.091s
sys 0m0.016s
]]>
http://mslc.ctf.su/wp/asisctfquals2015crosscheckcrypto350/feed/
5

VolgaCTF Quals 2015 – CPKC (Crypto 400) writeup
http://mslc.ctf.su/wp/volgactfquals2015cpkccrypto400writeup/
http://mslc.ctf.su/wp/volgactfquals2015cpkccrypto400writeup/#comments
Sun, 03 May 2015 22:46:48 +0000
http://mslc.ctf.su/?p=3497
Continue reading »]]>
cpkc
A homebrewed cryptosystem, should be easy to break. Its keyspace seems to be rather large though…
Summary: LLLbased attack on NTRUEncryptlike cryptosystem.
1. Cryptosystem
The private key consists of three numbers: (f, g, q): q is a large prime, f and g are random numbers smaller than sqrt(q). Also f must be invertible (mod g).
The public key consists of (h, q), where h = g/f (mod q).
Encryption of a message m which must be smaller than g is:
c = rand * h + m (mod q), where rand is an ephemeral key also smaller than sqrt(q).
Decryption of a ciphertext c is:
m = [c * f (mod q)] / f (mod g) = [rand * g + m * f (mod q)] / f (mod g) = m. The last equation holds because rand * g + m * f < q (it holds because of sizes of the numbers), thus (mod q) can be dropped.
2. Attack
We know h = g / f (mod q), thus f * h = g (mod q) and so f * h = g + kq for some integer k. Also we know that f and g must be small. Note that encryption used only h, not f or g, so any (g,f) pair satisfying the constrains will be good for decryption.
Since we need to find small values, it is reasonable to try LLL algorithm. Indeed, let’s run LLL on two vectors:
 (1, h)
 (0, q)
The output vectors will look like (x, x*h + y*q). Note that x*h + y*q will lie in [q/2; q/2], otherwise we could increase/decrease y to make the value smaller.
So x*h + y*q is something like x*h mod q. Thus we have a small vector like (x, x*h mod q) – but that’s exactly what we wanted – just let f = x, then g = x*h mod q = f*h mod q and both f and g are small.
To deal with negative numbers, note that we can multiply both f and g by 1: h=f/g=f/g (mod q). If only one of them is negative, we can try to make some positive linear combination of the two vectors we have, but that’s rarely needed.
Solution code (sage):
import sys
sys.path.append("/usr/lib/python2.7/distpackages/") # for gmpy2
from sage.all import *
from cpkc import PublicKey, PrivateKey, decrypt
pub = PublicKey()
pub.read("key.public")
h = int(pub.h)
q = int(pub.q)
M = MatrixSpace(ZZ, 2)([
[1, h],
[0, q],
])
ML = M.LLL()
print ML.str()
print ""
for row in ML.rows():
f, g = row
if f < 0 and g < 0:
g *= 1
f *= 1
if f > 0 and g > 0:
break
else:
print "error, try linear combination?"
quit()
assert (g * inverse_mod(f, q)) % q == h
assert g < sqrt(q)
assert f < sqrt(q)
priv = PrivateKey()
priv.__dict__.update(f=long(f), g=long(g), q=long(q))
s = open("ciphertext.bin", "rb").read()
print decrypt(s, priv)
The flag: {short_vector_is_sometimes_easy_to_find}
]]>
http://mslc.ctf.su/wp/volgactfquals2015cpkccrypto400writeup/feed/
1