한줄 리버싱 & 함수 리버싱

3.5 한 줄 리버싱

3.5.1 지역변수

  • 어셈블리어

    ​ : C 코드가 구현하는 프로그램을 디스어셈블리한 결과

    • 확인 방법:

      올리디버거에서 main 함수로 이동

      (주소는 보안떄문에 매번 바뀜)

    • 명령어:

      • MOV

        -> 2개의 오퍼랜드(Operand)가짐

        ex)

        0x012613EE MOV DWORD PTR SS:[EBP-8],2
        

        -> 0x012613EE 주소의 오퍼랜드는 [EBP-8]과 2임

        => EBP-8의 주소에 상수 2를 저장하라

      • ADD

        -> 1개의 오퍼랜드 가짐

        (1개의 오퍼랜드를 가지는 명령어는 연산 값을 해당 오퍼랜드에 저장)

        ex)

        ADD EAX, 2
        

        => EAX 레지스터의 값과 상수 2를 더해 EAX에 저장하라

      • 1개의 오퍼랜드를 가지는 명령어는 연산 값을 해당 오퍼랜드에 저장

      • 오퍼랜드가 변하지 않는 명령어는 EFLAGS 레지스터를 설정

3.5.2 전역 변수

  • 전역변수

    지역변수와 다르게 데이터 세그먼트 위치에 저장

    ex) 전역변수 gv의 어셈블리 코드

    0x00D713EE MOV DWORD PTR DS:[gv],2
    

    -> 전역변수 gv의 정확한 메모리 위치를 표시하면 EBP 레지스터가 아닌 특정 메모리 주소에 저장됨

    (전역변수가 모든 함수에서 사용할 수 있도록)

3.5.3 구조체

  • 구조체 출력 어셈블리 코드

    • C 구조체 코드

      struct ST {
          int sv_1;
          int sv_2;
          int sv_3;
      };
      
    • 어셈블리 변환 코드

      0x013d1F8 MOV DWORD PTR SS:[EBP-14],1
      0x013d1F8 MOV DWORD PTR SS:[EBP-10],2
      0x013d1F8 MOV DWORD PTR SS:[EBP-C],3
      
    • 스택 메모리에 저장된 구조체

      Low Address      
        1   EBP-0x14
        2   EPB-0x10
        3   EBP-0xC

      -> C언어 int형은 4바이트이므로, ST 구조체의 크기는 12바이트(4*3)

      => 스택메모리와 같이 연속된 12바이트에 차례로 값 저장

3.5.4 if 리버싱

  • if 어셈블리 코드

    • C언어 if문 코드

      int lv = 2;
      if(lv>0)
          print("%x\n",1v);
      
    • 어셈블리 변환 코드

      0x011213EE MOV DWORD PTR SS:[EBP-8],2
      0x011213EE CMP DWORD PTR SS:[EBP-8],0 #주소는 바뀌니까 그냥 쓰겠음
      0x011213EE JLE SHORT 01121416
      
    • 명령어

      • CMP

        -> 2개의 오퍼랜드를 비교하여 결과를 EFLAGS 레지스터의 사인 플래그에 저장

      • JLE(Jump if less)

        -> CMP가 설정한 EFLAGS 레지스터의 값(사인 플래그)을 보고 점프 여부 판단

3.5.5 switch 리버싱

  • switch 어셈블리 코드

    • C언어 switch 문

      int lv = 2;
      switch (1v){
          case 0:
              printf("lv is 0\n");
          case 1:
              printf("lv is 1\n");
          default:
              printf("default\n");
                      
      }
      
    • 어셈블리 변환 코드

      (주소는 생략함)

      MOV DWORD PTR SS:[EBP-8],2
      MOV EAX,DWORD PTR SS:[EBP-8]
      MOV DWORD PTR SS:[EBP-D0],EAX
      CMP DWORD PTR SS:[EBP-D0],0
      JE SHORT 
      CMP DWORD PTR SS:[EBP-D0],0
      JE SHORT 
      JMP SHORT 
      
    • 명령어

      • JE(Jump if Equal)

        -> CMP명령어로 비교한 값으로 점프 여부 판단

      • JMP(Jump)

        -> EFLAGS 레지스터와 상관없이 무조건 점프

        (default에 해당)

