Try   HackMD

ISITDTU FINAL 2021 - RE

Protector

Chall này cùng ý tưởng với một bài mình từng làm ơ SVATTT năm nay.

Đây là wu của mình cho bài đó:

SVATTT 2021 - UNLOCK (re) - HackMD

Reverse các opcode trong hàm VM thì mình có được script sau:

registers = ['TESTTT', 'EAX', 'EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'ESP', 'EIP', 'EBP']

instructions = [
    [0x00, 2, "MOV {}, {}"], # mov eax, ebx
    [0x01, 2, "MOV {}, {}"],    # MOV EAX, 0X20
    [0x02, 2, "XOR {}, {}"],    # xor byte1, byte2
    [0x03, 2, "SUB {}, {}"],
    [4, 2, "ADD {}, {}"],
    [5, 1, "PUSH {}"], # PUSH EAX
    [6, 1, "POP {}"], # POP EAX
    [7, 3, "MOV {}, BYTE PTR [{}+{}]"], # MOV BYTE1, [BYTE2+BYTE3]
    [8, 1, "JMP {}"], # JMP CURR+BYTE1
    [9, 1, "JNZ {}"], # FLAGS 0X40
    [10, 2, "CMP {}, {}"], # CMP AND SET FLAGS 0X40
    [11, 2, "AND {}, {}"],
    [12, 2, "SHL {}, {}"], # SHF EAX, EBX
    [13, 2, "SHR {}, {}"], # SHR EAX, EBX
    [14, 2, "CALL {}"], # CALL EAX 
    [15, 1, "JMP {}"], # JMP EAX
    [16, 3, "MOV {}, DWORD PTR [{}+{}]"], # MOV BYTE1, [BYTE2+BYTE3]
    [17, 1, "JZ {}"]
]

def main():
    with open("out.bin", "rb") as vm:
        opcodes = vm.read()
        vm.close()
    opcodes = opcodes[5:]
    pc = 0
    asm_text = ""

    value = [[] for _ in range(18)]
    bitmath = [[] for _ in range(18)]
    idx = 0
    pre_text = ""
    while pc < 0x1dc3:

        ins = instructions[opcodes[pc]]
        # print (ins)
        # text = hex(pc) + ":\t"
        text = ""
        if ins[1]==1:
            if ins[0]==8 or ins[0]==9 or ins[0]==17:
                text += ins[2].format(hex(pc -1 + int(opcodes[pc+1])))
            else:
                text += ins[2].format(registers[int(opcodes[pc+1])])
            
            # asm_text.append(text)
            pc += 2
        elif ins[1]==2:
            if int(opcodes[pc+1]) > 9 :
                print (int(opcodes[pc+1]) )
                text += "CORRUPTED {} {} {}".format(int(opcodes[pc]), int(opcodes[pc+1]), int(opcodes[pc+2]))

            if ins[0]==1:
                text += ins[2].format(registers[int(opcodes[pc+1])], hex(int(opcodes[pc+2])))
            elif int(opcodes[pc+1]) <= 9:
                text += ins[2].format(registers[int(opcodes[pc+1])], registers[int(opcodes[pc+2])])

            pc += 3
        elif ins[1]==3:
            text += ins[2].format(registers[int(opcodes[pc+1])], registers[int(opcodes[pc+2])], registers[int(opcodes[pc+3])])
            pc += 4

        text += "\n"
        # print (text)
        asm_text += text 
    print (asm_text)

    # print (asm_text)
if __name__=="__main__":
    main()

Đoạn này sẽ dump ra được code ASM

Revere tương tự như wu trên thì mình có được password

Note

Trong quá trình làm challenge này thì bạn teammate có chỉ cho mình một tools để emulate ASM khá là hay, đó là:

Assembly x86 Emulator

YAG Transform

Chall này cho một binary khá là ngắn, và tên hàm đều không bị strip. Nhưng điều đáng buồn là file được compile trên MAC arm64, vậy nên chúng mình hoàn toàn không thể debug được.

Đầu tiên, đi từ flow của hàm main

bzero(enc_pt, 0x1000uLL);
  memset(arr_int_secret, 0, sizeof(arr_int_secret));
  fp_pt = fopen("plaintext.txt", "r");
  if ( !fp_pt )
  {
    stdout_ = "No plaintext.txt for you!";
LABEL_33:
    puts(stdout_);
    return 1;
  }
  fp_pt_ = fp_pt;
  idx0 = 0LL;
  idx1 = 1LL;
  do
  {
    one_char = fgetc(fp_pt_);
    enc_pt[idx0] = (109330345517422uLL >> ((unsigned __int8)idx0 - 47 * (unsigned __int8)(idx1 / 47) + 1)) ^ one_char ^ fib(idx0);
    putchar('.');
    fflush((FILE *)&unk_100000CFE);
    ++idx0;
    ++idx1;
  }
  while ( idx0 != 4096 );

Đoạn này chương trình đọc dữ liệu từ file plaintext.txt và thực hiện encrypt (xor) với key là cothan

Tiếp theo:

fp_secret = fopen("secret.txt", "r");
  if ( !fp_secret )
  {
    stdout_ = "No secret.txt for you!";
    goto LABEL_33;
  }
  fp_secret_ = fp_secret;
  for ( i = 0LL; i != 32; ++i )
    fscanf(fp_secret_, "%d,", &arr_int_secret[i]);
  fclose(fp_secret_);
  puts(
    "There are 2 types of code reverser, the brave reverses everything and fall for traps, the coward only reverses what needed");
  idx2 = 0LL;
  while ( 2 )
  {
    value_int = arr_int_secret[idx2];
    if ( value_int > 29 )
      return 1;
    if ( idx2 <= 4 && (unsigned __int8)arr_int_secret[idx2] % 5u )
      return 2;
    switch ( (int)idx2 )
    {
      case 16:
        if ( value_int == 13 )
          goto LABEL_21;
        result = 3;
        break;
      case 17:
        if ( value_int == 11 )
          goto LABEL_21;
        result = 4;
        break;
      case 18:
        if ( value_int == 25 )
          goto LABEL_21;
        result = 5;
        break;
      case 19:
        if ( value_int == 16 )
          goto LABEL_21;
        result = 6;
        break;
      case 20:
        if ( value_int == 28 )
          goto LABEL_21;
        result = 7;
        break;
      default:
LABEL_21:
        if ( ++idx2 != 32 )

Chương trình đọc 32 byte từ file secret.txt và lưu vào một mảng int

  • Check 1: tất cả 32 bytes đều <29
  • check 2: các bytes từ 0→4 phải chia hết cho 5
  • check3: secret[16] = 13, secret[17] = 11 , secret[18] = 25, secret[19] = 16, secret[20] = 28.

Sau đó:

idx3 = 0LL;
        idx4 = 0uLL;
        do
        {
          idx4 = vaddq_s32(*(int32x4_t *)&arr_int_secret[idx3], idx4);
          idx3 += 4LL;
        }
        while ( idx3 != 16 );
        if ( vaddvq_s32(idx4) != 251 )
          return 1;
        idx5 = 0LL;
        idx6 = 0uLL;
        do
        {
          idx6 = vaddq_s32(*(int32x4_t *)&arr_int_secret[idx5 + 16], idx6);
          idx5 += 4LL;
        }
        while ( idx5 != 16 );
        if ( vaddvq_s32(idx6) != 263 )
          return 1;

Chương trình thực hiện cộng mỗi nửa của secret và so sánh với 1 số là 251 và 263

Cuối cùng:

idx7 = 0LL;
        idx8 = 0;
        do
        {
          transpose(&enc_pt[idx8], arr_int_secret[idx7], arr_int_secret[idx7 + 16]);
          idx8 += arr_int_secret[idx7 + 16] * arr_int_secret[idx7];
          ++idx7;
        }
        while ( idx7 != 16 );
        fp_ct = fopen("out.enc", "w");
        fwrite(enc_pt, 1uLL, 0x1000uLL, fp_ct);
        fclose(fp_ct);
        result = 0;

Chương trình thực hiện transpose để thay đổi dữ liệu của plaintext vừa bị encrypt ở trên sau đó ghi vào file out.enc

Chúng ta có file out.enc và sẽ phải đi ngược lại tìm plaintext.txt

Sau khi đọc đống dữ liệu trên mình đã liên tưởng đến z3, nhưng z3 trả về một số lượng lớn các nghiệm và nó khiến mình rất hoang mang.

Script z3 của mình:

from z3 import *

s = Solver()
secret = [BitVec("secret%s" % i, 8) for i in range(32)]

for i in range(32):
    s.add(secret[i]<29)
    s.add(secret[i]>0)

s.add(secret[16] == 13)
s.add(secret[17] == 11)
s.add(secret[18] == 25)
s.add(secret[19] == 16)
s.add(secret[20] == 28 )

x = 0
for i in range(16):
    x += secret[i]
s.add(x == 251)

x = 0
for i in range(16, 32):
    x += secret[i]
s.add(x == 263)

a = 0
for i in range(16):
    a += secret[i]*secret[i+16]
s.add(a==4096)

idx2 = 0
while (True):
    if s.check() == sat:
        m = s.model()
        
        answer = []
        for i in secret:
            answer.append(m[i].as_long())
        
        # print("ANSWER {}: {}".format(idx2, answer))
        print (answer)
        # print (idx2)
        idx2 += 1
        # secret_value = answer[6]
        # s.add(secret[6] != secret_value)
    else:
        print (unsat)
        break

Cuối cùng chúng mình đã quyết định bruteforce secret

Sau khi reverse hàm transpose mình nhận thấy hàm thực hiện chức năng giống transpose matrix với size là 2 tham số được truyền vào ngay sau (enc, row, column)

Script để brutefoce của mình:

import numpy as np

fib = [0 for i in range(4096)]
fib[0] = 0
fib[1] = 1
for i in range(2, 4096):
    fib[i] = (fib[i-1] + fib[i-2]) & 0xff

with open("./out.enc", "rb") as fp:
    enc_data = list(fp.read())
    fp.close()

def reverse(begin, row, column):
    arr = enc_data[begin:begin+row*column]
    # print (arr)

    B = np.array(arr).reshape(row, column)
    # print (B)
    C = np.transpose(B)
    # print (C)

    out = np.array(C).reshape(1, row*column) # (row, column)
    # print (out)

    #### part2 
    # print ("-----------------------------------------------")
    idx = begin
    out2 = []
    const = 0x636F7468616E
    while (idx < begin+row*column):
        tmp_idx = (idx%47+1)
        # if tmp_idx == 47:
        #     tmp1 = const >> 
        # else:
        tmp1 = const >> tmp_idx
        # print (idx%47+1)
        tmp = tmp1^fib[idx]^out[0][idx-begin]
        out2.append(tmp&0xff)
        idx += 1

    # print (out2)
    print (bytearray(out2))

    with open("out_test.bin", "wb") as ot:
        ot.write(bytearray(out2))
        ot.close()
'''
--> brute 4 pairs of secretkeys
reverse(0, 5, 13)
reverse(5*13, 20, 11)
curr = 5*13+20*11
reverse(curr, 25, 25)
#################################
curr += 25*25
reverse(curr, 20, 16)
#################################
curr += 20*16
reverse(curr, 25, 28)

# 
'''

test[6], test[6+16] = 19, 27
test[7], test[7+16] = 24, 7
test[8], test[8+16] = 11, 28
test[9], test[9+16] = 11, 20
test[10], test[10+16] = 10, 20

for ii in range(29):
    test[11] = ii
    for j in range(29):
        test[11+16] = j
        curr = 0
        print("********************** TRY TEST {} ******************".format(idx_test))
        for i in range(16):
            print("KEY: {} {}".format(test[i], test[i+16]))
            try:
                reverse(curr, test[i], test[i+16])
                curr += test[i]*test[i+16]
            except:
                print("FAILED: " + str(idx_test))
                break
        print("********************** END TEST {} ******************".format(idx_test))
        idx_test += 1

    #   ISITDTU{The_algorithm_is_inplace_matrix_transpose_do_you_know_that?}

Chúng mình nhận ra bài này là một bài trên wiki:

The Toyota Way | Wikiwand

Vừa bruteforce vừa kết hợp với wiki, với chỉ cần 8 cặp key là mình có thể lấy đc flag nên ko tính là lâu lắm :v: