Skip to content

Instantly share code, notes, and snippets.

@SpikeVN
Last active April 11, 2022 14:34
Show Gist options
  • Save SpikeVN/3f3aeeb956419b988023b85d4e507083 to your computer and use it in GitHub Desktop.
Save SpikeVN/3f3aeeb956419b988023b85d4e507083 to your computer and use it in GitHub Desktop.

VioEdu: Backend Reverse Engineer

bởi SpikeBonjour



Mục lục

Thông tin chung về backend

Thông tin Giá trị
Server backend nginx-1.20.1
API endpoint https://vio.edu.vn/graphql

Object formats

Curent Time Object

Mô tả
Lấy thời gian trước khi vào thi để đề phòng thí sinh đổi giờ đồng hồ trên máy nhằm qua mặt lịch thi.

Action
POST

Request

{
    "operationName": "getCurrentTimeServerQuery",
    "variables": {},
    "query": "
        query getCurrentTimeServerQuery {
            getCurrentTimeServer
        }
    "
}

Response

{
  "data": {
    // Server trả về UnixTimestamp
    "getCurrentTimeServer": "<timestamp>"
  }
}

Match Object

Mô tả
Lấy thông tin trận đấu sắp tham gia. Có thêm key của trận để chống tham gia nhầm.

Action
POST

Request

