aboutsummaryrefslogtreecommitdiff
path: root/CamilStaps-s4498062-Assignment-3/ex3
blob: d1da7dc173a70d9628a5d2d5e8bcd014b08a28ed (plain) (blame)
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
a
    $ ./auth0 $(python -c 'print "A"*512')
    ERROR: password incorrect
    Segmentation fault

b
    With optimisation, `correct` isn't really a variable anymore. The 
    compiler recognises the moments it can change (i.e. at definition time
    and after the strcmp), and when it is used (the last if), and manages
    to do the same without the variable.

    It now uses code similar to the following control flow:

        if (strcmp(hash1,hash2) == 0) {
            printf("Starting root shell\n");
            setuid(0);
            setgid(0);
            system("/bin/sh");
        } else {
            printf("ERROR: password incorrect\n");
        }

    So, `correct` has been eliminated. Therefore, the buffer overflow
    attack doesn't work anymore: there is no variable to overwrite.

    We can see this in auth3.s. The `bool correct = false;` statement doesn't
    translate to anything in assembly, while it does in auth0.s.

    More interestingly, we can see the control flow:

          19:auth.c        ****     if (strcmp(hash1,hash2) == 0) {
          99                    .loc 1 19 0
         100 001f BF000000      mov edi, OFFSET FLAT:.LC1   # tmp90,
         100      00
         101 0024 B96B0000      mov ecx, 107    # tmp91,
         101      00
         102 0029 4889C6        mov rsi, rax    # hash1, hash1
         103                .LBE7:
         104 002c F3A6          repz cmpsb
         105                .LVL4:
         106 002e 7530          jne .L6 #,
         107                .LVL5:
         108                .LBB8:
         109                .LBB9:
          20:auth.c        ****         correct = true;
          21:auth.c        ****     } else {
          22:auth.c        ****         printf("ERROR: password incorrect\n");
          23:auth.c        ****     }
          24:auth.c        ****
          25:auth.c        ****     if (correct) {
          26:auth.c        ****         printf("Starting root shell\n");
         110                    .loc 1 26 0
         111 0030 BF000000      mov edi, OFFSET FLAT:.LC3   #,
         111      00
         112 0035 E8000000      call    puts    #
         112      00
         113                .LVL6:
          27:auth.c        ****         setuid(0);
         114                    .loc 1 27 0
         115 003a 31FF          xor edi, edi    #
         116 003c E8000000      call    setuid  #
         116      00

    The jump in 106 goes to .L6 where the error printf assembly is stored:

          22:auth.c        ****     }
         138                    .loc 1 22 0
         139 0060 BF000000      mov edi, OFFSET FLAT:.LC2   #,
         139      00
         140 0065 E8000000      call    puts    #
         140      00

    If we don't jump (i.e. if the result from strcmp was 0), we continue in
    .LVL5, .LBB8, .LBB9 where we printf "Starting root shell" and after do
    the set{u,g}id stuff and start the root shell.

    In auth0.s we see a control flow that stays much closer to the C code.
    There is an actual variable `correct` which is set if the strcmp returns 0:

         19:auth.c        ****     if (strcmp(hash1,hash2) == 0) {
          76                    .loc 1 19 0
          77 004f 488B55E8      mov rdx, QWORD PTR [rbp-24] # tmp88, hash2
          78 0053 488B45F0      mov rax, QWORD PTR [rbp-16] # tmp89, hash1
          79 0057 4889D6        mov rsi, rdx    #, tmp88
          80 005a 4889C7        mov rdi, rax    #, tmp89
          81 005d E8000000      call    strcmp  #
          81      00
          82 0062 85C0          test    eax, eax    # D.3514
          83 0064 7506          jne .L2 #,
          20:auth.c        ****         correct = true;
          84                    .loc 1 20 0
          85 0066 C645FF01      mov BYTE PTR [rbp-1], 1 # correct,

    Finally: the compiler changes the control flow because it is more efficient.
    In this case, I think, it is both timewise and spacewise more efficient: we
    store one bool less, and also don't lose time in setting and checking its
    value.