GeistHaus
log in · sign up

c0ffee.me

Part of c0ffee.me

stories primary
Viewing the contents of a zip as a tree

I was building some jar’s for a Jenkins shared pipeline and trying to validate what files/folders were included in the result. Normally I use the tree command to inspect directories like this, as it makes more visual sense to me. However to view the contents of a zip we have to do something like unzip -l filename.zip which gives us a pretty verbose and difficult to understand result (at least to me).

Solution

Add a the following function to my ~/.zshrc

function tree() {
	arr=( "${@}" )
	if ( file $arr[-1] | grep -q 'Zip archive' ) ; then
		tar tf $arr[-1] | tree ${arr[@]:0:-1} --fromfile .
	else
		command tree $@
	fi
}

Now we can tree somefolder just as well as tree somejar.jar, or any other Zip archive! For example:

» tree ~/.m2/repository/org/testng/testng/6.13.1/testng-6.13.1.jar
.
├─ com
│  └─ beust
│    └─ testng
│      └─ TestNG.class
├─ META-INF
│  ├─ services
│  │  └─ org.testng.xml.ISuiteParser
│  └─ MANIFEST.MF
├─ org
│  └─ testng
│    ├─ annotations
│    │  ├─ AfterClass.class
│    │  ├─ AfterGroups.class
│    │  ├─ AfterMethod.class
│    │  ├─ AfterSuite.class
│    │  ├─ AfterTest.class
│    │  ├─ BeforeClass.class
/2023/07/07/zip-tree.html
Adding a new discord bot

When writing a discord bot, even if only for your own server, you need to use oauth to validate it. I wrote the following script to make it easy on myself, since i’m more than likely to do it again and forget how in the future. Make sure you have bottle, requests and discordpy (the bot framework i was writing in) runt hen simply browse to http://localhost:8080

Note that this requests Permissions(8) which is Administrator and only the scopes bot. You can change that at will - infact I suggest you don’t grant Admin.

import os, requests, string, random
from bottle import request, route, run, abort, redirect
from discord.utils import oauth_url
from discord import Permissions

state = ''.join(random.choice(string.ascii_letters) for i in range(10))


@route('/')
def index():
  redirect(oauth_url(os.environ['CLIENT_ID'], permissions=Permissions(8), redirect_uri='http://localhost:8080/oauth', scopes=['bot'], state=state))

@route('/oauth')
def exchange_code():
  code = request.query.code
  incstate = request.query.state
  if incstate != state:
    abort(401, "State mismatch somehow, are you being hacked...?")
  data = {
    'client_id': os.environ['CLIENT_ID'],
    'client_secret': os.environ['CLIENT_SECRET'],
    'grant_type': 'authorization_code',
    'code': code,
    'redirect_uri': 'http://localhost:8080/oauth'
  }
  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
  r = requests.post('https://discord.com/api/oauth2/token', data=data, headers=headers)
  if 400 > r.status_code >= 200:
    rdata = r.json()
    access_token = rdata['access_token']
    return "Successfully added bot"
  print(r.text)
  abort(401, "Failed to use code to auth")


if __name__ == '__main__':
  if os.path.isfile('.env'):
    with open('.env') as f:
      for line in f:
        k, v = line.strip().split('=', 1)
        os.environ[k] = v
  assert 'CLIENT_ID' in os.environ
  assert 'CLIENT_SECRET' in os.environ
  assert 'BOT_TOKEN' in os.environ

  print('Open http://localhost:8080 in your browser')
  run(host='localhost', port=8080, debug=True)


/2022/10/30/discord-oauth.html
Adding TLS to edgeos via acme.sh

Basic guidelines to setup acme.sh with your dnsapi

