zj3t

email: euntaejang@gmail.com

Latest Posts
이번문제는 약간 난이도가 있는 문제였습니다. 일단 스택의 구조를 정확히 알아야 했더군요 예전에 풀었을 때에는 쉬운 방법으로 풀었었는데 이번에는 ROP도 약간 배웠겠다. 
POP| POP| RET을 조금 써보고 싶어 다른 방법으로 풀었습니다. 



일단 문제의 코드입니다. 권한상승을 위한 함수가 없는게 눈에 띕니다. 따라서 이 문제는 return to lib 기법을 사용하라는 문제임은 확실합니다.

라이브러리에서 필요한 함수(예를 들어 system(), setreuid() 등)의 주소를 알아내어 사용하는 기법입니다.

두가지 방법이 있습니다. 
1.                          ebp
------------------------------------------------------------------------------------------
 | buf[20] | dummy[20] | sfp | setreuid() | ad of shellcode | 매개변수1 | 매개변수2 |
-------------------------------------------------------------------------------------------
낮                                                                                                     높

리턴 어드레스에 권한상승을 해주는 함수를 넣었습니다. 이 때 스택은 함수 바로 뒤가 아닌+4 한 거리에서 매개변수들을 가져와 실행됩니다. (바로 뒤는 그 함수의 ret의 주소) 그리고 ret 으로가서(여기서는 ad of shellcode) /bin/sh 쉘 코드가 담긴 환경변수의 주소를 실행합니다.

그렇게 하면 권한을 얻고 /bin/sh를 실행시킴으로써 level20의 권한으로 my-pass를 실행시킬 수 있습니다.





환경 변수의 주소를 얻는 코드!!!


이렇게 setreuid의 주소와 환경변수의 주소만 가지고 문제를 clear 할 수 있었습니다.
(0x00000C1C 는 3100 즉 level 20의 id)

2. 이번 방법은 rop 공부를 할 때 배운 pop ret을 이용 한것인데, 굳이 이 문제를 이렇게 푸는 사람을 드물겠지만 한번 보여드리겠습니다.

낮                                                                                                           높
| buf[20] | dummy[20] | sfp | setreuid() (ret) | pop pop ret | 3100 | 3100 | system() | \x90*4(ret)| "/bin/sh"문자열의 주소(환경 변수 아님) | 

우선 아까 위에서는 setreuid의 리턴 값에 /bin/sh를 실행시키는 쉘코드를 넣었지만 이번 방법은 pop pop ret 을 넣었습니다.

여기서 중요한데 깊게는 안다루고 간단히 설명하자면

setreuid(3100, 3100) 함수가 실행되고 나면 esp가 한칸 올라가게 된다.

말이 조금 어려운데 만약 pop pop ret이 없다면, (빨간색이 esp의 위치)
                                                                                                  
| buf[20] | dummy[20] | sfp | setreuid() | setreuid의 ret주소 | 3100 | 3100 | system() | 
                                                                      

이랬던 스택이 
                                                                                     
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() | 
                                                                            
이렇게 된다. 그러므로 우리는  esp 가 system 함수를 실행 시킬 위치가 아니게 된다.

                                                            
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() | \x90*4 | "/bin/sh"|
                                                                    

이 구조에서 그렇다면 어떻게 esp 를 

| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() | \x90*4 | "/bin/sh"|
                                                                        

이렇게 움직여서 system() 함수를 실행시킬 수 있을까?

그것이 바로 pop pop ret 을 setreuid()의 return address에 넣어 esp 의 위치를 두번 이동시킨뒤 채택하는 것이다.

(채택이라는 말이 조금 이상한데 ret 명령어 자체가 pop eip jmp eip 라 채택이라고 받아들이면 쉬울 것이다.)

따라서 그림을 보면 

-처음 상태
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() |
                                                        

-pop
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() |
                                                                        
-pop
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() | 
                                                                                 

-ret (채택)
| buf[20] | dummy[20] | sfp | setreuid() | ret | 3100 | 3100 | system() |
                                                                                             

이렇게 esp를 바꿔주어 system() 을 실행시킬 수 있는 것이다.