3.5.6 for 리버싱

  • for 어셈블리 코드

    • C언어 for문

      int 1v;
      for(lv=0;lv<2;lv++)
      	print("%x\n",1v);
      
    • 어셈블리 변환 코드

      (주소는 생략함)

      MOV DWORD PTR DWORD PTR SS:[EBP-8],0
      JMP SHORT 
      MOV DWORD PTR DWORD PTR SS:[EBP-8]
      ADD EAX,1
      MOV DWORD PTR DWORD PTR SS:[EBP-8],EAX
      CMP DWORD PTR DWORD PTR SS:[EBP-8],2
      JGE SHORT 
      
    • 명령어

      • JGE(Jump if greater of equal)

        -> CMP 명령어로 비교한 값으로 반복문 탈출 여부 판단

3.5.7 while 리버싱

  • while 어셈블리 코드

    • C언어 while문

      int lv=0;
      while(true){
        if(lv == 2)
        break;
      }
      
    • 어셈블리 변환 코드

      MOV DWORD PTR SS:[EBP-8], 0
      MOV EAX, 1
      TEST EAX, EAX
      JE SHORT 0138142C
      CMP DWORD PTR SS:[EBP-8], 2
      JNZ SHORT 01381406
      JNZ SHORT 0138142C
          
      MOV, EAX, DWORD PTR SS:[EBP-8]
      ADD EAX, 1
      MOV DWORD PTR SS:[EBP-8], EAX
      JMP SHORT 013813F5
      
    • 명령어

      • JNZ(Jump not zero)

        -> CMP명령어로 비교한 값으로 점프 여부 판단

3.6 함수 리버싱

3.6.1 콜링 컨벤션

  • 콜링 컨벤션

    함수가 어떻게 인자를 전달받고 자신을 호출한 함수에게 리턴값을 어떻게 돌려주는지에 대한 약속된 함수 호출 규약

  • 호출 규약

    구분 __cdecl __stdcall __fastcall
    인자 전달 방법 스택 스택 레지스터, 스택
    스택 해제 방법 호출한 함수 호출된 함수 호출된 함수
    • __cdecl

      • __cdecl 호출 규약 C언어

        ex) sum()

        int __cdecl sum(int a, int b, int c)
        {
            return a+b+c;
        }
        int main(){
        	int s;
            s=sum(1,2,3);
            print("%x\n",s);
              	
            return 0;
        }
              
        
      • __cdecl 어셈블리어 코드

        PUSH 3
        PUSH 2
        PUSH 1
        CALL 00221104
        ADD ESP, 0C
        MOV DWORD PTR SS:[EBP-8], EAX
        

        => PUSH 로 stack에 인자 쌓기

      • __cdecl 인자 전달 스택 구조

        Low Addres      
          1 <- ESP
          2    
          3    
        High Address      

        => 스택은 높은 주소에서 낮은 주소로 데이터 쌓음

      • __cdecl 스택 해체

        Low Addres      
          1    
          2    
          3 <- ESP
        High Address      
    • __stdcall

      • __stdcall 호출 규약 C언어

        ex) sum()

        int __cdecl sum(int a, int b, int c)
        {
            return a+b+c;
        }
        int main(){
        	int s;
            s=sum(1,2,3);
            print("%x\n",s);
              	
            return 0;
        }
              
        
      • __stdcall 어셈블리어 코드

        PUSH 3
        PUSH 2
        PUSH 1
        CALL 01271104
        MO DWORD PTR SS:[EBP-8], EAX
              
        
      • 명령어

        • RETN

          -> 호출된 함수에서 호출한 함수로 되돌아감

      • __cdecl 호출 규약과 달리 스택 해제를 호출된 함수에서 함 -> RETN 0C를 이용해 스택 해제
    • __fastcall

      • sum 함수 __fastcall 함수 어셈블리어 코드

        PUSH 3
        MOV EDX, 2
        MOV ECX, 1
        CALL 00B711EA
        MOV DWORD PTR SS:[EBP-8], EAX
        
      • __ cdecl , __stdcall 호출 규약과 달리 인자 입력에 메모리 보다 속도가 빠른 레지스터가 사용됨

        -> 사용에 한계가 있어 스택도 같이 사용

      • 스택 해제는 __stdcall 과 같이 호출된 함수에서 함