$ ./acme.sh --install --home /config/auth/.acme.sh --config-home 
# see https://github.com/acmesh-official/acme.sh/wiki/dnsapi
$ ./acme.sh --issue --dns ... your dns here ... -d router.your.domain
$ sudo chown root.root /config/auth/.acme.sh/router.your.domain/*
$ cat /config/auth/.acme.sh/router.your.domain/router.your.domain.key >> /config/auth/.acme.sh/routeryour.domain/fullchain.cer
$ configure
$ set service gui cert-file /config/auth/.acme.sh/router.your.domain/fullchain.cer
$ commit
$ save

Got errors? See if the service came up via service lighttpd status

Want to see real error messages? /usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf

/2022/10/15/home-edge-tls.html
Java URL Pattern Matching Gotchas

Many security features in Java rely on endpoint pattern matching which allow for URL pattern matching bypasses if not careful. Additionally Spring MVC and Spring Security together introduces are a few gotcha’s during implementation.

Security Constraint Matching

The most basic form of authentication within Java is using the <security-constraint/> tag. The following is an example constraint restricting access to /basic endpoint using Basic auth.

<!-- Basic Security -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>baisc auth
restriction</web-resource-name>
        <url-pattern>/basic</url-pattern>
    </web-resource-collection>
    …
</security-constraint>

All official Java documentation uses the url-pattern /*, though details for <url-pattern/> can be found in section 12.2 of the 3.0 servlet specification. The following details mapping test-cases which have standard Java servlets mapped to /basic

Servlet Map <url-pattern/> Request Response Code /basic /basic* GET /basic 200 /basic /basic/ GET /basic 200 /basic /basic/* GET /basic 401 /basic /basic GET /basic 401

Wild cards do not function as expected, and only by leaving out extensions for a literal matching or using /* can servlet patterns be correctly matched.

Security Constraints with Spring MVC

However if, for example, using the <security-restraint/> function to protect Spring MVC controllers the following is observed:

MVC Map <url-pattern/> Request Response Code /mvcpoint /mvcpoint* GET /mvcpoint 200 /mvcpoint /mvcpoint/ GET /mvcpoint 200 /mvcpoint /mvcpoint/* GET /mvcpoint. 200 /mvcpoint /mvcpoint GET /mvcpoint/ 200 /mvcpoint /mvcpoint*/* GET /mvcpoint 200

The lesson here is never use standard <security-constraint/> methods when attempting to restrict endpoints which are not servlets. We will touch on the use of the . below.

Spring Security Pattern Matching

Security interceptors used by Spring Security use ANT pattern matching. This type of matching is different from Regex. See the ANT documentation on patterns for specifics.

The following is an example of a Spring Security URL pattern constraint:

<http>
    <intercept-url pattern="/welcome*" access="ROLE_USER" />
    <http-basic />
</http>

The following is a test table showing various patterns, and their bypass:

MVC Map Request Response Code /mvcpoint /mvcpoint* GET /mvcpoint/ 200 /mvcpoint /mvcpoint/ GET /mvcpoint 200 /mvcpoint /mvcpoint/* GET /mvcpoint 200 /mvcpoint /mvcpoint** GET /mvcpoint/ 200 /mvcpoint /mvcpoint/** GET /mvcpoint. 200 /mvcpoint /mvcpoint GET /mvcpoint. 200 /mvcpoint /mvcpoint/ GET /mvcpoint 200 /mvcpoint /mvcpoint*/ GET /mvcpoint 200 /mvcpoint /mvcpoint*/** GET /mvcpoint. 401

The table shows that only a single spring-security pattern (*/**) is able to secure against unauthorized access. To discover why, we must dig deeper.

Spring MVC with Spring Security

The problem arises in spring-mvc and its handling of extensions. We are able to supply an incomplete extension (note the trailing . on the requests) to spring-security which results in the bypass of the rule. When the incomplete extension gets to spring-mvc however, the incomplete extension is treated as erroneous and automatically returns the original mapping.

private String getMatchingPattern(String pattern, String lookupPath) {
    if (pattern.equals(lookupPath)) {
        return pattern;
    }
    if (this.useSuffixPatternMatch) {
        if (useSmartSuffixPatternMatch(pattern, lookupPath)) {
            for (String extension : this.fileExtensions) {
                if (this.pathMatcher.match(pattern + extension, lookupPath)) {
                    return pattern + extension;
                }
            }
        }
        else {
            boolean hasSuffix = pattern.indexOf('.') != -1;
            if (!hasSuffix &amp;&amp; this.pathMatcher.match(pattern + ".*",
lookupPath)) {
                return pattern + ".*";
            }
        }
    }
    if (this.pathMatcher.match(pattern, lookupPath)) {
        return pattern;
    }
    boolean endsWithSlash = pattern.endsWith("/");
    if (this.useTrailingSlashMatch) {
        if (!endsWithSlash &amp;&amp; this.pathMatcher.match(pattern + "/",
lookupPath)) {
            return pattern +"/";
        }
    }
    return null;
}