system() 함수에서도 마찬가지이다. \x90*4가 아닌 pop | ret 가젯을 써서 명령어를 이어 붙일 수가 있다.
여기서는 그럴 필요가 없기 때문에 하지 않았다.



먼저 system(), setreuid() 함수를 이용할 것이기 때문에 이 두 주소의 주소를 구한다.



이는 아주 유명한 코드인데 라이브러리 영역에서 "/bin/sh" 문자열이 있는 주소를 찾는 코드이다.


이렇게 "/bin/sh" 의 주소 그리고 system(), setreuid()의 주소를 구했다. 이제 pop | pop | ret이 있는 주소의 위치를 찾으면 된다.


이는 objdump -d(disassemble) ./실행파일 명 | egrep "찾을 어셈블리 명령어"

을 이용하면 된다. 

그러면 여러 pop ret 가젯들이 보이는데 이어져 있는 주소를 골라야 한다 여기서는 804849d 정도면 될 것 같다.


그럼 이제 공격 코드를 작성하면 성공 할 수 있다.

첫번 째 방법으로 푸는 것이 아마 이 문제 출제자의 목적이라고 생각이 되지만, 밑에 방법으로 푸는 것이 나중에 어차피 공부할 것을 미리 공부하는 마음가짐으로 공부하면 재밌을 것이라 생각된다.
FTZ 18,19,20 번을 예전에 풀었는데 포스팅을 안한거 같아 해봅니다.


#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void shellout(void);

int main()
{
  char string[100];
  int check;
  int x = 0;
  int count = 0;
  fd_set fds;   //fd 변수 선언

  printf("Enter your command: ");
  fflush(stdout);

  while(1)
    {
      if(count >= 100)
        printf("what are you trying to do?\n");
      if(check == 0xdeadbeef)
        shellout();
      else
       
          FD_ZERO(&fds);  //fd 변수의 모든 내용을 지운다.                                
          FD_SET(STDIN_FILENO,&fds); //fd를 setting함

          if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)
            {
              if(FD_ISSET(fileno(stdin),&fds))
                {
                  read(fileno(stdin),&x,1); x를 1byte 입력받음
                  switch(x)
                    {
                      case '\r':
                      case '\n':
                        printf("\a");
                        break;
                      case 0x08:
                        count--;
                        printf("\b \b");
                        break;
                      default:
                        string[count] = x;
                        count++;
                        break;
                    }
                }
            }
        }
    }
}

void shellout(void)
{
  setreuid(3099,3099);
  execl("/bin/sh","sh",NULL);
}  

다시 풀다보니 기억이 잘안나서 시간을 좀썻는데 일단 fd_set, FD 관련 함수들은 나중에 생각을 해도 괜찮습니다. (몰라도 문제는 풀 수 있습니다.)

중요한 부분은
  char string[100];
  int check;
  int x = 0;
  int count = 0;
  fd_set fds;

if(check == 0xdeadbeef)
        shellout();

void shellout(void)
{
  setreuid(3099,3099);
  execl("/bin/sh","sh",NULL);
}  

입니다.

변수를 보면

  char string[100] |                높
---------------------
  int check         |
---------------------
  int x = 0         |
---------------------
  int count        |
---------------------
  fd_set fds       |                  낮
--------------------


count로 증가시켜서 check를 덮어씌우자니 100보다 큰지 체크를 해서 안되고
x를 입력하자니 1바이트만 입력을 받아서 안되고,
따라서 string 배열을 이용해야할텐데 값을 쓰면 높은 데로 증가해서 check를 덮어 씌울수가 없는데
여기서 필요한게 string 변수는 배열이면서 이름 자체가 포인터 입니다.
따라서 check변수를 덮어씌울 수가 있게됩니다.(이는 C언어 수업이 아니므로 생략 C언어는 다들 다룰줄 알아야합니다.)

하지만 간단히 보이겠습니다ㅎㅎ

int a=10;
int *p=&b;
int b=20;
int c=25;

printf("%d\n",*(p-1));
printf("%d\n",*p);
printf("%d\n",*(p+1));
printf("%d\n",*(p+2));

a출력됨 10
p출력됨 20
b출력됨 20
c출력됨 25

