Back to homepage

Introduction

Waze is a great collaborative GPS with realtime traffic and road danger alerting for smartphone.
Fixed speed cameras are also announced by Waze but partialy if you are not exceeding the speed limit (in such case, you get a silent popup without sound alert).

The purpose of this application reverse engineering is to force the audio speed trap alerting even if the current speed is lower than the speed limit.
The target is the Waze application on android market.

Analysis

First thing we notice is that Waze was open-source in a previous version – 2.4 – but this not the case anymore and the current program is the version 3.7.6.0.

Source code of previous version

Getting the source code of this previous version will help us to understand what is the general architecture and where we should focus.
Application is developped in C. This is the most time-consumming part, as there is a lot of code lines, but it is worth the effort.

We find what you are looking for in the file roadmap_alerter.c at line 500, where the importance level of alert is set regarding the speed.

//checks whether an alert is within range static int is_alert_in_range(const RoadMapGpsPosition *gps_position, const PluginLine *line){ [...] if(found_alert){ speed = (* (RoadMapAlertProviders.provider[alert_providor_ind]->get_speed))(alert_index); // if driving speed is over the speed limit - it's an ALERT, otherwise it's a WARNING if ((unsigned int)roadmap_math_to_speed_unit(gps_position->speed) < speed) { the_active_alert.alert_type = WARN; the_active_alert.distance_to_alert = distance; the_active_alert.active_alert_id = (* (RoadMapAlertProviders.provider[alert_providor_ind]->get_id))(alert_index); the_active_alert.alert_provider = alert_providor_ind; if (square_ind != -1) the_active_alert.square = squares[square_ind]; else the_active_alert.square = line->square; roadmap_math_set_context(&context_save_pos, context_save_zoom); return TRUE; } else{ the_active_alert.alert_type = ALERT; [...] }

If we drive under the speed limit, the field alert_type of the alert is set to WARN else it is set to ALERT.
The constants WARN and ALERT are defined at the begining of the file, at line 102:

#define ALERT 1 #define WARN 2

The most interesting part is the function roadmap_alerter_display, at the end of this same file, at line 850:

// Draw the warning on the screen void roadmap_alerter_display(void){ if (alert_should_be_visible) { if (the_active_alert.active_alert_id == -1){ return; } if ((!alert_active) || (prev_alert.active_alert_id != the_active_alert.active_alert_id)){ if (alert_active) hide_alert_dialog(); kill_timer(); prev_alert.active_alert_id = the_active_alert.active_alert_id; show_alert_dialog(); if (the_active_alert.alert_type == ALERT) roadmap_alerter_audio(); alert_active = TRUE; } else{ update_alert(); } } else { if (alert_active && !alert_should_be_visible) { if (AlerterTimerCallback == NULL){ SsdWidget text = ssd_widget_get(dialog, "Distance"); ssd_text_set_text(text, " "); g_seconds = 12; AlerterTimerCallback = hide_alert_timeout; roadmap_main_set_periodic (1000, AlerterTimerCallback); } } } }

If the field alert_type is ALERT, application alerts by audio the driver.

Program retrieving and disassembling

One of the easiest way to retrieve the apk file of Waze is to use a free backup application like App Monster ; this application is able to export apk file of an installed application and able to reinstall one from the SD card.

Once we get the apk archive, we can extract files easily since apk is a signed jar file, jar file which is a specific zip archive.
This is not very surprising as application of Andoid are made in java.

But here, you should wonder this: How a program made in C can be released as an Android application since it works with java?
If you browse the apk file, you will find that there is a lib/ subfolder which means that you can include some dynamic libraries:

This is interesting, it means that all the C code we browsed previously is compiled into a unique core dynamic library named libwaze.so.
We get two versions of this library, depending of the hardware architecture, even if both are binaries compiled for ARM.

If you running under Linux, you may use objdump to disassemble it, but it's not very user friendly for this dead listing step.
I have used the Hopper disassembler ; you can get a demo version, limited to be ran by sessions of 30 minutes (which is very anoying) ; but if you get the full version it does not looks like bad at all.