private boolean useSmartSuffixPatternMatch(String pattern, String lookupPath) {
    return (!this.fileExtensions.isEmpty() &amp;&amp; lookupPath.indexOf('.')
!= -1) ;
}

org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.java starting at line 223

The code, upon being passed to spring mvc as such

getMatchingPattern("/basic2", "/basic2.")

Will end up in the following block because of useSmartSuffixPatternMatch evaluating to false.

boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
    return pattern + ".*";
}

This results in the return of “/basic2.” due to the pathMatcher automatically appending . to the pattern. In the end this function will find the correct controller mapping in spite of the added period.

Summary
  • Only protect servlets with the <security-constraint/> element
  • Always use /* when defining <security-constraint/>  patterns
  • Always use */** when defining <intercept-url/> patterns
/security/2014/04/02/java-url-pattern-matching-gotchas.html
Compiling iOS Libraries using Theos

If you want to compile a shared library for iOS, particularly for Mobile Substrate, here are some easy enough steps to do it all via CLI.

Setup

You need Theos installed, normally into /opt/theos. Follow the getting started guide

Then create a new Theo project. The following is named SampleCrack

user@myhost> $THEOS/bin/nic.pl
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/application
[2.] iphone/library
[3.] iphone/preference_bundle
[4.] iphone/tool
[5.] iphone/tweak
Choose a Template (required): 2
Project Name (required): SampleCrack
Package Name [com.yourcompany.samplecrack]:
Author/Maintainer Name [c0ffee]: 
Instantiating iphone/library in samplecrack/...
Done.

Then look about

user@myhost> cd samplecrack
user@myhost> ls
./working/samplecrack
Makefile       SampleCrack.mm control        theos

We are going to be using captain hook. Check it out

user@myhost> git clone
git://github.com/rpetrich/CaptainHook.git
Write

Then let’s write the code, make sure you mod it to your liking, sadly there are no docs for CaptainHook.

user@myhost> cat > SampleCrack.h
#import <Foundation/Foundation.h>

@interface SampleCrack : NSObject
@end
user@myhost> cat > SampleCrack.mm
#import "SampleCrack.h"
#import "Foundation/Foundation.h"
#import "CaptainHook/CaptainHook.h"
#include "notify.h"

@implementation SampleCrack
-(id)init
{
    if ((self = [super init])){} return self;
}
@end

@class SampleAppViewController;
CHDeclareClass(SampleAppViewController);
CHOptimizedMethod(0, self, _Bool, SampleAppViewController, isDeviceRooted)
{
    NSLog(@"####### isJailBroken hooked"); // Logging saves lives
    return true;
}

CHConstructor {
    @autoreleasepool {
        CHLoadLateClass(SampleAppViewController);
        CHHook(0, SampleAppViewController, isDeviceRooted); // register hook
    }
}
Build

Then we compile:

user@myhost> make

If you get an error that looks anything like the following:

./working/samplecrack/theos/include/IOSurface/IOSurface.h:20:10:
fatal error: 'IOSurface/IOSurfaceAPI.h' file not found
#include <IOSurface/IOSurfaceAPI.h>

Then try including the IOSurfaceAPI.h in, I had to do this on lion.

> cp
/System/Library/Frameworks/IOSurface.framework/Headers/IOSurfaceAPI.h
./theos/include/IOSurface/

You will probably need to comment out the following lines also:

    /* This call lets you get an xpcobject_t that holds
a reference to the IOSurface.
    Note: Any live XPC objects created from an IOSurfaceRef implicity increase
the IOSurface's global use
    count by one until the object is destroyed. */
    // xpc_object_t IOSurfaceCreateXPCObject(IOSurfaceRef aSurface)
    // IOSFC_AVAILABLE_STARTING(_MAC_10_7, __IPHONE_NA);

    /* This call lets you take an xpcobject_t created via IOSurfaceCreatePort()
and recreate an IOSurfaceRef from it. */
    // IOSurfaceRef IOSurfaceLookupFromXPCObject(xpc_object_t xobj)
    // IOSFC_AVAILABLE_STARTING(_MAC_10_7, __IPHONE_NA);

