RfidResearchGroup / proxmark3

Iceman Fork - Proxmark3
http://www.icedev.se
GNU General Public License v3.0
4.07k stars 1.07k forks source link

hf mf hardnested causes SIGSEGV on Termux #1508

Closed NathanielJS1541 closed 2 years ago

NathanielJS1541 commented 3 years ago

Describe the bug When building the client from source on Termux, hf mf hardnested causes a SIGSEGV, but when it is compiled with make client DEBUG=1, it does not cause the same issue. I think it's because when DEBUG=1 is specified, the -O0 flag is included. I'd guess that the compiler optimises something out that causes the segmentation fault. I presume this is what's causing the issues with hf mf autopwn for some Termux users too

To Reproduce

  1. cd proxmark3
  2. git pull
  3. make clean && make client
  4. client/proxmark3 tcp:loaclhost:1234
  5. hf mf hardnested --blk 0 -a -k FFFFFFFFFFFF --tblk 60 --ta
  6. Eventually during the attack, the following or similar will happen: image

Expected behavior The attack should complete the same as it does on a PC.

Screenshots Running the compiled proxmark3 client with gdb to show more about the error: image After compiling with DEBUG=1, the attack runs correctly: image

Desktop (please complete the following information):

NathanielJS1541 commented 3 years ago

I managed to get gdb to work properly and display line numbers etc:

Thread 1 "proxmark3" received signal SIGSEGV, Segmentation fault.
0x00000055556de4f8 in count_bitarray_AND_NOSIMD (A=0xb400007ca5371040, B=0x0)
    at hardnested_bitarray_core.c:212
212             A[i] &= B[i];
(gdb) bt
#0  0x00000055556de4f8 in count_bitarray_AND_NOSIMD (A=0xb400007ca5371040, B=0x0)
    at hardnested_bitarray_core.c:212
#1  0x000000555560a708 in apply_sum_a0 () at src/cmdhfmfhard.c:1287
#2  acquire_nonces (blockNo=<optimized out>, keyType=<optimized out>, key=<optimized out>,
    trgBlockNo=<optimized out>, trgKeyType=<optimized out>, nonce_file_write=false, slow=<optimized out>,
    filename=0x7fffffe39c "") at src/cmdhfmfhard.c:1491
#3  mfnestedhard (blockNo=<optimized out>, blockNo@entry=0 '\000', keyType=<optimized out>,
    keyType@entry=0 '\000', key=key@entry=0x7fffffe7ac "\377\377\377\377\377\377\377\377\006",
    trgBlockNo=<optimized out>, trgBlockNo@entry=60 '<', trgKeyType=<optimized out>,
    trgKeyType@entry=0 '\000', trgkey=<optimized out>, nonce_file_read=<optimized out>,
    nonce_file_write=false, slow=<optimized out>, tests=0, foundkey=0x7fffffe390, filename=0x7fffffe39c "")
    at src/cmdhfmfhard.c:2380
#4  0x00000055555ea2bc in CmdHF14AMfNestedHard (Cmd=<optimized out>) at src/cmdhfmf.c:1788
#5  0x000000555565f3e0 in CmdsParse (Commands=0x555581f688 <CommandTable>,
    Cmd=0xb400007d8daa31a6 "hardnested --blk 0 -a -k ", 'f' <repeats 12 times>, " --tblk 60 --ta")
    at src/cmdparser.c:297
#6  0x000000555565f3e0 in CmdsParse (Commands=0x555581cac0 <CommandTable>,
    Cmd=0xb400007d8daa31a3 "mf hardnested --blk 0 -a -k ", 'f' <repeats 12 times>, " --tblk 60 --ta")
    at src/cmdparser.c:297
#7  0x000000555565f3e0 in CmdsParse (Commands=0x5555823240 <CommandTable>,
    Cmd=Cmd@entry=0xb400007d8daa31a0 "hf mf hardnested --blk 0 -a -k ", 'f' <repeats 12 times>, " --tblk 60 --ta") at src/cmdparser.c:297
#8  0x000000555565dad8 in CommandReceived (Cmd=0x0,
    Cmd@entry=0xb400007d8daa31a0 "hf mf hardnested --blk 0 -a -k ", 'f' <repeats 12 times>, " --tblk 60 --ta") at src/cmdmain.c:348
#9  0x00000055556ac83c in main_loop (script_cmds_file=<optimized out>, script_cmds_file@entry=0x0,
    script_cmd=0xb400007d8daa359b "ated", script_cmd@entry=0x0, stayInCommandLoop=<optimized out>)
    at src/proxmark3.c:462