여기서 int p[] 라도 마찬가지!!!

따라서
string은 100byte check는 4byte 이므로 4byte를 덮어씌우기 위해서는 count를 -4만큼 감소시켜 그 위치에 값을 써주면 check를 덮을 수가 있습니다.



오랫만의 포스팅입니다. 몇일동안 학교 시스템해킹 과제를 하느랴 FTZ를 못풀었었습니다. FTZ를 끝낸후 과제푼 것을 포스팅 해보겠습니다.



#include <stdio.h>

void printit() {
  printf("Hello there!\n");
}

main()
{ int crap;
  void (*call)()=printit;
  char buf[20];
  fgets(buf,48,stdin);
  setreuid(3098,3098);
  call();
}

이런 코드 입니다. 공격 방법은 간단합니다. 저 printit함수의 시작주소를 저장하는 call변수에 쉘코드가 입력된 환경변수의 주소를 써주면 공격이 성공하게 됩니다.









dummy 부분을 찾아내는 건 저번 포스팅 level16에서 찾은 방법과 동일합니다. 너무 쉽기 때문에 여기까지 포스팅 하겠습니다. 아마 지금까지 문제 푸신분들은 그림만 봐도 아실것입니다.


level16은 코드만 보면 조금 어렵게 느낄 수 있겠지만 레벨 15보다도 훨훨훨씬 쉬었습니다.

풀이 하겠씁니다~



#include <stdio.h>
 
void shell() {
  setreuid(3097,3097);
  system("/bin/sh");
}
 
void printit() {
  printf("Hello there!\n");
}
 
main()
{ int crap;
  void (*call)()=printit;
  char buf[20];
  fgets(buf,48,stdin);
  call();
}   

이런 코드입니다. 많이 긴가민가 하셨을 부분이 아마도 void (*call)()=printit; 부분이라고 생각 됩니다.

void (*call)()=printit; 부분은 C언어에서 함수 포인터라고 합니다.

함수포인터란 말 그대로 함수를 가리키는 포인터 입니다. 함수도 다른 변수들처럼 메모리에 저장이 되기 때문에 시작주소가 존재하고 그럼으로써 시작주소를 가리킬 수 있게 되는 것 입니다.

원형으로는

리턴타입 *변수명(인자)=함수의시작주소

그렇다면 저 코드를 보자면 | buf |dummy| call |dummy| crap | 과 같은 구조로 스택이 되어있습니다.
지금까지 문제를 푸신분들은 감이 오셨을 거에요 buf에서 입력을 받아 buf[20]을 채우고 dummy까지를 채운뒤 call변수의 값으로 shell()함수의 주소를 넣어주면 됩니다.

그럼 어떻게 shell함수의 시작주소를 알 수가 있을까요??

이렇게 gdb를 이용해서 함수의 시작주소를 찾을 수 있습니다. 

리눅스에서는 objdump라는 명령이 존재합니다. 이 명령으로 시작 주소를 찾으신 분들도 계셨습니다.


시작 주소는 모두 찾았습니다. 이제 해야할 건 공격코드를 만드는 것입니다. 
그러기 위해서는 dummy의 크기를 알아야하는데 노가다를 하기 싫어 약간의 실험(?)을 해보았습니다.


리버싱을 해서 그런지 어셈블리어에서 함수가 호출될 때 인자가 어떤 방식으로 들어가는지 를 알아서 대충 그려봤습니다.

즉 ebp-56이 변수 buf이고 printit()이 call변수에 저장되는 곳이 ebp-16이였습니다. 따라서 이 거리는 40이라고 알 수 있었습니다. 
햇갈리시는 분들은 위의 hint에 있는 attackme.c코드와 스택구조를 같이보시면 이해가 쉬우실 겁니다.



끝났습니다.

레벨 15가 저한테는 굉장히 어려워서 그런지 이 문제는 굉장히 쉬웠었습니다.



일단 이 15번 문제는 가장 오랜 시간이 걸렸던거 같아요...

저번 포스팅 level15 틀 설명드린 것 중 두 가지 방법이 있었죠?? 첫번째와 두번째 먼저 두번째 방법을 포스팅 해보겠습니다. 첫번째 방법은 역시나 ASLR이 걸려있어 쉽지가 않네요.



