Skip to content

Feed to Win

September 28, 2019 / CTF

Writeups ISITDTU

Giải này mình phạm một sai lầm cực kì lớn, mình đã quá chủ quan và quá chày cối vào câu RE cuối cùng để rồi làm cho team tuột mất giải thưởng -> không có tiền để đi đú đởn 🙁

Inter (RE)
Hàm mã giả hàm main trong IDA:

Luồng thực thi của chương trình rất đơn giản. Chương trình tạo 2 tiểu trình A và B chạy tương ứng 2 hàm StartAdress và sub_4017F0. Tại hàm sub_4015C0 sẽ nhận input rồi gửi input đến tiểu trình A thông qua Pipe. Tiểu trình A sẽ kiểm tra input xem có hợp lệ không, nếu hợp lệ thì sẽ gửi "1" về pipe. sub_4051C0 đọc pipe và kiểm tra nếu nhận được 5 lần "1" liên tiếp thì xác nhận 5 input đó chính là flag.

Vậy là vấn đề mấu chốt nằm ở hàm StartAdress để giải ra flag. Tuy nhiên, khi xem mã hàm StartAdress thì lại như thế này:

Cái này là do IDA không decode được opcode, bằng một số biện pháp nghiệp vụ :v , mình nghi ngờ BTC cố ý cho byte rác vào địa chỉ .text:00401331 để chống bị IDA phân tích (thực ra đọc lệnh jz short near ptr loc_401331+1 là biết EIP sẽ nhảy đến .text:00401332 mà IDA lại phân tích tại địa chỉ .text:00401331)

Giờ muốn IDA phân tích đúng thì chỉ cần đưa con trỏ tại địa chỉ .text:00401331 rồi ấn u để undefine

Rồi tại địa chỉ .text:00401331, mình dùng keypath để patch nó lại thành NOP, rồi ấn c ngay tại địa chỉ đó để cho IDA phân tích lại opcode sẽ ra như thế này:

Rồi mình tiếp tục đưa con trỏ về địa chỉ hàm StartAddress, ấn p rồi F5 là có ngay mã giả


Đọc được mã giả rồi thì việc giải rất là nhanh, mình dùng Z3 để giải. Script thì mình không mang theo nên không upload được =))

Unexploitable (Pwn)
Câu này thực sự mình khá tiếc, trong thời gian thi mình chỉ cắm mỗi phần reverse chứ không chơi Pwn, trong khi câu này mình đủ khả năng để làm. Tiếc là sau khi cuộc thi kết thúc mình mới đọc đề Pwn và phát hiện ra Unexploitable 🙁

Vào trang web thì thấy nó cho mình cái source code

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
from flask import Flask, Response, render_template, session, request, jsonify, send_file
import os
from subprocess import run, STDOUT, PIPE, CalledProcessError
from base64 import b64decode, b64encode
 
app = Flask(__name__)
app.secret_key = open('private/secret.txt').read()
 
keys = {
    'key1': open('tmp/key1').read().strip(),
    'key2': open('tmp/key2').read().strip()
}
 
@app.route('/')
def main():
    if session.get('level') == None:
        session['level'] = 0
 
    return render_template('index.html', name="CNV", level = session['level'])
 
@app.route('/source', methods=['GET'])
def resouce():
    file_name = request.args.get('name')
    if '/' in file_name or '..' in file_name or 'private' in file_name:
        return 'Access Denied'
    file_path = 'tmp/' + file_name
    if os.path.isfile(file_path):
        return send_file(file_path)
    else:
        return render_template('index.html', name="CNV", level = session['level'])
 
@app.route('/exploit', methods=['POST'])
def exploit():
    if session.get('level') <= 1:
        return jsonify({'result': 'Only for supper user!'})
 
    try:
        data = request.get_json(force=True)
    except Exception:
        return jsonify({'result': 'Wrong data!'})
 
    try:
        payload = b64decode(data['payload'].encode())
    except TypeError:
        return jsonify({'result': 'Wrong data!'})
 
    try:
        #result = run(['tmp/unexploitable'], input=payload, stdout=PIPE, stderr=STDOUT, timeout=2, check=True).stdout
        result = run(['nc', 'localhost', '9999'], input=payload, stdout=PIPE, stderr=STDOUT, timeout=2, check=True).stdout
    except CalledProcessError:
        return jsonify({'result': 'Error run file!'})
 
    return jsonify({'result': result.decode()})
 