3.6.2 함수 호출 리턴값 확인

  • 결과값 리턴하는 main 함수 어셈블리어

    MOV DWORD PTR SS:[EBP-8], EAX
    MOV EAX,DWORD PTR SS:[EBP-8]
    PUSH EAX
    

    -> EAX 레지스터에 함수 리턴값을 스택에 저장

3.6.3 함수 프롤로그, 에필로그

  • 프롤로그

    함수 시작을 위해 스택과 레지스터를 재구성

  • 에필로그

    함수 종료 시 스택과 레지스터를 정리

  • 문자열 출력 C 언어 코드

    int main()
    {
        printf("prologue & epiloque\n");
        return 0;
    }
    
  • 문자열 출력 main 함수 어셈블리어

    PUSH EBP
    MOV EBP, ESP
    MOV ESP,EBP
    POP EBP
    RETN
    

    -> 함수 프롤로그:

    * 이전 함수에서 사용한 EBP를 **PUSH EBP 명령어로 스택에 저장**
    * MOV EBP, ESP 명령어로 **현재 스택 위치 EBP에 저장**
    
    • 함수의 변수와 인자를 위해 스택을 사용함

    -> 함수 에필로그:

    • MOV ESP, EBP를 사용해 스택 위치를 함수 시작 전 위치로 되돌림
    • POP EBP 명령어로 스택에 저장된 이전 EBP 값을 EBP 레지스터에 저장
    • 스택과 레지스터의 정리 끝나면 RETN 명령어로 호출한 함수로 되돌아감

3.6.4 지역 변수, 전역 변수, 포인터

지역 변수, 전역 변수

  • 지역 변수, 전역 변수를 사용하는 C언어 코드

    int main()
    {
      int lv;
      int *gv;
        
      
      lv = 1;
      gv = (int*)malloc(0x4);
      *gv = 2;
      
          
      printf("lv is %d\n", lv);
      printf("gv is %d\n", gv);
      return 0;
    }
    
  • 지역 변수, 전역 변수 사용 함수의 어셈블리어

    MOV DWORD PTR SS:[EBP-8], 1
    # 지역변수 1v에 1 대입
      
    PUSH 4
    CALL DWORD PTR DS:[2B92C0]
    #malloc(4)부분으로, 힙 역역 메모리 할당
    # malloc() 함수 호출하면 EAX 레지스터로 할당된 주소 리턴
      
    MOV DWORD PTR SS:[EBP-14], EAX
    MOV EAX, DWORD PTR SS:[EBP-14]
    MOV DWORD PTR DS:[EAX], 2
    # EAX 명령어로 할당된 주소를 스택 [EBP-14] 주소에 저장
    # 2개의 명령어를 사용하여 힙 주소에 2 대입 -> 포인터 변수 gc를 통해 할당된 힙 사용가능
      
    MOV EAX,DWORD PTR SS:[EBP-8]
    # 지역 변수 인자 입력(스택에 저장되어 있는 값 입력)
      
    MOV EAX,DWORD PTR SS:[EBP-14]
    MOV ECX, DWORD PTR DS:[EAX]
    # 스택 변수를 2개의 명령어로 [EBP-14]에 저장되어 있는 힙 주소를 EAX 레지스터로 저장 & EAX 레지스터에 저장된 힙 주소 값을 ECX 레지스터로 저장
      
    

포인터

  • 포인터 C언어

    int inc(int *a)
    {
      *a = *a+1;
      return *a;
    }
      
    int main()
    {
      int s, ret;
      
      s = 2;
      ret = inc(&s);
      printf("%d, %d\n", s, ret);
      
      return 0;
    }
    
  • 포인터 함수 어셈블리어(주소 이용)

    MOV EAX,DWORD PTR SS:[EBP+8]
    # 지역 변수 s의 스택 주조 할당
    MOV ECX,DWORD PTR DS:[EAX]
    # EAX 레지스터 값을 지역 변수 s의 스택 주소로 할당
    ADD ECX,1
    # ECX 레지스터에 2할당
    MOV EDX,DWORD PTR SS:[EBP+8]
    # 지역 변수 S의 스택 주소 구하기
    MOV DWORD PTR DS:[EDX],ECX
    # 구한 스택 주소에 1 더한 값 할당
    MOV EAX,DWORD PTR SS:[EBP+8]
    MOV EAX,DWORD PTR DS:[EAX]
    # 리턴 값 설정