일단 여기서는 환경변수를 선언해 줍니다.

 export POINTER=`python -c 'print "\xef\xbe\xad\xde"'`

그런데 가장 먼저 선언한 것은

export P="0xdeadbeef" 였습니다. 굳이 리틀엔디안 방식으로 안해도 이 변수안에 값을 넣고 주소만 알면 이 값을 그대로 읽어 줄것 같았지만 역시나 이런 방식이 아닌 python으로 리틀엔디안을 넣어주었을 때만 성공하더라고요.... 이유는 학교에 잘하는 친구있는데 그 친구한테 물어보고 다시 올리겠습니다.


vi 에디터로 static_address.c 파일을 만들었습니다. 코드는 밑에


솔직히 말씀드리면 저도 시스템해킹을 공부한지 얼마안되고 이렇게 문제풀면서 하나하나 

익히고있습니다. C나 python은 어느정도 다루지만 저런 코드를 짤정도의 실력은 안됩니다.

 왜 저 코드를 써야하나는 지금은 설명드리지 못하고 이것도 제가 이해한 뒤 위에 궁금증과

같이 포스팅 하겠습니다.


어떻게 문제는 잘 푼거 같아요. 이렇게 오래걸릴지 몰랐는데... 처음 봤을 때는 굉장히 간단한 문제라고 생각했었습니다.

이제 level16이네요 level16포스팅은 일요일쯤 하겠습니다.


------------------------------------------------<핵심>-------------------------------------------




쉬운 문제일 줄 알았는데 아주 2일동안 못풀게한 문제입니다.

이 방법이 왜 틀린지만 하루종일 고민했던것 같습니다. 그러다가 생각난 이유가 그동안 왜

우리가 문제를 풀면서 NOP 썰매 즉, 'python -c 'print "\x90"*100+"\xff\xff\xff\xbf"'` 에

서 왜 앞에 NOP을 100개 넣어 주었나 하는 이유였습니다.  우리가 정의한 환경변수에는 우

리가 입력한 값말고 다른 값도 들어가있습니다. 또한 파일의 이름, 디렉토리의 위치에 따라

주소가 환경변수가 위치하는 주소가 다릅니다(그에 대한 설명이 위의 그림중 <핵심>이라

고 설명된 사진 두장입니다.)  즉 100개를 넣음으로써 대략적인 위치를 찾아 우리가 집어넣

은 데이터를 실행하도록 한 것이 NOP썰매 방법이였습니다.

따라서 이러한 이유로 저 윗방법은 틀린 것 입니다.

따라서 문제를 풀기 위해서는 두가지 크게 두가지 방법이 있는 것 같습니다.

1.변수 buf에 0xdeadbeef를 넣고 buf의 주소를 check 포인터 변수에 넣어주는 것
(이 방법은 ASLR이 적용되있다면 할 수 있을지 없을지 모르겠습니다.)

2./home/level15/attackme에서 0xdeadbeef가 들어있는 환경변수의 정확한 주소를 찾아 그 주소를 넣어 공격하는 방법

아직 못해봤지만 가능하다면 두 가지 모두 포스팅 해보겠습니다.






이번 문제는 level12와 거의 유사한 문제입니다.

#include <stdio.h>
#include <unistd.h>

main()
{ int crap;
  int check;
  char buf[20];
  fgets(buf,45,stdin);
  if (check==0xdeadbeef)
   {
     setreuid(3095,3095);
     system("/bin/sh");
   }
}  

fget()함수로 최대 입력횟수(NULL포함)을 지정해준 문제입니다. 따라서 최대 44개의 문자를 입력할 수 있습니다.

우리는 이 문제에서 crap 변수를 생각할 필요가 없습니다. 왜냐하면 밑에 그림을 보시면 됩니다.

따라서 스택의 구조상 우리는 buf[20] | dummy | check 이 부분만 살펴보면 됩니다.   
dummy의 크기를 알아보려고 또 노가다를 해야겠습니다. 
범위는 dummy가 없다고 가정한 20부터 40까지로 지정했습니다.
   