See this stack overflow post if you want more detail.

You are also going to need a copy of ldid. If you have ports, try there. Brew doesn’t seem to hold a copy (They gave up on it because it fails with clang? Use llvm g++). If those fail check try making it yourself:

git clone git://git.saurik.com/ldid.git
cd ldid
git submodule update --init
./make.sh
cp -f ./ldid $THEOS/bin/ldid

Make sure you drop it into $THEOS/bin/ldid

scp ./obj/SampleCrack.dylib
root@iphone:/Library/MobileSubstrate/
ssh root@iphone
root@iphone's password: 
iphone:~ root# 
ldid -S SampleCrack.ldid

Now you’ve got the dependencies, make it

user@myhost> export SDKVERSION=7.0
user@myhost> make

And you’ve got yourself a nice library

> file obj/SampleCrack.dylib
~/Documents/Customer/Documents/Elavon/working/samplecrack
obj/SampleCrack.dylib: Mach-O universal binary with 2 architectures: [arm_v7:
Mach-O arm_v7 dynamically linked shared library] [arm subarchitecture=11:
Mach-O arm subarchitecture=11 dynamically linked shared library]

 

/security/2013/10/25/compiling-ios-libraries-using-theos.html
Method patching on iOS applications
Summary

These are the steps needed to crack (method patch the jailbreak function) an iOS application to work on a rooted iOS device. This example uses “Sample.app” from within OSX Lion. Mileage may vary.

Requirements

iOS Physical device rooted with cydia and some basic tools:

  • syslogd - See all console output in /var/log/
  • openssh
  • adv-cmds - Tools like ps, kill, etc
  • GNU debugger - (If GDB is working unexpectedly, install from radare.org)
  • Darwin CC Tools - otool and such

Get the application and install it. This guide is assuming an encrypted IPA (compiled for ARM) distribution.

Decrypting

In the cases in which the binary is encrypted, you must decrypt. The easiest way to do this is to find a program to do it for you (Google this if you want to skip this step). The surefire (manual) way  to do this is to execute the binary breaking at the end of the decoding stub. This will leave the entire un-ecrypted binary in memory where you can then dump it to disk.

Locate application binary within the application folder on the device. e.g.:

/private/var/mobile/Applications/BC2DA09D-7189-44E8-B190-2EE03BAAAAA8/SampleApp.app/SampleApp

Then check application for encryption:

root# otool -l SampleApp | grep -B5 cryptid
Load command 11
 cmd LC_ENCRYPTION_INFO
 cmdsize 20
 cryptoff 4096
 cryptsize 94208
 cryptid 1
--

Given cryptid 1 (0 == Not Encrypted). Keep note of the cryptsize, we will use this value later.

Verify application is not FAT (That is does not contain multiple versions):

root# otool -f SampleApp

If the application contains multiple versions, you must use lipo to extract the correct (armv7) version and continue.

Given a single encrypted binary:

root# gdb --quiet -e ./SampleApp
...
(gdb) set sharedlibrary load-rules ".*" ".*" none
(gdb) set inferior-auto-start-dyld off
(gdb) set sharedlibrary preload-libraries off
(gdb) rb doModInitFunctions
Breakpoint 1 at 0x2fe0cece
&lt;function, no debug info&gt;
__dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE;
(gdb) r
Starting program:
/private/var/mobile/Applications/BC2DA09D-7189-44E8-B190-2EE03BAAAAA8/SampleApp.app/SampleApp.app/SampleApp
Breakpoint 1, 0x2fe0cece in
__dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE
()

Dump the binary from memory to disk:

dump binary memory /var/root/dump 0x2000 0x18000

See python -c 'print(hex(4096+94208))' from crytpid analysis for the end limit, which is 0x18000 bytes in the example. You will have to substitute your own value in.

Then pull the binary off phone and use classdump

scp root@iphone:/var/root/dump ./SampleApp
./class-dump SampleApp > SampleAppDump

Examine dump for a root checking function (probably returns BOOL or _Bool) with some grep-fu. You will most likely find multiple functions. If the application is more complicated, cycript may help you find it.

Setup

You can either follow my other instructions on compiling iOS libraries using Theos (Reccomended!), or do the following:

