Search

[Christmas CTF 2020] lock

Last edited time
2022/01/07 12:48
tag
reversing
arm
Category
CTF 문제들
Visibility
Public

1. 문제

요즘 건물들에는 굴뚝이 없어서 산타가 몰래 들어가서 선물을 줄 수가 없다… 그래서 열쇠를 몰래 딸 수 있는 당신을 고용했다. 그런데 이 도어락은 우리가 흔히 보던 것과 좀 다른 것 같은데…? 원리만 이해하면 쉽게 딸 수 있을 것 같다!
Plain Text
복사
arm 어셈 덤프가 주어진다 - 다운로드
대회 당일 arm 어셈에 대한 지식이 부족해서 분석하다가 끝내 마무리하지 못했다. 롸업이 올라오고 나서 보니 그냥 분석해서 플래그 값을 얻어내면 되는 문제이다

2. 접근방법

main()
0000000000000c50 <main>: c50: a9be7bfd stp x29, x30, [sp, #-32]! //스택공간 확보, sp=sp-32, push x29, push x30 c54: 910003fd mov x29, sp //sp 를 x29에 복사 c58: d2800021 mov x1, #0x1 // #1 초기 x1=1 c5c: d2800260 mov x0, #0x13 // #19 x0=0x13 c60: 97fffee0 bl 7e0 <calloc@plt> //0x13만큼 동적할당하고 1로 초기화 c64: f9000be0 str x0, [sp, #16] //sp+16에 calloc한 주소 저장 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
C
복사
몰랐던 instruction 위주로 설명하겠다. 우선 32바이트 만큼 스택을 확보하고, calloc(0x13,1)을 호출한다.
arm에서는 함수의 반환값을 x0 레지스터에 저장하므로 calloc을 통해 할당받은 힙주소를 sp+16에 저장한다. (buf)
sub_ab8() 함수를 호출한다. 반환되는 값은 x0에 담기므로 sub_9b0()은 x0를 인자로 하여 호출된다
함수 호출의 인자는 x0, x1, x2 ... 순인므로 scanf("%s", buf) 사용자의 입력을 받은 뒤 buf에 저장한다
buf(sp+16)와, sub_9b0() 반환값(sp+24)를 인자로 하여 sub_b30()이 호출된다
메인함수를 수도코드로 포팅하면 다음과 같이 표현할 수 있다
int main() { char* buf=calloc(0x13,1); //buf=sp+16 a=sub_9b0(sub_ab8()); // a= sp+24 b=sub_96c(); scanf("%s",buf); sub_b30(buf,a); return 0; }
C
복사
sub_ab8()
0000000000000ab8 <sub_ab8>: ab8: a9be7bfd stp x29, x30, [sp, #-32]! // sp-32만큼 확보하고 sp=sp-32, push x29, x30 abc: 910003fd mov x29, sp // sp를 x29에 복사 ac0: 52800103 mov w3, #0x8 // #8 ac4: 90000000 adrp x0, value0@page // 명령 포인터 복사 ac8: 9136e002 add x2, x0, value0@pageoff // x2=포인터+value0 즉, value2의 주소=>x2 acc: 528007c1 mov w1, #0x3e // #62 ad0: 90000000 adrp x0, value1@page // x0=value1 주소 ad4: 91372000 add x0, x0, value1@pageoff //x0=x0+value1주소 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
C
복사
sub_9fc()에 들어가는 인자 4개를 세팅한다(x0, w1, x2, w3)
(w- 계열 레지스터는 4byte 레지스터에고 8바이트 확장된게 x- 계열이다)
w3 = 0x8
x2 = value0
w1 = 0x3e
x0 = value1
sub_9fc(value1,0x3e,value0,w3)
위 함수의 반환값을 ret한다
sub_ab8()함수를 포팅하면 다음과 같다
sub_ab8() { return sub_9fc(value1,0x3e,value0,0x8); // a=sp+24 }
C
복사
sub_9fc()
00000000000009fc <sub_9fc>: 9fc: a9bc7bfd stp x29, x30, [sp, #-64]! a00: 910003fd mov x29, sp a04: f90017e0 str x0, [sp, #40] // x0=value1 주소 a08: b90027e1 str w1, [sp, #36] // w1=0x3e a0c: f9000fe2 str x2, [sp, #24] // x2=value0 주소 a10: b90023e3 str w3, [sp, #32] // w3=8 a14: b94027e0 ldr w0, [sp, #36] // w0=0x3e a18: 11000400 add w0, w0, #0x1 // w0=0x3f a1c: 93407c00 sxtw x0, w0 a20: 97ffff68 bl 7c0 <malloc@plt> // 0x3f 사이즈 동적할당 a24: f9001fe0 str x0, [sp, #56] // 할당받은 주소를 sp+56에 저장 a28: b98027e0 ldrsw x0, [sp, #36] // x0에 0x3e 저장 a2c: f9401fe1 ldr x1, [sp, #56] // 할당 받은 주소를 x1에 저장 a30: 8b000020 add x0, x1, x0 // x0 = 할당받은 주소 + 0x3e a34: 3900001f strb wzr, [x0] // wzr는 zero register. 항상 0이 들어감 a38: b90037ff str wzr, [sp, #52] a3c: 14000018 b a9c <sub_9fc+0xa0> // ...a9c ---------------------------------------loop---------------------------------- a40: b98037e0 ldrsw x0, [sp, #52] // 초기값 0 a44: f94017e1 ldr x1, [sp, #40] //value1 a48: 8b000020 add x0, x1, x0 // value1 주소 + x0 a4c: 39400002 ldrb w2, [x0] // value1+x0에 담긴 한바이트를 w2에 저장 a50: b94037e0 ldr w0, [sp, #52] // 다시 초기값 0 a54: b94023e1 ldr w1, [sp, #32] // 8 a58: 1ac10c03 sdiv w3, w0, w1 // index / 8 a5c: b94023e1 ldr w1, [sp, #32] a60: 1b017c61 mul w1, w3, w1 // (index/8)*8 a64: 4b010000 sub w0, w0, w1 // index - 8*(index/8) a68: 93407c00 sxtw x0, w0 a6c: f9400fe1 ldr x1, [sp, #24] //x1=value0 a70: 8b000020 add x0, x1, x0 // x0 = value0 + x0 a74: 39400001 ldrb w1, [x0] // value0 한바이트 가져오기 a78: b98037e0 ldrsw x0, [sp, #52] // index a7c: f9401fe3 ldr x3, [sp, #56] // buf a80: 8b000060 add x0, x3, x0 // buf + index a84: 4a010041 eor w1, w2, w1 // w1 =w2 ^ w1 a88: 12001c21 and w1, w1, #0xff // w1 = w1 & 0xff a8c: 39000001 strb w1, [x0] a90: b94037e0 ldr w0, [sp, #52] a94: 11000400 add w0, w0, #0x1 // index++ a98: b90037e0 str w0, [sp, #52] a9c: b94037e1 ldr w1, [sp, #52] // 초기값 0 aa0: b94027e0 ldr w0, [sp, #36] // 0x3e 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
C
복사
넘어온 인자 4개를 변수에 저장한다
0x3e+1 만큼 malloc한다
str wzr, [sp, #52] ⇒ wzr은 항상 0이 저장되고 0이 읽히는 레지스터이다. 따라서 sp+52에 0을 집어넣는다
sub_9fc+0xa0 로 이동한다 ⇒ 0x..a9c
sp+52 값인 0을 w1에 넣고, sp+36인 0x3e를 w0에 넣은뒤 두 개를 서로 배교한다. w1이 더 작으면 sub_9fc+0x44 ⇒ 0x..a40으로 다시 이동한다
sp+52를 인덱스로하여 특정 연산지 진행된다
a50: b94037e0 ldr w0, [sp, #52] // 다시 초기값 0 a54: b94023e1 ldr w1, [sp, #32] // 8 a58: 1ac10c03 sdiv w3, w0, w1 // index / 8 a5c: b94023e1 ldr w1, [sp, #32] a60: 1b017c61 mul w1, w3, w1 // (index/8)*8 a64: 4b010000 sub w0, w0, w1 // index = index - 8*(index/8) a68: 93407c00 sxtw x0, w0
C
복사
1.
value1 + index 의 한바이트 값을 가져온다. 초기 index(w0)는 0이다
2.
sp+32는 8이므로 w1에 저장한다
3.
(sp+52) - ((sp+52)/8) * 8 연산을 진행한다
4.
이 연산의 의미는 index = index - 8*(index/8) 이고 해당 index가 다음 루프의 index로 사용된다. 즉 value1[index %8] 로 해석하면 된다
5.
계산된 w0를 singed 부호확장을 진행하여 x0에 저장한다.
sp+52를 인덱스로 하여 이번에는 value0의 한바이트를 가져온다.
value[index]
위에서 구한 두개의 값을 xor 연산하고 0xff와 and 연산을 진행한다
( value1[index%8] ^ value0[index] ) & 0xff
계산된 한바이트를 malloc buf에 저장한다
루프가 끝나면 buf의 주소를 반환한다
해당 함수는 다음과 같이 표현 가능하다
sub_9fc(char* value1, int len, char* value0, int b) { char* arg1=value1; int arg2=a; //0x3e char* arg3=value0; int arg4=b; //8 char* buf=malloc(arg2+1); // buf = sp+56 buf+0x3e=0; for(i=0;i<len;i++) { buf[i]=( value0[i] ^ value1[i%b] ) & 0xff; } return buf; }
C
복사
sub_9b0()
00000000000009b0 <sub_9b0>: 9b0: a9bd7bfd stp x29, x30, [sp, #-48]! 9b4: 910003fd mov x29, sp 9b8: f9000fe0 str x0, [sp, #24] // sub_9fc의 반환 값 저장 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
C
복사
인자로 넘어온 연산된 값이 저장된 buf를 인자로 strlen이 호출된다. buf의 사이즈를 계산한다
strlen을 통해 얻은 사이즈 + 1만큼 malloc을 한다
할당받은 주소가 0이면 0x..9f4로 분기해서 종료되고 아니면 0x..9e4로 분기한다
sp+24인 buf의 주소와 할당받은 주소인 sp+40을 인자로 strcpy를 호출한다
즉 새롭게 할당받은 영역에 연산된 buf 를 복사하고 buf를 반환한다
위 함수는 다음과 같이 표현 할 수 있다
sub_9b0(int data) { b=strlen(data); char* buf=malloc(b+1); //buf = sp+40 if(buf) { strcpy(buf,data); return buf; } else { return 0; } }
C
복사
sub_b30(buf,a)
0000000000000b30 <sub_b30>: b30: a9be7bfd stp x29, x30, [sp, #-32]! b34: 910003fd mov x29, sp b38: f9000fe0 str x0, [sp, #24] // x0값을 sp+24에 저장 b3c: f9000be1 str x1, [sp, #16] // x1값을 sp+16에 저장 b40: f9400fe0 ldr x0, [sp, #24] // sp+24를 읽어서 x0에 저장 b44: 91001800 add x0, x0, #0x6 // x0 = x0 + 6 b48: 39400001 ldrb w1, [x0] //x0에 들어있는 한바이트를 w1에 저장 b4c: f9400be0 ldr x0, [sp, #16] // sp+16을 읽어서 xo에 저장 b50: 91003800 add x0, x0, #0xe // x0 = x0+ 0xe b54: 39400000 ldrb w0, [x0] // x0에 들어있는 한바이트를 w0에 저장 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
C
복사
사용자가 입력한 값과 특정 연산이 수행된 값을 비교한다.
사용자의 입력값 8바이트를 각각 비교한다. 이를 다음과 같이 표현 할 수 있다
a=a+6; b=b+0xe; w1=*a; x0=*b; a=a+4; b=b+3; w1=*a; x0=*b; a=a+3; b=b+0xa; w1=*a; x0=*b; a=a+7; b=b+0x11; w1=*a; x0=*b; a=a+1; b=b+0xe; w1=*a; x0=*b; a=a+5; b=b+0x34; w1=*a; x0=*b; a=a+0; b=b+0x35; w1=*a; x0=*b; a=a+2; b=b+0x2; w1=*a; x0=*b;
C
복사
8개의 조건을 다 만족한다면 이를 인자로 하여 sub_aec 함수를 호출한다
sub_aec(a)
0000000000000aec <sub_aec>: aec: a9bd7bfd stp x29, x30, [sp, #-48]! af0: 910003fd mov x29, sp af4: f9000fe0 str x0, [sp, #24] // buf 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
C
복사
사용자 입력값의 사이즈를 strlen을 통하여 얻는다
아까 봤던 sub_9fc()를 호출한다. 들어가는 인자는 다음과 같다
1.
x0 : value2 주소
2.
w1 : 0x1f
3.
w2 : 사용자 입력값 주소
4.
w3 : strlen을 통해서 얻은 사용자 입력 값 사이즈

3. 풀이

코드 분석은 다했다. 중요한 부분은 결국 다음과 같다
1.
value0, value1을 통해서 계산되는 buf의 값을 구한다
value1 = [ 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 ] value0 = [0x96, 0x19, 0x7, 0x11, 0x99, 0x19, 0x2, 0x11] value2 = [ 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 ] buf = "" for i in range(0x3E): buf += chr((value1[i] ^ value0[i % 8]) & 0xFF) print(buf) ================================================================================ E:\JungJaeho\STUDY\Self\hacking\ctf\chrismasctf\c545e718-b031-4028-876c-1998a6ab071e> abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 // 연산된 값
C
복사
2.
사용자 입력값 8바이트와 1번을 통해 얻은 buf의 특정 인덱스 별 바이트 값을 비교하여 동일하게 세팅한다
... my_input = "" my_input += buf[0x35] my_input += buf[0xE] my_input += buf[0x2] my_input += buf[0xA] my_input += buf[0x3] my_input += buf[0x34] my_input += buf[0xE] my_input += buf[0x11] print(my_input) ================================================================================ E:\JungJaeho\STUDY\Self\hacking\ctf\chrismasctf\c545e718-b031-4028-876c-1998a6ab071e> abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 // 연산된 값 1ockd0or // 사용자가 입력해야하는 값!!
C
복사
3.
2번을 통해 얻은 사용자 입력값을 인자로 다시 특정 연산을 거친뒤 출력한다
... result = "" for i in range(0x1F): result += chr((value2[i] ^ ord(my_input[i % len(my_input)])) & 0xFF) print(result) ================================================================================ E:\JungJaeho\STUDY\Self\hacking\ctf\chrismasctf\c545e718-b031-4028-876c-1998a6ab071e> abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 // 연산된 값 1ockd0or // 사용자가 입력해야하는 값!! XMAS{s4nta_can_enter_the_h0use}
C
복사
최종 페이로드는 다음과 같다
value1 = [ 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 ] value0 = [0x96, 0x19, 0x7, 0x11, 0x99, 0x19, 0x2, 0x11] value2 = [ 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 ] buf = "" for i in range(0x3E): buf += chr((value1[i] ^ value0[i % 8]) & 0xFF) print(buf) my_input = "" my_input += buf[0x35] my_input += buf[0xE] my_input += buf[0x2] my_input += buf[0xA] my_input += buf[0x3] my_input += buf[0x34] my_input += buf[0xE] my_input += buf[0x11] print(my_input) result = "" for i in range(0x1F): result += chr((value2[i] ^ ord(my_input[i % len(my_input)])) & 0xFF) print(result)
Python
복사

4. 몰랐던 개념

arm 어셈 (aarch64)
str, ldr 순서. str은 ⇒, ldr은 <=
sxtw : signed 부호 확장 명령어
wzr : zero register로서 항상 0이 들어간다

5. 참고자료