[level14@ftz level14]$ (python -c 'print "\x90"*20+"\xef\xbe\xad\xde"';cat)|./attackme
.
.
.
.
.
.
.
[level14@ftz level14]$ (python -c 'print "\x90"*40+"\xef\xbe\xad\xde"';cat)|./attackme




cat을 붙여주여야 하는 이유!!
=level12에서 이해하기 쉽게는 썼습니다. 여기서 간단히 적고 넘어가자면 cat없이 python만 쓴다면 python 명령이 실행되고 종료되어 파이프라인 오류, 즉 stdout의 역할을 하지 못하므로 cat을 너어주어 입력상태까지 함께 넣어주는 것이라고 생각하면 쉽습니다.








#include <stdlib.h> 

main(int argc, char *argv[])
{
   long i=0x1234567;
   char buf[1024];

   setreuid( 3094, 3094 );
   if(argc > 1)
   strcpy(buf,argv[1]);

   if(i != 0x1234567) {
   printf(" Warnning: Buffer Overflow !!! \n");
   kill(0,11);
   }
}

kill 함수가 모지 라고 생각했지만 프로그램을 이해하기에는 꼭 알고 넘어갈 필요는 없었습니다. 그냥 level11 문제에 i를 변환 시키면 안된다는 것이 추가 됬을 뿐입니다.


일단 이렇게 정리를 할 수가 있겠습니다. gdb어떤 버젼?? 부터 변수가 할당된 후 dummy가 추가 됬다고 합니다. level11 첫 버퍼오버플로우를 설명할 때 설명드렸던거 같아요~~


일단 무조건적으로 일일이 시험을 해봤습니다. 1036에서 변수 i의 값을 침범했습니다. 그럼

 처음부터 변수 i의 거리는 1035일까요?? 아닙니다. 우리는 문자 \x90, 즉 문자열을 일일히

 스택에 덮어씌었었습니다. 그렇다면 NULL문자는?? 문자열의 끝에는 항상 NULL문자열이

붙습니다. 따라서 \x90을 1035개 입력했을 때, 1036번째에는 NULL문자열이 들어가게 됩니

다. 1036개의 \x90을 입력했을 경우에는 1037번째에 NULL문자열이 들어가게 되고, 변수 i

의 영역을 침범했다는 메시지가 뜨게됩니다. 따라서 i까지의 거리는 1036이고 변수 i는 

1037번째 부터 값이 대입되어야 합니다.





level11, level12와 마찬가지로 환경변수에 /bin/sh셸 코드를 넣어 실행하는 방법으로 문제를 풀었습니다. 생략된 부분이 있으면 level11,level12를 보고 오시면 됩니다.

안녕하세요 오랫만의 포스팅인거 같아요~ 그동안 시험기간 때문에 제대로 포스팅을 하지 못했습니다.

오늘도 화이트햇 행사에 참가해서 집에오니 9시라 문제하나만 포스팅 하도록 하겠습니다.

FTZ-level12 문제는 level11문제와 거의 유사합니다. 즉 버퍼오버플로우의 취약점을 이용한 해킹인데 level11에서와 다르게 gets함수로 입력을 받는다는 점입니다.


지금까지와 같은 방식인 hint에 attackme의 코드가 들어있습니다.




이 문제 또한 ASLR이 걸려있습니다. 따라서 level11과 같은 방법으로 풀어야 겠네요...


자 여기까지는 level11과 똑같은 방법입니다. 하지만 level 11에서는 프로그램을 실행할 때 인자를 주어 쉽게 실행할 수 있었습니다. 하지만 이번문제에서는 프로그램을 실행한 뒤 문자를 입력하도록 되어있습니다. ./attackme를 실행한뒤 `python -c 'print " ........"'`를 실행해도 그냥 단순히 변수에 `python -c 'print " ........"'`이란 문자열이 저장 되는 것입니다. 그러면 어떻게 해야할 까요??




바로 파이프라인을 이용하는 것 입니다.

파이프라인은 기호 "|"의 왼쪽 명령의 표준 출력(stdout)을 오른쪽 명령의 표준 입력(stdin)으로 사용됩니다.