Install MobileSubstrate on the iOS device. You can check if it’s installed by looking for /Library/MobileSubstrate.

Writing

Checkout CaptainHook into the project into the project. Mine is called NoCheck saved in ~/Documents/workspace

user@box> cd ~/Documents/workspace/NoCheck/NoCheck
user@box> git clone git://github.com/rpetrich/CaptainHook.git

Write the code itself using the lib. This sample code was referenced from the MDSec iOS doc. Read it if you haven’t.

#import "NoCheck.h"
#import "Foundation/Foundation.h"
#import "CaptainHook/CaptainHook.h"
#include "notify.h"

@implementation NoCheck
-(id)init
{
    if ((self = [super init])){} return self;
}
@end

@class SampleAppViewController;
CHDeclareClass(SampleAppViewController);
CHOptimizedMethod(0, self, _Bool, SampleAppViewController, isDeviceRooted)
{
    NSLog(@"####### isJailBroken hooked"); // Logging saves lives
    return true;
}

CHConstructor {
    @autoreleasepool {
        CHLoadLateClass(SampleAppViewController);
        CHHook(0, SampleAppViewController, isDeviceRooted); // register hook
    }
}

 

All is needed now compile to dylib by running the application and copying it over. Press the run button to compile. Find it by looking at the left hand bar, mine was located at /Users/user/Library/Developer/Xcode/DerivedData/NoCheck-abheoijxmwkxefbirkgyhsismoxg/Build/Products/Debug-iphoneos

Copy the output file to /Library/MobileSubstrate/DynamicLibraries/

user@box> cd
/Users/user/Library/Developer/Xcode/DerivedData/NoCheck-abheoijxmwkxefbirkgyhsismoxg/Build/Products/Debug-iphoneos
user@box> scp ./NoCheck.dylib
root@iphone:/Library/MobileSubstrate/DynamicLibraries/

Run application and be happy. If you’re not sure it worked tail your syslog output to see if the module is loading (it will do so often) and the NSLog is output.

/security/2013/04/19/method-patching-iOS-applications.html
Pretty Print XML

Put this python file in your path to get the following:

pretty printed xml

#!/usr/bin/env python

"""
Command-line tool to validate and pretty-print XML.

Based on `pjson` but without the crap.

Usage::

    $ echo '&lt;bunk atr="hello"&gt;world&lt;/bunk&gt;' | pxml
  &lt;?xml version="1.0" ?&gt;
  &lt;bunk atr="hello"&gt;
    world
  &lt;/bunk&gt;

Original Author: Igor Guerrero &lt;igfgt1@gmail.com&gt;, 2012
Contributor: Matthew Gill, 2012
"""

import xml.dom.minidom
import sys

from pygments import highlight
from pygments.formatters import TerminalFormatter
from pygments.lexers import XmlLexer

def format_xml_code(code):
    """
    Parses XML and formats it
    """
    x = xml.dom.minidom.parseString(code)
    return x.toprettyxml()

def color_yo_shit(code):
    """
    Calls pygments.highlight to color yo shit
    """
    return highlight(code, XmlLexer(), TerminalFormatter())

if __name__ == '__main__':
    code = format_xml_code(sys.stdin.read())
    print color_yo_shit(code)