#10 0x00000055556ada48 in main (argc=<optimized out>, argv=<optimized out>) at src/proxmark3.c:1110

Following this back, it appears that the variable first_byte_sum is set to 132 in cmdhfmfhard.c, but the array which it is used as an index for is 2x19. This results in a value for B at addres 0x00 being passed to count_bitarray_AND_NOSIMD and causing the SIGSEGV it looks like. I'll keep digging.

iceman1001 commented 3 years ago

first_byte_sum == 132...
used as index in a arr with size of 1<<19 elements. Index 132 shouldn't be any issue, its almost like the arr wasn't initialized properly.

or termux doesn't align the memory when initialized. __builtin_assume_aligned is heavily used in hardnested_bitarray_core.c

iceman1001 commented 3 years ago

Its a interesting bug though.

NathanielJS1541 commented 3 years ago

Ok a little more progress on the issue. It looks like it's something to do with first_byte_Sum since without compiler optimisations it has a value of < 19 at the point where it is used as an index for sum_a0_bitarrays, and with them it has a value of over 100. The array sum_a0_bitarrays appears to be the same size in both cases so I don't think it's a problem with the initialisation there.

Here is a gdb output without compiler optimisations:

Thread 1 "proxmark3" hit Breakpoint 1, acquire_nonces (blockNo=0 '\000', keyType=0 '\000', key=0x7fffffe324 "\377\377\377\377\377\377\020@\006", trgBlockNo=60 '<', trgKeyType=0 '\000', nonce_file_write=false, slow=false,
    filename=0x7fffffdf14 "") at src/cmdhfmfhard.c:1490
1490                        hardnested_stage |= CHECK_2ND_BYTES;
(gdb) s
1491                        apply_sum_a0();
(gdb) s
apply_sum_a0 () at src/cmdhfmfhard.c:1286
1286        uint32_t old_count = num_all_bitflips_bitarray[EVEN_STATE];
(gdb) p old_count
$1 = 1
(gdb) p first_byte_Sum
$2 = 7
(gdb) p all_bitflips_bitarray[EVEN_STATE]
$4 = (uint32_t *) 0xb400007ca5391040
(gdb) p *all_bitflips_bitarray[EVEN_STATE]
$5 = 4294967295
(gdb) p sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]
$6 = (uint32_t *) 0xb400007ca83d9040
(gdb) p *sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]
$7 = 4294967295
(gdb) p sum_a0_bitarrays[EVEN_STATE]
$9 = {0xb400007caa003040, 0xb400007ca9bfd040, 0xb400007ca97f7040, 0xb400007ca93f1040, 0xb400007ca8feb040, 0xb400007ca8be5040, 0xb400007ca87df040, 0xb400007ca83d9040, 0xb400007ca7fd3040, 0xb400007ca7bcd040, 0xb400007ca77c7040,
  0xb400007ca73c1040, 0xb400007ca6fbb040, 0xb400007ca6bb5040, 0xb400007ca67af040, 0xb400007ca63a9040, 0xb400007ca5fa3040, 0xb400007ca5b9d040, 0xb400007ca5797040}
(gdb)

And here's one at the same point with compiler optimisations enabled:

Thread 1 "proxmark3" hit Breakpoint 1, acquire_nonces (blockNo=<optimized out>, keyType=<optimized out>, key=<optimized out>, trgBlockNo=<optimized out>, trgKeyType=<optimized out>, nonce_file_write=false, slow=<optimized out>,
    filename=0x7fffffe2cc "") at src/cmdhfmfhard.c:1490
1490                        hardnested_stage |= CHECK_2ND_BYTES;
(gdb) s
1491                        apply_sum_a0();
(gdb) s
apply_sum_a0 () at src/cmdhfmfhard.c:1287
1287        num_all_bitflips_bitarray[EVEN_STATE] = count_bitarray_AND(all_bitflips_bitarray[EVEN_STATE], sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]);
(gdb) p all_bitflips_bitarray[EVEN_STATE]
$4 = (uint32_t *) 0xb400007ca537c040
(gdb) p *all_bitflips_bitarray[EVEN_STATE]
$5 = 4294967295
(gdb) p sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]
$6 = (uint32_t *) 0x0
(gdb) p first_byte_Sum
$7 = 125
(gdb) p sum_a0_bitarrays[EVEN_STATE]
$8 = {0xb400007ca9fee040, 0xb400007ca9be8040, 0xb400007ca97e2040, 0xb400007ca93dc040, 0xb400007ca8fd6040, 0xb400007ca8bd0040, 0xb400007ca87ca040, 0xb400007ca83c4040, 0xb400007ca7fbe040, 0xb400007ca7bb8040, 0xb400007ca77b2040,
  0xb400007ca73ac040, 0xb400007ca6fa6040, 0xb400007ca6ba0040, 0xb400007ca679a040, 0xb400007ca6394040, 0xb400007ca5f8e040, 0xb400007ca5b88040, 0xb400007ca5782040}