@app.route('/upto', methods=['POST'])
def upto():
    try:
        data = request.get_json(force=True)
    except Exception:
        return jsonify({'result': 'Wrong data!'})
 
    try:
        if session['level'] == 0 and data['type'] == 'key1' and data['key'] == keys['key1']:
            session['level'] = 1
            return jsonify({'result': 'Up to level 1!'})
 
        if session['level'] == 1 and data['type'] == 'key2' and data['key'] == keys['key2']:
            session['level'] = 2
            return jsonify({'result': 'Up to level 2!'})
    except Exception:
        return jsonify({'result': 'Wrong data!'})
 
    return jsonify({'result': 'Wrong key!'})
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Để ý tại hàm resource nó chỉ filter từ khóa private mà không filter key1,key2 => tải được file key1, key2

Sau khi có được key1, key2 rồi mình thử nhập tùm lum thứ vào payload nhưng không có kết quả gì. Lúc đấy mình bắt đầu suy nghĩ lại, liệu có thể bypass phần filter trong resource để tải về flag luôn không? Có thể từ khóa in trong python có bug hay trick gì đấy. Và kết quả là đéo =))

Đọc lại source code thì để ý dòng 51, nó bị comment, lúc này mình mới sực nhớ có lẽ lệnh nc localhost 9999 thực ra là đang giao tiếp với chương trình tmp/unexploitable. Mình thử lấy nội dung file unexploitable giống như lấy key1, key2

Thấy ELF signature là hiểu rồi, mục đích của bài này là exploit file này.

Chương trình gọi sys_read để đọc input và bị BoF ngay chỗ đó. Hướng của mình là dùng ROP để gọi sys_open, sys_read và sys_write để đọc flag và ghi ra stdout.

Làm theo cách này mình gặp phải một số vấn đề, đầu tiên cần phải tìm địa chỉ thích hợp gọi sys_open,sys_read và sys_write. Để ý trong IDA mỗi lần có lệnh syscall thì nó sẽ comment cho mình biết đó là sys nào. Mình sẽ lợi dụng cái này để search text

Kết quả:

Mình ngồi kiểm tra từng cái một và thấy địa chỉ 0x43F130 là phù hợp, tương tự đối với sys_read và sys_write

Vấn đề thứ 2 là sau khi chạy xong sys_open thì fp được lưu trong thanh ghi rax, mình dùng ROPgadget để tìm lệnh có thể chuyển giá trị rax sang rdi để dùng cho sys_read. Và mình tìm thấy lệnh 0x409885 : xchg eax, edi ; ret

Vấn đề thứ 3 là mình cần tìm một nơi mình biết rõ địa chỉ để lưu private/secret.txt và flag dùng cho syscall vì mình không leak được địa chỉ stack. Để ý đoạn lệnh

Mình có thể điều khiển được giá trị của saved RBP -> điều khiển được địa chỉ để lưu buffer. Vậy là mình sẽ lợi dụng BoF để sửa giá trị của saved RBP thành địa chỉ trong section .bss tại vì địa chỉ của bss luôn cố định và retaddr thành 0x4009B6 để sys_read một lần nữa.

Để ý giá trị thanh ghi RSI là địa chỉ trong section .bss và lưu chuỗi "ahihi" mà mình vừa nhập. Và sau lệnh leave thì RSP sẽ trỏ đến bss luôn.

Vậy là xong. Đó là toàn bộ ý tưởng của mình để làm bài này. Mình chỉ làm đến thế chứ chưa viết đầy đủ exploit nên không biết ý tưởng của mình còn bị vướng chỗ nào dẫn đến không khả thi không nữa =)) hy vọng là nó đúng :v

Post navigation

Previous Post:

Câu chuyện về GDB

Next Post:

Writeups MatesCTF Round 3

Recent Posts

  • SVATTT2019
  • flag_check – AceBear CTF 2019
  • Writeups MatesCTF Round 3
  • Writeups ISITDTU
  • Câu chuyện về GDB

Recent Comments

    Archives

    • December 2019
    • April 2019
    • February 2019
    • July 2018
    • June 2018
    • April 2018
    • March 2018
    • February 2018

    Categories

    • CTF
    • Exploit
    • RE
    • Uncategorized

    Meta

    • Log in
    • Entries feed
    • Comments feed
    • WordPress.org
    © 2021 Feed to Win - Powered by SimplyNews