[Write-Up] Christmas CTF 2020 - lock

출제 의도

baby_crackme를 하나 간단하게 내고 싶었는데 arm으로 내면 어떨까? 싶어서 낸 문제입니다.

Solution

dump로 파일이 하나 주어지며 vscode나 메모장 등으로 열면 아래와 같은 dump 코드가 나옵니다.

aarch64에서 동작하는 바이너리이며 Input 값을 통해 어떠한 연산을 수행하는 것으로 볼 수 있습니다.

0000000000000c50 <main>:
 c50:   a9be7bfd    stp x29, x30, [sp, #-32]!
 c54:   910003fd    mov x29, sp
 c58:   d2800021    mov x1, #0x1                    // #1
 c5c:   d2800260    mov x0, #0x13                   // #19
 c60:   97fffee0    bl  7e0 <calloc@plt>
 c64:   f9000be0    str x0, [sp, #16]
 c68:   97ffff94    bl  ab8 <sub_ab8>
 c6c:   97ffff51    bl  9b0 <sub_9b0>
 c70:   f9000fe0    str x0, [sp, #24]
 c74:   97ffff3e    bl  96c <sub_96c>
 c78:   f9400be1    ldr x1, [sp, #16]
 c7c:   90000000    adrp    x0, 0 <_init-0x760>
 c80:   9138a000    add x0, x0, #0xe28              // #0xe28 '%s'
 c84:   97fffeeb    bl  830 <__isoc99_scanf@plt>
 c88:   f9400fe1    ldr x1, [sp, #24]
 c8c:   f9400be0    ldr x0, [sp, #16]
 c90:   97ffffa8    bl  b30 <sub_b30>
 c94:   f9400be0    ldr x0, [sp, #16]
 c98:   97fffee2    bl  820 <free@plt>
 c9c:   52800000    mov w0, #0x0                    // #0
 ca0:   a8c27bfd    ldp x29, x30, [sp], #32
 ca4:   d65f03c0    ret

main 함수를 보면 sp + #16에 calloc(0x13,1)을 해주고 sub_ab8함수가 호출된 후 나온 값을 인자로 sub_9b0 함수가 실행된 후 sp + #24에 저장합니다. 그 후 sub_96c 함수를 호출하고 값을 받아서sp+#16에 넣어주고 sub_9b0(sub_ab8()) 한 값과 input 값을 인자로 sub_b30 함수를 호출하고 프로그램이 종료됩니다. 해당 함수를 c 코드로 간단하게 나타내면 아래와 같습니다.

// main
int main()
{
    char *var1 = calloc(0x13, 1);
    int var2 = sub_9b0(sub_ab8());

    sub_96c();
    scanf("%s", var1);

    sub_b30(var1, var2);
    free(var1);

    return 0;
}

sub_ab8sub_9b0 함수를 살펴봅시다.

value0  DCB 0x96, 0x19, 0x7, 0x11, 0x99, 0x19, 0x2, 0x11
value1  DCB 0xF7, 0x7B, 0x64, 0x75, 0xFC, 0x7F, 0x65, 0x79, 0xFF,
0x73, 0x6C, 0x7D, 0xF4, 0x77, 0x6D, 0x61, 0xE7, 0x6B, 0x74, 0x65,
0xEC, 0x6F, 0x75, 0x69, 0xEF, 0x63, 0x46, 0x53, 0xDA, 0x5D, 0x47,
0x57, 0xD1, 0x51, 0x4E, 0x5B, 0xD2, 0x55, 0x4F, 0x5F, 0xD9, 0x49, 0x56,
0x43, 0xCA, 0x4D, 0x57, 0x47, 0xC1, 0x41, 0x5E, 0x4B, 0xA9, 0x28, 0x30,
0x22, 0xA2, 0x2C, 0x31, 0x26, 0xA1, 0x20

0000000000000ab8 <sub_ab8>:
 ab8:   a9be7bfd    stp x29, x30, [sp, #-32]!
 abc:   910003fd    mov x29, sp
 ac0:   52800103    mov w3, #0x8                    // #8
 ac4:   90000000    adrp    x0, value0@page
 ac8:   9136e002    add x2, x0, value0@pageoff
 acc:   528007c1    mov w1, #0x3e                   // #62
 ad0:   90000000    adrp    x0, value1@page
 ad4:   91372000    add x0, x0, value1@pageoff
 ad8:   97ffffc9    bl  9fc <sub_9fc>
 adc:   f9000fe0    str x0, [sp, #24]
 ae0:   f9400fe0    ldr x0, [sp, #24]
 ae4:   a8c27bfd    ldp x29, x30, [sp], #32
 ae8:   d65f03c0    ret

aarch64의 calling convention은 x0, x1, x2, x3 이므로 인자에 value1, 0x3e, value0, 0x8을 넣어 sub_9fc를 호출합니다. 호출하고 난 후 연산된 문자열을 return 해줍니다.

00000000000009b0 <sub_9b0>:
 9b0:   a9bd7bfd    stp x29, x30, [sp, #-48]!
 9b4:   910003fd    mov x29, sp
 9b8:   f9000fe0    str x0, [sp, #24]
 9bc:   f9400fe0    ldr x0, [sp, #24]
 9c0:   97ffff78    bl  7a0 <strlen@plt>
 9c4:   91000400    add x0, x0, #0x1
 9c8:   97ffff7e    bl  7c0 <malloc@plt>
 9cc:   f90017e0    str x0, [sp, #40]
 9d0:   f94017e0    ldr x0, [sp, #40]
 9d4:   f100001f    cmp x0, #0x0
 9d8:   54000061    b.ne    9e4 <sub_9b0+0x34>  // b.any
 9dc:   d2800000    mov x0, #0x0                // #0
 9e0:   14000005    b   9f4 <sub_9b0+0x44>
 9e4:   f9400fe1    ldr x1, [sp, #24]
 9e8:   f94017e0    ldr x0, [sp, #40]
 9ec:   97ffff95    bl  840 <strcpy@plt>
 9f0:   f94017e0    ldr x0, [sp, #40]
 9f4:   a8c37bfd    ldp x29, x30, [sp], #48
 9f8:   d65f03c0    ret

sub_ab8에서 반환한 문자열을 받아서 길이를 계산한 후 malloc 해줍니다. 할당한 주소가 0이라면 0을 반환해주고, 아니면 strcpy를 통해 sub_ab8에서 온 문자열을 복사하여 return 해줍니다.

이제 sub_9fc 함수를 분석을 해봅시다.

00000000000009fc <sub_9fc>:
 9fc:   a9bc7bfd    stp x29, x30, [sp, #-64]!
 a00:   910003fd    mov x29, sp
 a04:   f90017e0    str x0, [sp, #40]
 a08:   b90027e1    str w1, [sp, #36]
 a0c:   f9000fe2    str x2, [sp, #24]
 a10:   b90023e3    str w3, [sp, #32]
 a14:   b94027e0    ldr w0, [sp, #36]
 a18:   11000400    add w0, w0, #0x1
 a1c:   93407c00    sxtw    x0, w0
 a20:   97ffff68    bl  7c0 <malloc@plt>
 a24:   f9001fe0    str x0, [sp, #56]
 a28:   b98027e0    ldrsw   x0, [sp, #36]
 a2c:   f9401fe1    ldr x1, [sp, #56]
 a30:   8b000020    add x0, x1, x0
 a34:   3900001f    strb    wzr, [x0]
 a38:   b90037ff    str wzr, [sp, #52]
 a3c:   14000018    b   a9c <sub_9fc+0xa0>
 a40:   b98037e0    ldrsw   x0, [sp, #52]
 a44:   f94017e1    ldr x1, [sp, #40]
 a48:   8b000020    add x0, x1, x0
 a4c:   39400002    ldrb    w2, [x0]
 a50:   b94037e0    ldr w0, [sp, #52]
 a54:   b94023e1    ldr w1, [sp, #32]
 a58:   1ac10c03    sdiv    w3, w0, w1
 a5c:   b94023e1    ldr w1, [sp, #32]
 a60:   1b017c61    mul w1, w3, w1
 a64:   4b010000    sub w0, w0, w1
 a68:   93407c00    sxtw    x0, w0
 a6c:   f9400fe1    ldr x1, [sp, #24]
 a70:   8b000020    add x0, x1, x0
 a74:   39400001    ldrb    w1, [x0]
 a78:   b98037e0    ldrsw   x0, [sp, #52]
 a7c:   f9401fe3    ldr x3, [sp, #56]
 a80:   8b000060    add x0, x3, x0
 a84:   4a010041    eor w1, w2, w1
 a88:   12001c21    and w1, w1, #0xff
 a8c:   39000001    strb    w1, [x0]
 a90:   b94037e0    ldr w0, [sp, #52]
 a94:   11000400    add w0, w0, #0x1
 a98:   b90037e0    str w0, [sp, #52]
 a9c:   b94037e1    ldr w1, [sp, #52]
 aa0:   b94027e0    ldr w0, [sp, #36]
 aa4:   6b00003f    cmp w1, w0
 aa8:   54fffccb    b.lt    a40 <sub_9fc+0x44>  // b.tstop
 aac:   f9401fe0    ldr x0, [sp, #56]
 ab0:   a8c47bfd    ldp x29, x30, [sp], #64
 ab4:   d65f03c0    ret

인자로 받은 값을 차례대로 스택에 저장합니다. w1w3은 데이터의 길이로 추정되므로 x0, x2에 들어온 값을 보면 [sp + #40] = \xFC\x7F\x65\x79\xFF\x73\x6C\x7D\xF4\x77\x6D\x61\xE7\x6B\x74\x65\xEC\x6F\x75\x69\xEF\x63\x46\x53\xDA\x5D\x47\x57\xD1\x51\x4E\x5B\xD2\x55\x4F\x5F\xD9\x49\x56\x43\xCA\x4D\x57\x47\xC1\x41\x5E\x4B\xA9\x28\x30\x22\xA2\x2C\x31\x26\xA1\x20, [sp + #24] = \x96\x19\x7\x11\x99\x19\x2\x11 이 됩니다. 스택에 값을 저장한 후 첫 번째 인자의 길이+1 만큼 malloc을 해준 후 sp+#56에 저장합니다. 그 후 첫 번째 인자의 길이를 가져와서 malloc한 주소의 마지막 index에 0x0을 넣어주고 sp+#52에 0을 저장해준 후 a9c로 이동합니다.

a9c에서는 0을 w1에 넣고 첫 번째 문자열의 길이를 w0에 불러온 후 두 개를 비교하여 a40으로 이동합니다. w1=w0이 되면 flag0으로 세팅되고 b.lt 연산이 수행되지 않으므로 첫 번째 문자열의 길이만큼 반복하는 구간임을 알 수 있습니다. a40에서 [sp+#40 + sp+#52]의 값을 w2에 넣고 w3에는 sp+#52 - sp+#32 * (sp+#52 / sp+#32) 연산을 통해 sp+#52 % sp+#32 를 수행한 후 해당 주소의 값을 가져와서 두 문자열을 xor 연산합니다.

sub_9fc 를 c로 간단하게 나타내면 아래와 같습니다.

// sub_9fc
char* sub_9fc(char* param1, int param2, char* param3, int param4)
{
    char* var1 = malloc(param2 + 1);
    var1[param2] = '\0';

    for(int i = 0; i < param2; i++)
    {
        var1[0] = (param1[i] ^ param3[i % param4]) & 0xff;
    }
    return var1;
}

해당 문자열들이 sub_9fc를 거쳐 어떠한 값이 나오는지 구하는 python 스크립트는 아래와 같습니다.

string1 = [
    0xF7, 0x7B, 0x64, 0x75, 0xFC, 0x7F, 0x65, 0x79, 0xFF,
    0x73, 0x6C, 0x7D, 0xF4, 0x77, 0x6D, 0x61, 0xE7, 0x6B, 0x74, 0x65,
    0xEC, 0x6F, 0x75, 0x69, 0xEF, 0x63, 0x46, 0x53, 0xDA, 0x5D, 0x47,
    0x57, 0xD1, 0x51, 0x4E, 0x5B, 0xD2, 0x55, 0x4F, 0x5F, 0xD9, 0x49, 0x56,
    0x43, 0xCA, 0x4D, 0x57, 0x47, 0xC1, 0x41, 0x5E, 0x4B, 0xA9, 0x28, 0x30,
    0x22, 0xA2, 0x2C, 0x31, 0x26, 0xA1, 0x20
]

string2 = [0x96, 0x19, 0x7, 0x11, 0x99, 0x19, 0x2, 0x11]
result = ""

for i in range(len(string1)):
    result += chr(string1[i] ^ string2[i % len(string2)])

print(result)

'''
C:\Users\idioth\Desktop>lock.py
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
'''

sub_9fc까지 마쳤으니 다시 main으로 돌아가죠!

// main
int main()
{
    char *var1 = calloc(0x13, 1);
    int var2 = sub_9b0(sub_ab8());

    sub_96c();
    scanf("%s", var1);

    sub_b30(var1, var2);
    free(var1);

    return 0;
}

이제 입력 값을 받아서 sub_b30하는 것만 남았네요. sub_b30 함수를 확인해봅시다.

0000000000000b30 <sub_b30>:
 b30:   a9be7bfd    stp x29, x30, [sp, #-32]!
 b34:   910003fd    mov x29, sp
 b38:   f9000fe0    str x0, [sp, #24]
 b3c:   f9000be1    str x1, [sp, #16]
 b40:   f9400fe0    ldr x0, [sp, #24]
 b44:   91001800    add x0, x0, #0x6
 b48:   39400001    ldrb    w1, [x0]
 b4c:   f9400be0    ldr x0, [sp, #16]
 b50:   91003800    add x0, x0, #0xe
 b54:   39400000    ldrb    w0, [x0]
 b58:   6b00003f    cmp w1, w0
 b5c:   54000761    b.ne    c48 <sub_b30+0x118>  // b.any
 b60:   f9400fe0    ldr x0, [sp, #24]
 b64:   91001000    add x0, x0, #0x4
 b68:   39400001    ldrb    w1, [x0]
 b6c:   f9400be0    ldr x0, [sp, #16]
 b70:   91000c00    add x0, x0, #0x3
 b74:   39400000    ldrb    w0, [x0]
 b78:   6b00003f    cmp w1, w0
 b7c:   54000661    b.ne    c48 <sub_b30+0x118>  // b.any
 b80:   f9400fe0    ldr x0, [sp, #24]
 b84:   91000c00    add x0, x0, #0x3
 b88:   39400001    ldrb    w1, [x0]
 b8c:   f9400be0    ldr x0, [sp, #16]
 b90:   91002800    add x0, x0, #0xa
 b94:   39400000    ldrb    w0, [x0]
 b98:   6b00003f    cmp w1, w0
 b9c:   54000561    b.ne    c48 <sub_b30+0x118>  // b.any
 ba0:   f9400fe0    ldr x0, [sp, #24]
 ba4:   91001c00    add x0, x0, #0x7
 ba8:   39400001    ldrb    w1, [x0]
 bac:   f9400be0    ldr x0, [sp, #16]
 bb0:   91004400    add x0, x0, #0x11
 bb4:   39400000    ldrb    w0, [x0]
 bb8:   6b00003f    cmp w1, w0
 bbc:   54000461    b.ne    c48 <sub_b30+0x118>  // b.any
 bc0:   f9400fe0    ldr x0, [sp, #24]
 bc4:   91000400    add x0, x0, #0x1
 bc8:   39400001    ldrb    w1, [x0]
 bcc:   f9400be0    ldr x0, [sp, #16]
 bd0:   91003800    add x0, x0, #0xe
 bd4:   39400000    ldrb    w0, [x0]
 bd8:   6b00003f    cmp w1, w0
 bdc:   54000361    b.ne    c48 <sub_b30+0x118>  // b.any
 be0:   f9400fe0    ldr x0, [sp, #24]
 be4:   91001400    add x0, x0, #0x5
 be8:   39400001    ldrb    w1, [x0]
 bec:   f9400be0    ldr x0, [sp, #16]
 bf0:   9100d000    add x0, x0, #0x34
 bf4:   39400000    ldrb    w0, [x0]
 bf8:   6b00003f    cmp w1, w0
 bfc:   54000261    b.ne    c48 <sub_b30+0x118>  // b.any
 c00:   f9400fe0    ldr x0, [sp, #24]
 c04:   39400001    ldrb    w1, [x0]
 c08:   f9400be0    ldr x0, [sp, #16]
 c0c:   9100d400    add x0, x0, #0x35
 c10:   39400000    ldrb    w0, [x0]
 c14:   6b00003f    cmp w1, w0
 c18:   54000181    b.ne    c48 <sub_b30+0x118>  // b.any
 c1c:   f9400fe0    ldr x0, [sp, #24]
 c20:   91000800    add x0, x0, #0x2
 c24:   39400001    ldrb    w1, [x0]
 c28:   f9400be0    ldr x0, [sp, #16]
 c2c:   91000800    add x0, x0, #0x2
 c30:   39400000    ldrb    w0, [x0]
 c34:   6b00003f    cmp w1, w0
 c38:   54000081    b.ne    c48 <sub_b30+0x118>  // b.any
 c3c:   f9400fe0    ldr x0, [sp, #24]
 c40:   97ffffab    bl  aec <sub_aec>
 c44:   d503201f    nop
 c48:   a8c27bfd    ldp x29, x30, [sp], #32
 c4c:   d65f03c0    ret

위에서 복호화한 [a-zA-Z0-9] 값과 입력 값을 통해 하나씩 비교한 후 조건이 모두 맞으면 x0에 input 값을 넣고 sub_aec를 호출합니다. 값을 비교하는 부분을 간단하게 정리하면 아래와 같습니다.

  • var1[0x6] == var2[0xe]
  • var1[0x4] == var2[0x3]
  • var1[0x3] == var2[0xa]
  • var1[0x7] == var2[0x11]
  • var1[0x1] == var2[0xe]
  • var1[0x5] == var2[0x34]
  • var1[0x0] == var2[0x35]
  • var1[0x2] == var2[0x2]

순서에 맞춰서 배열하면 input 값을 구할 수 있습니다.

user_input = ""
user_input += result[0x35] + result[0xe] + result[0x2] + result[0xa] + result[0x3] +
result[0x34] + result[0xe] + result[0x11]

print(user_input)

'''
C:\Users\idioth\Desktop>lock.py
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
1ockd0or
'''

조건이 맞는 입력 값을 인자로 sub_aec 값을 호출하니 해당 함수를 확인해보면 아래와 같습니다.

value2  DCB 0x69, 0x22, 0x22, 0x38, 0x1F, 0x43, 0x5B, 0x1C, 0x45, 0xE, 0x3C,
0x8, 0x5, 0x5E, 0x30, 0x17, 0x5F, 0x1B, 0x6, 0x19, 0x3B, 0x44, 0x7, 0x17,
0x6E, 0x7, 0x53, 0x1E, 0x17, 0x55, 0x12

0000000000000aec <sub_aec>:
 aec:   a9bd7bfd    stp x29, x30, [sp, #-48]!
 af0:   910003fd    mov x29, sp
 af4:   f9000fe0    str x0, [sp, #24]
 af8:   f9400fe0    ldr x0, [sp, #24]
 afc:   97ffff29    bl  7a0 <strlen@plt>
 b00:   2a0003e3    mov w3, w0
 b04:   f9400fe2    ldr x2, [sp, #24]
 b08:   528003e1    mov w1, #0x1f                   // #31
 b0c:   90000000    adrp    x0, value2@page
 b10:   91382000    add x0, x0, value2@pageoff
 b14:   97ffffba    bl  9fc <sub_9fc>
 b18:   f90017e0    str x0, [sp, #40]
 b1c:   f94017e0    ldr x0, [sp, #40]
 b20:   97ffff3c    bl  810 <puts@plt>
 b24:   d503201f    nop
 b28:   a8c37bfd    ldp x29, x30, [sp], #48
 b2c:   d65f03c0    ret

입력 값과 value2를 인자로 sub_9fc를 수행하는 것을 확인할 수 있습니다. sub_9fc의 경우 아까 python script를 짜 놓았기 때문에 그냥 값을 입력하여 연산하면 됩니다.

string3 = [
    0x69, 0x22, 0x22, 0x38, 0x1F, 0x43, 0x5B, 0x1C, 0x45, 0xE, 0x3C,
0x8, 0x5, 0x5E, 0x30, 0x17, 0x5F, 0x1B, 0x6, 0x19, 0x3B, 0x44, 0x7, 0x17,
0x6E, 0x7, 0x53, 0x1E, 0x17, 0x55, 0x12
]
flag = ""
for i in range(len(string3)):
    flag += chr(string3[i] ^ ord(user_input[i % len(user_input)]))

print(flag)

'''
C:\Users\idioth\Desktop>lock.py
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
1ockd0or
XMAS{s4nta_can_enter_the_h0use}
'''

FLAG : XMAS{s4nta_can_enter_the_h0use}