Let's start with the file armeabi/libwaze.so. You will not find roadmap_alerter_display function in symbols list but alerter_display in this new version.
I have reported some jump/call informations in blue in the listing to help the understanding of the program flow.

alerter_display: 000d7080 10402DE9 push {r4, lr} 000d7084 B4309FE5 ldr r3, = 0x3be9c0 000d7088 03308FE0 add r3, pc, r3 000d708c D425D3E1 ldrsb r2, [r3, #0x54] 000d7090 000052E3 cmp r2, #0x0 000d7094 1E00000A beq 0x6dbbbfd2000d7114 ; conditional jump to 000d7114 (1) 000d7098 5C2093E5 ldr r2, [r3, #0x5c] 000d709c 010072E3 cmn r2, #0x1 000d70a0 1E00000A beq 0x6dbbbfd2000d7120 ; conditional jump to 000d7120 (2) 000d70a4 581093E5 ldr r1, [r3, #0x58] 000d70a8 000051E3 cmp r1, #0x0 000d70ac 0300000A beq 0x6dbbbfd2000d70c0 ; conditional jump to 000d70c0 (3) 000d70b0 903093E5 ldr r3, [r3, #0x90] 000d70b4 030052E1 cmp r2, r3 000d70b8 1E00000A beq 0x6dbbbfd2000d7138 ; conditional jump to 000d7138 (4) 000d70bc 371EFEEB bl 0x6dbbbfd20005e9a0 ; call to waze_ui_details_popup ; conditional jump from 000d70ac (3) 000d70c0 7C409FE5 ldr r4, = 0x3be984 000d70c4 04408FE0 add r4, pc, r4 000d70c8 A40094E5 ldr r0, [r4, #0xa4] 000d70cc 000050E3 cmp r0, #0x0 000d70d0 0200000A beq 0x6dbbbfd2000d70e0 ; conditional jump to 000d70e0 (5) 000d70d4 91FCFDEB bl 0x6dbbbfd200056320 ; call to main_time_interval 000d70d8 0030A0E3 mov r3, #0x0 000d70dc A43084E5 str r3, [r4, #0xa4] ; conditional jump from 000d70d0 (5) 000d70e0 60409FE5 ldr r4, = 0x3be964 000d70e4 04408FE0 add r4, pc, r4 000d70e8 5C3094E5 ldr r3, [r4, #0x5c] 000d70ec 903084E5 str r3, [r4, #0x90] 000d70f0 0AFFFFEB bl 0x6dbbbfd2000d6d20 ; call to editor_point_reset_munching 000d70f4 643094E5 ldr r3, [r4, #0x64] ; r3 = alert_type 000d70f8 010053E3 cmp r3, #0x1 ; if (r3 == ALERT) 000d70fc 0B00000A beq 0x6dbbbfd2000d7130 ; conditional jump to 000d7130 (6) - then jump to (6) ; jump from 000d7134 (8) 000d7100 44309FE5 ldr r3, = 0x3be940 000d7104 0120A0E3 mov r2, #0x1 000d7108 03308FE0 add r3, pc, r3 000d710c 582083E5 str r2, [r3, #0x58] 000d7110 1080BDE8 pop {r4, pc} ; conditional jump from 000d7094 (1) 000d7114 583093E5 ldr r3, [r3, #0x58] 000d7118 000053E3 cmp r3, #0x0 000d711c 0000001A bne 0x6dbbbfd2000d7124 ; conditional jump to 000d7124 (7) ; conditional jump from 000d70a0 (2) 000d7120 1080BDE8 pop {r4, pc} ; conditional jump from 000d711c (7) 000d7124 0F00A0E3 mov r0, #0xf 000d7128 1040BDE8 pop {r4, lr} 000d712c 0B1EFEEA b 0x6dbbbfd20005e960 ; jump to waze_ui_alerter_popup_set_close_counter ; conditional jump from 000d70fc (6) – we want to go there 000d7130 5AFEFFEB bl 0x6dbbbfd2000d6aa0 ; call to alerter_audio 000d7134 F1FFFFEA b 0x6dbbbfd2000d7100 ; jump to 000d7100 (8) ; conditional jump from 000d70b8 (4) 000d7138 1040BDE8 pop {r4, lr} 000d713c 87FFFFEA b 0x6dbbbfd2000d6f60 ; jump to update_alert

And for the file armeabi-v7a/libwaze.so:

alerter_display: 000d4b00 B4309FE5 ldr r3, = 0x3b0f40 000d4b04 10402DE9 push {r4, lr} 000d4b08 03308FE0 add r3, pc, r3 000d4b0c D425D3E1 ldrsb r2, [r3, #0x54] 000d4b10 000052E3 cmp r2, #0x0 000d4b14 1E00000A beq 0x6dbbbfd2000d4b94 ; conditional jump to 000d4b94 (1) 000d4b18 5C2093E5 ldr r2, [r3, #0x5c] 000d4b1c 010072E3 cmn r2, #0x1 000d4b20 1080BD08 popeq {r4, pc} 000d4b24 581093E5 ldr r1, [r3, #0x58] 000d4b28 000051E3 cmp r1, #0x0 000d4b2c 0300000A beq 0x6dbbbfd2000d4b40 ; conditional jump to 000d4b40 (2) 000d4b30 903093E5 ldr r3, [r3, #0x90] 000d4b34 030052E1 cmp r2, r3 000d4b38 1D00000A beq 0x6dbbbfd2000d4bb4 ; conditional jump to 000d4bb4 (3) 000d4b3c 2F26FEEB bl 0x6dbbbfd20005e400 ; call to waze_ui_alerter_popup_hide ; conditional jump from 000d4b2c (2) 000d4b40 78409FE5 ldr r4, = 0x3b0f04 000d4b44 04408FE0 add r4, pc, r4 000d4b48 A40094E5 ldr r0, [r4, #0xa4] 000d4b4c 000050E3 cmp r0, #0x0 000d4b50 0200000A beq 0x6dbbbfd2000d4b60 ; conditional jump to 000d4b60 (4) 000d4b54 E904FEEB bl 0x6dbbbfd200055f00 ; call to main_remove_periodic 000d4b58 0030A0E3 mov r3, #0x0 000d4b5c A43084E5 str r3, [r4, #0xa4] ; conditional jump from 000d4b50 (4) 000d4b60 5C409FE5 ldr r4, = 0x3b0ee4 000d4b64 04408FE0 add r4, pc, r4 000d4b68 5C3094E5 ldr r3, [r4, #0x5c] 000d4b6c 903084E5 str r3, [r4, #0x90] 000d4b70 12FFFFEB bl 0x6dbbbfd2000d47c0 ; call to show_alert_dialog 000d4b74 643094E5 ldr r3, [r4, #0x64] ; r3 = alert_type 000d4b78 010053E3 cmp r3, #0x1 ; if (r3 == ALERT) 000d4b7c 0A00000A beq 0x6dbbbfd2000d4bac ; conditional jump to 000d4bac (5) – then jump to (5) ; jump from 000d4bb0 (6) 000d4b80 40309FE5 ldr r3, = 0x3b0ec0 000d4b84 0120A0E3 mov r2, #0x1 000d4b88 03308FE0 add r3, pc, r3 000d4b8c 582083E5 str r2, [r3, #0x58] 000d4b90 1080BDE8 pop {r4, pc} ; conditional jump from 000d4b14 (1) 000d4b94 583093E5 ldr r3, [r3, #0x58] 000d4b98 000053E3 cmp r3, #0x0 000d4b9c 1080BD08 popeq {r4, pc} 000d4ba0 0F00A0E3 mov r0, #0xf 000d4ba4 1040BDE8 pop {r4, lr} 000d4ba8 0426FEEA b 0x6dbbbfd20005e3c0 ; jump to waze_ui_alerter_popup_set_close_counter ; conditional jump from 000d4b7c (5) – we want to go there 000d4bac 63FEFFEB bl 0x6dbbbfd2000d4540 ; call to alerter_audio 000d4bb0 F2FFFFEA b 0x6dbbbfd2000d4b80 ; jump to 000d4b80 (6) ; conditional jump from 000d4b38 (3) 000d4bb4 1040BDE8 pop {r4, lr} 000d4bb8 88FFFFEA b 0x6dbbbfd2000d49e0 ; jump to update_alert

Program change

libwaze libraries modificiation

What I propose is to simply turn the conditionnal jump (@000d4b7c / @000d70fc) into unconditional one.

Regarding ARM opcode, the differences with the 8086 opcode is basically the constant instruction size (4 bytes) and the fact that the entire instruction is stored in little endian.
The branch (jump/conditional jump/call) instruction is opcoded as the bellow:

76543210 15141312111098 2322212019181716 3130292827262524
@@@@ @@@@ @@@@ @@@@ @@@@ @@@@ CCCC 101L

Bit field description:

Relative address – @Signed value. Add 2 and multiply by 4 to this value to get bytes to add to current instruction.
Link – LReturn address stored: 1 for subroutine call (bl), 0 otherwise
Instruction – 101Always 101 for branch
Condition – CCondition code of branch:
0000BEQEqual0001BNENon equal
0010BCSCarry set0011BCCCarry clear
0100BMIMinus0101BPLPlus
0110BVSOverflow set0111BVCOverflow clear
1000BHIHigher1001BLSLower or same
1010BGEGreater or equal1011BLTLess than
1100BGTGreater than1101BLELess than or equal
1110BALAlways1111BNVNever

We will change a beq instruction (conditional equal branch instruction) to become a b instruction (unconditional branch).

beq
0000 1010
b
1110 1010

The both instructions modified in armeabi/libwaze.so and armeabi-v7a/libwase.so becomes respectively:

000d70fc 0B0000EA b 0x6dbbbfd2000d7130

000d4b7c 0A0000EA b 0x6dbbbfd2000d4bac

The modification with hexadecimal editor is easy, no need to convert instruction offset into file offset, there are identical.

Application package rebuilding and reinstall

As we seen before, the apk file is a specific apk archive with some special things in it.
The file META-INF/MANIFEST.MF is for example special in the archive, so this file must not be changed with a zip editor. You also have two signatures file in the directory META-INF/.

With your zip editor, edit apk archive and replace files /lib/armeabi/libwaze.so and /lib/armeabi-v7a/libwase.so with modified ones.
Delete also signature files META-INF/WAZEANDR.DSA and META-INF/WAZEANDR.SF

The last step is to resign the application archive ; I am afraid we need the java developpement evil kit, with java bin directory set in the path environment variable.
The good news is that the bellow works on Windows and on Linux systems.

We generate a key valid for a long time:
$ keytool -genkey -keystore wazeandr -alias wazeandr -validity 4000 -dname "CN=Andoid Waze,OU=Waze,O=Google Inc."

Then, we sign the application:
$ jarsigner -keystore wazeandr -digestalg SHA1 waze_modified.apk wazeandr

With this command, you can check the archive signature:
$ jarsigner -verify -certs waze_modified.apk
You should see expression jar verified and probably a warning regarding the nonexistence of a complete certification chain.

The backup application we used to retrieve .apk file will does the job to deploy this modified application instead of official one.
Do not forget to backup the waze/ directory first and then uninstall the official application before installing the new one.

After installation of modified application, you can restore the waze/ directory over the new one to recover settings after reinstallation (I guess it works, I forgot to do a backup on my side).
Note: you can still recover your favorites from Waze server in case of data loss : after logged-in with your account, go to Navigate, type the magic code ##@restorefav as destination and run the search.

Functional test

Well, here you just a car or a bike, and to go on a road with a fixed speed trap. Even if you are driving slowly you should get the audio alert
Bingo! Now you can be alerted at any time and slow down. Drive safe!

Warning: In some very specific countries, this application is not legal (like in France, you may lose you driver license, your phone and have to pay 1500 € as fine).

Further version

For newest version of Waze, make sure to enable first radar alarms alerts by modifying the configuration file of Waze (thank you Aurélien for detail of this information):
GeoConfig.Ignore server config: yes
Alerts.Enable Enforcement Alerts: yes
contact/mail protection