예를들어) 명령어A | 명령어B


=명령어 A의 표준출력이 명령어 B에 표준입력으로 된다.

여기서 궁금한 점이 있다. 왜 cat을 붙여야 할까?????

파이프라인 사용시에 왼쪽의 표준 출력이 파이프라인 오른쪽의 표준 입력으로 들어가는데 python이 실행되고 종료되면서 pipe broken오류로 인해 종료가 일어난다. 따라서 사용자의 입력을 출력해주는 cat을 이용해여 stdin을 유지시키는 것이다.

예를 들어) $cat | bash 를 실행하면
cat과 파이프(|)가 동등한 자식프로세스로 연결되면서 cat이 입력 받은 라인이 파이프         를 통해 쉘로 전달되게 된다.

결론적으로 (python -c 'print "........"';cat) | ./attackme에서 괄호를 써주어야 하는 이유는 괄호를 써주지 않으면 python -c 'print "....."'와 cat|./attackme가 따로 실행된다. 따라서 괄호를 사용하는 것이다.
그리고 (python -c 'print "........"';cat) | ./attackme같이 명령어를 입력하게 되면 print사이의 문자열이 출력되고 cat의 입력 대기라인이 같이 attackme에 전달된다 그래서 버퍼오버플로우가 발생하면서 쉘이 실행되는 것이며, 바로 쉘이 종료되는 것이 아닌 사용자입력을 대기하게 되고 이때 우리가 값을 전달하면 쉘에서 그대로 실행이 되는 것이다.














일단 밑에 사진은 제가 푼 것을 요약한 것을 순서대로 나열한 것입니다. 설명은 밑에서 하겠습니다.







굉장이 짜증나는 문제였습니다. 

#include <stdio.h>
#include <stdlib.h>

int main( int argc, char *argv[] )
{
char str[256];

  setreuid( 3092, 3092 );
strcpy( str, argv[1] );
printf( str );

이 코드가 힌트에서 준 attackme코드 입니다. 저 코드를 보고 Shellcode를 이용하여 /bin/bash를 실행해주면 되겠구나 싶었습니다.





여기 까지는 사진을 보고 이해가 가능하실 겁니다. 

즉 우리가 해야할 것은 buffer부터 sfp까지 NOP(0x90)까지 채운뒤 즉 268byte를 채운 뒤 return address를 쉘코드를 실행할 곳으로 바꾸어 주면 됩니다.

<shell code>-25byte(shell을 얻도록 해주는 코드)
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

그래서 제가 맨첨에 시도한 방법은

243byte-NOP
25byte-shellcode

로 설정한뒤에 return address를 NOP으로 설정한 아무곳의 주소로 설정하는 것입니다.
리버싱에서도 했듯이 NOP은 아무명령도 아니기 때문에 NOP만 실행되다가 결국 shellcode를 만나 실행하게 되는 것입니다.

하지만 여기서 엄청나게 많은 시간을 소모하게 했습니다. 
바로 ASLR이 걸려있었습니다ㅋㅋ 다른 블로그를 보니 예전에는 ASLR이 없던 것 같았는데 지금 제가 풀때는 걸려 있었습니다. 


ASLR은 사진으로 확인할 수 있습니다.

그래서 생각한 방법이 RTL기법 입니다. (Return to lib) 
말 그대로, 리턴 값을 라이브러리가 있는 곳으로 바꿔버린다는 것입니다.

하지만 RTL을 사용하기에는 제가 systemhacking을 많이 공부한게 아니여서 잘 모르고 RTL에 모티브를 얻은 것이 환경변수를 이용한 것이였습니다.

저는 환경변수를 하나 만들어 그곳에 shellcode를 넣었고 환경변수의 주소를 알려주는 getenv함수를 이용하여 환경변수의 주소가 바뀌는지 확인하였습니다. 다행이 바뀌지 않는 것을 확인 할 수 있었습니다.



이렇게 해서 리턴어드레스를 환경변수의 주소로 설정해주어 level12의 쉘을 얻을 수 있었습니다.



RTL기법으로 푼 다른 분이 있어서 URL 첨부하겠습니다.