The original can be found here (https://github.com/igorgue/pjson)

/code/2012/12/11/pretty-print-xml.html
Derbycon 2012 CTF - Crypto Challenge

This was one of my favorite challenges at this year’s CTF. Thanks to the guys who put it on, it was a load of fun and I appreciate all the hard work I know that goes into this sort of thing.

The Challenge

Posted on one of the websites was the file fu2.zip contained the following files:

zoidberg% unzip fu2.zip -d ./fu2
zoidberg% cd fu2
zoidberg% file ./*
./BouncyCastle.Crypto.dll: PE32 executable for MS Windows (DLL) (console) Intel 80386 32-bit Mono/.Net assembly
./FileEncryptor.exe:       PE32 executable for MS Windows (console) Intel 80386 32-bit Mono/.Net assembly
./plain1_encrypted:        data
./plain2:                  ASCII text, with no line terminators
./plain2_encrypted:        data
zoidberg% ls -al ./plain*
-rw-r--r--  1 mgill  staff  128 Sep 24 13:14 ./plain1_encrypted
-rw-r--r--  1 mgill  staff  112 Sep 17 06:18 ./plain2
-rw-r--r--  1 mgill  staff  128 Sep 24 13:14 ./plain2_encrypted

With the following contents of plain2:

zoidberg% cat plain2
KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57KC57

There are a few clues here if you just go off file names and file sizes, but let’s take the easy route and simply decompile the .NET binary. A quick google search on the md5sum of the BouncyCastle lib tells us it’s unlikely it has been modified so we only want to see what FileEncryptor.exe does. My favorite .net decompiler is dotPeek, and lucky us .net allows for some awesome code generation.

The view in dotpeek

After looking through the code we get a better idea on what is going on. We pass in a password, a plain text file, and a output file. The program then encrypts the plain text file with the password, to produce the output. Here are the interesting functions:

public static void EncryptFile(string password, string inFile, string outFile)
{
    string salt = "s@1tValue";
    string s = "1234567890123456";
    int keySize = 128;
    byte[] plain = Aes.readBytesFromFile(inFile);
    byte[] bytes1 = Encoding.ASCII.GetBytes(s);
    byte[] key = Aes.GenerateKey(password, salt, keySize);
    byte[] bytes2 = Aes.Encrypt(plain, key, bytes1);
    Aes.writeBytesToFile(outFile, bytes2);
}
public static byte[] Encrypt(byte[] plain, byte[] key, byte[] iv)
{
    PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher((IBlockCipher)new OfbBlockCipher((IBlockCipher)new AesEngine(), 128));
    ICipherParameters parameters = (ICipherParameters)new ParametersWithIV((ICipherParameters)new KeyParameter(key), iv);
    cipher.Init(true, parameters);
    return Aes.cipherData(cipher, plain);
}

Now we have all the key pieces of information. Our plain2 file, which is 112 bytes of repeating characters (‘KC57’*28) produces plain2_encrypted, at 128 bytes. We have plain1_encrypted, but no plain1. A brute force attack here seems a bit inelegant not to mention being the ballers we are we don’t have time for that shit.

Notice that the password is seeded with a salt and the IV is static. Generally speaking the IV should be a noonce value to stop different types of attacks against block encryptions. Static IV’s are bad. The Encrypt() function above shows that we’re using the OfbBlockCipher or Output-FeedBack. OFB is a stream cipher which generally means the keystream is xor’d with the plaintext value to produce the output. Really what we’re interested in is attacks against OFB, especially when the programmer makes the mistake of using a static IV.

All the stars align. We have a static IV making the keystreams identical for each encryption and a plaintext and encrypted value pair. This means we can decrypt the plain1_encrypted file without ever needing to know the key!  Simply compute the value of the keystream by xoring the plain2 against the encrypted plain2_encrypted, and apply that keystream to the plain1_encrypted. Voila! Here is a bit of python code to get the job done

import itertools

#take in the plain1 encrypted file and convert to ints
p1e = open('plain1_encrypted','r').read()
p1e_ord = map(ord, p1e)

#take in the plain2 encrypted file and convert to ints
p2e = open('plain2_encrypted','r').read()
p2e_ord = map(ord, p2e)

#take in the plain2 file and convert to ints
p2 = open('plain2','r').read()
p2_ord = map(ord, p2)

keystream = []
for x,y in itertools.izip_longest(p2_ord, p2e_ord, fillvalue=0):
	keystream.append(x^y)

final = []
for x,y in zip(p1e_ord, keystream):
	final.append(chr(x^y))

print ''.join(final)

And if you run it…

zoidberg% python decrypt.py      
KC57KC57KC57KC57FLAG=DecryptTheFlagToGetTheKeyLolKC57KC57KC?KC57KC57KC57KC57KC57

Success, the flag has been disclosed! While this is a workable solution, I’m not exactly sure how the bytes are properly padded (instead of using 0 in my case) nor am I sure why there are unprintable characters in the decoded stream. You can see them by directly printing the repr of the finalResult object. But it works and the flag is decoded. If you have any input please throw it my way, it would be nice to know if there is any way to improve on it all.

Thanks!

tl;dr static iv’s are bad and break your crypto

/security/2012/10/17/derbycon.html