(gdb)

As you can see there's also a lot of stuff optimised out, including old_count etc. I'm not too familiar with this codebase so I'll try and keep digging but please let me know if you see anything that stands out as weird here.

NathanielJS1541 commented 3 years ago

Ok I'm not sure whether I'm interpreting this right, but I've found something weird going on with lines 1483 to 1492 of cmdhfmfhard.c (I think anyway). Here are the lines of code in question:

if (hardnested_stage == CHECK_1ST_BYTES) {
    for (uint8_t i = 0; i < NUM_SUMS; i++) {
        if (first_byte_Sum == sums[i]) {
            first_byte_Sum = i;
            break;
        }
    }
    hardnested_stage |= CHECK_2ND_BYTES;
    apply_sum_a0();
}

With the compiler optimisations, here is the gdb info I've gathered:

Thread 1 "proxmark3" hit Breakpoint 11, acquire_nonces (blockNo=<optimized out>, keyType=<optimized out>, key=<optimized out>, trgBlockNo=<optimized out>, trgKeyType=<optimized out>, nonce_file_write=false, slow=<optimized out>,
    filename=0x7fffffe2cc "") at src/cmdhfmfhard.c:1485
1485                            if (first_byte_Sum == sums[i]) {
(gdb) p first_byte_Sum
$49 = 124
(gdb) p sums
$50 = {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}
(gdb) p sums[i]
$51 = 0
(gdb) s
0x000000555560a6e0 in apply_sum_a0 () at src/cmdhfmfhard.c:1287
1287        num_all_bitflips_bitarray[EVEN_STATE] = count_bitarray_AND(all_bitflips_bitarray[EVEN_STATE], sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]);
(gdb) p first_byte_Sum
$52 = 124
(gdb) s
1286        uint32_t old_count = num_all_bitflips_bitarray[EVEN_STATE];
(gdb) s

Thread 1 "proxmark3" hit Breakpoint 13, acquire_nonces (blockNo=<optimized out>, keyType=<optimized out>, key=<optimized out>, trgBlockNo=<optimized out>, trgKeyType=<optimized out>, nonce_file_write=false, slow=<optimized out>,
    filename=0x7fffffe2cc "") at src/cmdhfmfhard.c:1490
1490                        hardnested_stage |= CHECK_2ND_BYTES;
(gdb) s
1491                        apply_sum_a0();
(gdb) s
apply_sum_a0 () at src/cmdhfmfhard.c:1287
1287        num_all_bitflips_bitarray[EVEN_STATE] = count_bitarray_AND(all_bitflips_bitarray[EVEN_STATE], sum_a0_bitarrays[EVEN_STATE][first_byte_Sum]);
(gdb)

First off, if first_byte_sum is not in sums[] (as it isn't here), this loop won't do anything. It seems like it's intended to convert a number to a valid index for sum_a0_bitarrays, but if the number in first_byte_sum is not contained within sums[], the number will simply remain the same and provide an invalid index for sum_a0_bitarrays, which is what I believe is causing the SIGSEGV fault.

The thing that's confusing me is that by stepping through after if (first_byte_Sum == sums[i]) {, instead of being able to step through the loop, the apply_sum_a0 function, I can then step through lines 1287 and 1286 (I'm presuming they're in reverse order due to compiler optimisations), but then the next step takes me back to line 1490, and then 1491 which is the call to the apply_sum_a0 function?? Is this some inlining optimisation or something?

I'll be honest I'm way out of my depth with this one, so please tell me if any conclusions I come to are completely stupid. But from what I can tell this might be at least part of the problem?

iceman1001 commented 3 years ago

you are right, there are no logic for when the first byte sum doesn't exists in sums still doing second byte check regardless.. I wonder what happens if you break if not found, not doing the apply_sum_a0 call..

iceman1001 commented 2 years ago

interesting, the 132 is not one of the possible sums. And the attack should reset or something else, but it continues and messes up

iceman1001 commented 2 years ago

ok, since we don't know anymore but there is a check added in order to make the segfault to trigger. It doesn't solve the underlaying issue on that hardware but when someone finds more details about it they can make a new issue / PR or comment in this one and we can reopen it.