{
    "operationName": "getMatchDetail",
    "variables": {
        "matchKey": "<id>",
        "browserSessionID": "<id>"
    },
    "query": "
        query getMatchDetail($matchKey: String, $browserSessionID: String) {
            getMatchDetail(matchKey: $matchKey, browserSessionID: $browserSessionID) {
                listQuestionId
                matchId
                code
                arenaType
                isMath
                matchType
                timeType
                matchName
                startTime
                battleEnd
                endTime
                numberCorrect
                numberWrong
                totalTimeThink
                listPlayer
                totalQuestion
                listPlayerInformation
                matchResult
                resultSummary
                __typename
        }
    "
}

Response

{
  "data": {
    "getMatchDetail": {
      // Danh sách ID các câu hỏi trong bài
      // Với vòng thi chính thức là 30 câu.
      "listQuestionId": ["<id>", "..."],
      // ID trận đấu
      "matchId": "62500e8f603ac8028b28cdbb",
      "code": 0,
      // Thể loại
      "arenaType": "ALL",
      // Là môn toán
      "isMath": true,
      "matchType": "P",
      "timeType": 2,
      // Tên vòng thi
      "matchName": "<tên có dấu>",
      // Thời gian bắt đầu (UnixTimestamp)
      "startTime": "<timestamp>",
      // Trận đấu đã kết thúc chưa
      "battleEnd": "<boolean>",
      // Thời gian kết thúc (UnixTimestamp)
      "endTime": "<timestamp>",
      // Số câu đúng
      "numberCorrect": 0,
      // Số câu sai
      "numberWrong": 0,
      // Thời gian suy nghĩ tối đa
      "totalTimeThink": 16,
      // Danh sách người đang thi
      "listPlayer": ["<username>"],
      // Tổng câu hỏi
      "totalQuestion": 30,
      // Thông tin người dùng
      "listPlayerInformation": {},
      // Kết quả làm bài
      "matchResult": {
        "<username>": {}
      },
      // Tổng kết
      "resultSummary": null,
      // Thể loại
      "__typename": "ArenaMatchDetailType"
    }
  }
}

Question Object

Mô tả
Client của VioEdu (không hiểu tại sao) lại request từng câu hỏi một lên database mà không lấy hết luôn một thể (chắc là để destribute load). Client sẽ request lên database với những ID câu hỏi có trong trận đấu ở trên.
Hay ở chỗ là response không gửi kèm đáp án (khó exploit).

Action
POST

Request

{
    "operationName": "getQuestionDetail",
    "variables": {
        "questionId": "5fd05434aede1b001c902fc2",
        "matchId": "62500e8f603ac8028b28cdbb",
        "isMath": true
    },
    "query": "
        query getQuestionDetail($questionId: String, $matchId: String, $isMath: Boolean) {
            getQuestionArenaById(questionId: $questionId, matchId: $matchId, isMath: $isMath) {
                _id
                content
                childrenQuestions {
                    _id
                    content
                    q_index
                    q_type
                    isConsider
                    numberRow
                    numberColumn
                    styleAnswer
                    autoConvert
                    answerColumnNo
                    score
                    answers {
                        _id
                        id
                        text
                        subText
                        selected
                        answerType
                        partType
                        position
                        order
                        inputLength
                        __typename
                    }
                    __typename
                }
                q_index
                q_type
                isConsider
                numberRow
                numberColumn
                totalPart
                colorPart
                styleAnswer
                autoConvert
                answerColumnNo
                score
                audio {
                    content
                    explanation
                    __typename
                }
                rightMatching {
                    _id
                    right_answer_id: id
                    textContent
                    __typename
                }
                leftMatching {
                    id
                    textContent
                    __typename
                }
                listSubAnswer {
                    _id
                    textContent
                    __typename
                }
                answers {
                    _id
                    id
                    text
                    subText
                    selected
                    answerType
                    partType
                    position
                    order
                    inputLength
                    listSubAnswer {
                    _id
                    textContent
                    __typename
                    }
                    __typename
                }
                textDropdownAnswers {
                    _id
                    list {
                        value: _id
                        label: text
                        __typename
                    }
                    order
                    __typename
                }
                __typename
            }
        }
    "
}

Response

{
  "data": {
    "getQuestionArenaById": {
      // ID câu hỏi (string)
      "_id": "<qid>",
      // Nội dung câu hỏi (string -> html + mathml)
      "content": "<html>",
      // Câu hỏi con (?)
      "childrenQuestions": null,
      "q_index": null,
      "q_type": "3",
      "isConsider": null,
      "numberRow": null,
      "numberColumn": null,
      "totalPart": null,
      "colorPart": 0,
      "styleAnswer": 1,
      "autoConvert": true,
      "answerColumnNo": 0,
      // Điểm câu hỏi
      "score": "10",
      // Âm thanh cho câu hỏi
      "audio": null,
      "rightMatching": null,
      "leftMatching": null,
      "listSubAnswer": null,
      // Những đáp án để chọn (đây là điền vào ô trống)
      "answers": [
        {
          "_id": "5fd15b70e103bb0b2e2ae938",
          "id": "5fd15b70e103bb0b2e2ae938",
          "text": null,
          "subText": null,
          "selected": null,
          "answerType": null,
          "partType": null,
          "position": null,
          "order": 1,
          // Độ dài đáp án tối đa cho phép
          "inputLength": 1,
          "listSubAnswer": null,
          // Luôn luôn là "AnswerInExam"
          "__typename": "AnswerInExam"
        }
      ],
      // Answer cho bài chọn trắc nghiệm
      "answers": [
        {
          "_id": "5fd0ea2c30fb0508d4d1efea",
          "id": "5fd0ea2c30fb0508d4d1efea",
          // Câu hỏi (string -> html + mathml)
          "text": "<html>",
          "subText": null,
          "selected": null,
          "answerType": null,
          "partType": null,
          "position": null,
          "order": 0,
          // Là null khi là câu hỏi trắc nghiệm
          "inputLength": null,
          "listSubAnswer": [],
          "__typename": "AnswerInExam"
        },
        "..."
      ],
      "textDropdownAnswers": [],
      "__typename": "QuestionInExam"
    }
  }
}

Check Answer Object

Mô tả
Kiểm tra đáp án của người dùng sau khi làm bài.

Action
POST

Request

{
    "operationName": "submitQuestion",
    "variables": {
        // Bê nguyên xi question object từ trên xuống luôn. Tốn băng thông +100
        "question": "<QuestionObject>",
        // ID phòng thi (string)
        "arenaMathId": "<id>",
        // Key phòng thi (string)
        "arenaKey": "<key>",
        // Thời gian suy nghĩ
        "timeThinking": 16
    },
    "query": "
        mutation submitQuestion($question: AnswerInExamResultSubmit, $arenaMathId: String, $arenaKey: String, $timeThinking: Int) {
            checkAnswerQuestionArena(question: $question, arenaMathId: $arenaMathId, arenaKey: $arenaKey, timeThinking: $timeThinking) {
                isCorrect
                numberWrong
                numberCorrect
                timeThinkAll
                __typename
            }
        }
    "
}

Response

{
    "data": {
        "checkAnswerQuestionArena": {
            // Đúng/sai (boolean)
            "isCorrect": false,
            // Số câu đã làm sai
            "numberWrong": "1",
            // Số câu đã làm đúng
            "numberCorrect": "1",
            // Thời gian đã trôi qua
            "timeThinkAll": "71",
            // Object type
            "__typename": "checkAnswerQuestionArenaResult"
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment