donderdag 17 april 2014

Android and Proguard

I wanted to write a prologue here, on the motivation for this blog post. For some reason, I couldn't formulate it in a way that we would call "politically correct".

To give you some context: in the last months, I've been developing a high-profile app for a governmental institution, during which I needed to do quite some R&D on the do's-and-don'ts of mobile development. So I took my Android phone and connected it to my computer, and found to my surprise that some of the apps where spawning results from REST services to their logcat, which included confidential information.

Astonished I looked a bit further, how easy would it be to fetch the application from a non-rooted device. Quite easy so it seemed.
Would I be able access the "internals" of the applications? Yes I was! Some of the hybrid solutions (Cordova) were even easier to look at! I had direct access to Javascript and HTML source files. Wow! So it should be more difficult for Native Android applications, right? Wrong!

"WTF?!" was about the only thing that keept going through my mind.
How could we be so sloppy in developing Android applications!
Are we ignorant of the available tools?
Or are we just lazy?
Or are we not specialised enough?

Catching my drift here? So it was quite difficult to write an intro here.

This post will try to provide some insights in decompiling an APK file and securing your app using proguard.

PS/ This is not the Holy Grail of app security. It helps you in delivering higher quality apps. But it is still up to you to use your common sense. If it needs to be secure, or the information is sensitive: don't store it. Period. That's my advice.

An insight on the APK

When you install an Android application from Google Play, you download an APK file. This APK contains the assets (images, layouts, etc) and code (DEX-file) required for your application to run on your Android Smartphone.

An APK is "no more" than a ZIP file constructed using your favourite IDE (e.g. Android Studio & Gradle) which respects a certain structure. Some believe that you need a Jailbroken or Rooted device to download the APK, but that's actually not the case. The Android SDK is shipped with the required tools to download the APK from your mobile device.

One does not simply download an APK

Actually, you do.
  • First, connect your smart device to your computer, and make sure that you've enabled the developer option on the smartphone (no, that's not rooting)
  • Once connected, list the installed packages on your phone
  • Look for the package you need and check the install location
  • "Pull" the APK from the install location

Show me the packages!

Start a Terminal window and go to the "platform-tools" folder (if it's not in your path, that is). Connect to your smartphone using the "adb shell" command.

$ adb shell pm list packages

The result will be a list of all the installed packages on your smart device, e.g:

package:com.imdb.mobile
...
package:com.google.android.apps.books
package:com.google.android.videos
package:com.lge.update
package:be.acuzio.foobar
package:com.google.android.inputmethod.pinyin
...
package:com.google.android.syncadapters.contacts
package:com.google.android.backup


O Package, where art thou?
Let's say that we are interested in the package "be.acuzio.foobar" and we want to know the path:

$ adb shell pm path be.acuzio.foobar
package:/data/app/be.acuzio.foobar-1.apk

Excellent! Next, let's pull the APK from the location /data/app/be.acuzio.foobar-1.apk


Download the APK
$ adb pull /data/app/be.acuzio.foobar-1.apk
4551 KB/s (6484838 bytes in 1.391s)

$ ls -alh
total 16920
drwxr-xr-x   9 vandekr  staff   306B Feb 10 20:05 .
drwxr-x---@ 14 vandekr  staff   476B Jan 21 10:24 ..
...
-rw-r--r--   1 vandekr  staff   6.2M Feb 10 20:05 be.acuzio.foobar-1.apk
...

Eh, voila, you are now the proud owner of the zip APK file.

Mastering the ZIP

Unzipping the APK is one thing you can do to check the internals of the app. If the app was developed as a native app, you can use the dex2jar and JD-GUI tools to have a look at the code.

Dex-de-compilation
You can use the dex2jar tool to extract the DEX File from the APK and convert it to a JAR (which we need for JD-GUI)

$ d2j-dex2jar.sh -f -o ~/Desktop/output_jar.jar ~/Desktop/be.acuzio.foobar-1.apk

Look at the code
Start the JD-GUI tool and open the created "output_jar.jar" from your desktop.



Now that looks like readable code to me. Let's have a look at the actual code.

package be.acuzio.foobar;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends ActionBarActivity {
    private final static String TAG = MainActivity.class.getSimpleName();

    private Button btnLogin = null;
    private EditText etUsername = null;
    private EditText etPassword = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate was called");

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.etPassword = (EditText) this.findViewById(R.id.et_password);
        this.etUsername = (EditText) this.findViewById(R.id.et_username);
        this.btnLogin = (Button) this.findViewById(R.id.btn_login);

        this.btnLogin.setOnClickListener(new LoginOnClickListener());
    }


    class LoginOnClickListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            Log.d(TAG, "onClick was triggered for Login");

            String username = etUsername.getText().toString();
            String password = etPassword.getText().toString();

            Log.d(TAG, String.format("  > Username: %1$s", username));
            Log.d(TAG, String.format("  > hashing password: %1$s", password));
        }
    }

}


Even without much effort, you can read and understand the decompiled code. It's not even that much different from the actual code.

So let us talk  about what we can do to make it more difficult to decompile and understand.

Note: it's a way to optimize your code and avoid data leaks via logging. By no means what so ever is it an encryption of values. A stubborn developer with enough time on his/her hands will still be able to figure your code, even when it is obfuscated.


Proguard

Proguard was chosen by Google/Android as the preferred framework for code obfuscation and mimification. Proguard is not the only framework available for obfuscation: a more complex (and commercial) version is DexGuard.

When you develop an Android application, you might want to consider the following:

  • During development and (unit) testing, you added logging to your application. This logging might have confidential information: data consumed from remote services, usernames, contents of arrays, etc. This data dumping is the primary source of information leaks! You just need to connect your Android device to your computer and look at the rendered logcat to see what is spawned by the different applications (PS/ we assume the usage of the standard Android Logging);
  • You use libraries to facilitate your development. Sometimes you just need a small portions (e.g.  Bouncy Castle encryption, Apache Commons libs, Custom JAR files, etc) of the provided libraries.
  • During development, you might have URL's, passwords, hashes etc stored clear text in your application to secure the communication between the app and the remote service.
  • You've worked very hard (for a client), and you're not snappy on the idea of sharing your code with the world.

Although not perfect, Proguard will help you in the above points, resulting in an app that is:
  • Not leaking information via Logging and cleaning up your code;
  • Packaging the code you use and removing the libraries or classes that are solely there as dead-weight;
  • Obfuscate your code, making it harder to decompile and understand what you did

A Proguard example

Proguard is not the easiest framework to master. So a generic proguard file is already provided by your Android SDK. It might resemble a set-up like this one:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose


# Keep a fixed source file attribute and all line number tables to get line
# numbers in the stack traces.
# You can comment this out if you're not interested in stack traces.

-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

# keep annotations
-keepattributes *Annotation*

# keep the licencing service classes from Google
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

#############################################################################
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# an example if you don't want to be warned about missing libraries
# -dontwarn javax.naming.**

#############################################################################
# remove logging, note that this removes ALL logging, including the 
# Log.e statements
-assumenosideeffects class android.util.Log {
    *;
}

# an example on how to keep an entire package
# -keep class com.google.zxing.**

#############################################################################
# repackage and optimize
-repackageclasses ""
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify

Update the Gradle build file

Having a proguard file is just one thing, you need to activate it during your build. I do suggest that you add a proguard file to your debug assembly: because of its intrusive nature, you really need to test your app after the obfuscation.

apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        debug {
            runProguard true
            proguardFiles 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile fileTree(dir: 'libs', include: ['*.jar'])
}


If you deploy the app to a device or run the assembleDebug command, a new APK will be built using the proguard settings.

Assembling a debug APK using gradle is a simple as running:

$ ./gradlew assembleDebug


The .apk will be stored in the "build/apk" folder of your app project.


Have a look now


Now that already looks a bit better!

  • The logging is removed from the class;
  • The names are obfuscated, but the methods and classes that are required by the AndroidManifest or the Android ecosystem are kept (e.g. onCreate). 
  • The packages are flattened and renamed, so happy hunting if you have a complex project!


donderdag 10 april 2014

Change your MySQL datafolder

When you use MySQL, it defaults its data files to /var/lib/mysql. In case you have a separate data disk mounted (e.g. /data). You might want to change the data folder used by MySQL. You can change the folder if you follow these steps:

Stop your mysql instance:

$ sudo service mysql stop

Create the datadir folder, eg. /data/mysql/data

$ mkdir -p /data/mysql/data

Change the owner of the data folder

$ sudo chown -R mysql:mysql /data/mysql/data

Copy the existing data from the /var/lib/mysql folder to /data/mysql/data

$ sudo cp -a /var/lib/mysql/* /data/mysql/data/

Note: this would copy a directory and retain the permissions of the copied folder

$ sudo cp -rp /var/lib/mysql/* /data/mysql/data/


Edit the MySQL config file (my.cnf) and update the datadir configuration

$ sudo vi /etc/mysql/my.cnf
 
...
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
# datadir = /var/lib/mysql
datadir = /data/mysql/data
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
...

AppArmor is not keen on changes. So make sure that it allows you to change the datadir of MySQL

$ sudo vi /etc/apparmor.d/usr.sbin.mysqld
 
# vim:syntax=apparmor
 
# Last Modified: Tue Jun 19 17:37:30 2007
#include 
 
/usr/sbin/mysqld {
...
 
/var/lib/mysql/ r,
/var/lib/mysql/** rwk,
/var/log/mysql/ r,
/var/log/mysql/* rw,
...
/run/mysqld/mysqld.sock w,
/data/mysql/data/ r,
/data/mysql/data/** rwk,
 
...
}

Add the configuration of the datadir at the bottom (in our case /data/mysql/data/)


Restart both apparmor and mysql

$ sudo service apparmor restart
* Reloading AppArmor profiles Skipping profile in /etc/apparmor.d/disable: usr.sbin.rsyslogd
[ OK ]
$ sudo service mysql restart
mysql stop/waiting
mysql start/running, process 1532

And now you are done.

vrijdag 4 april 2014

A 10-step guide in setting up Push Notifications in XCode5/iOS7

Prerequisites

We are assuming that:

  • You have XCode5 installed; 
  • At least know your way around XCode; 
  • Have a developer account at Apple.

Introduction

This document describes the different steps to take when setting up an iOS app with Remote Notifications. The detailed information about sending Remote Notifications can be found here:

https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html.

The summary:

  • You need to generate a certificate that is tightly linked with your app. With the certificate the APNS servers know where to send the messages to, and if you are allowed to do so; 
  • The certificates open an SSL connection over which a payload is sent. 
  • The payload contains a JSON encoded message, which at least should have a body like:
    {
        aps =     {
            alert = "FooBarPushDemo test!";
        };
    }
    
  • The APS key in the payload is mandatory, other keys can be added (e.g. foobar to store additional information)
    {
        aps =     {
            alert = "FooBarPushDemo test!";
        };
        foobar =     {
            some = "data";
        };
    
    }

Step 1: Generate a Certificate Signing Request (CSR)

  • Open the Keychain Access application on your Mac; 
  • Open the Keychain Access menu, and choose Certificate Assistant > Request a Certificate from a Certificate Authority 


  • Enter the following information: 
    • User E-mail Address: your email address;
    • Common name: the name of the application, e.g. FooBarPushDemo
    • Select the option to save the CSR to disk (save it to a specific folder as you will need to store more files there, e.g. ~/Desktop/FooBarPushDemo and name it FooBarPushDemo.certSigningRequest 
  • Close the window after completion, but stay in the Keychain Access application

Step 2: export the private key

  • In the Keychain Access application, select the newly created Private Key  
  • Right-click (or ctrl-click) it and select Export “FooBarPushDemo” 
  • Name the file FooBarPushDemoKey.p12 and save it to the same location as your CSR file; 
  • You will be prompted for a password (passphrase) to protect the exported key. Choose “foobar” as passphrase 
  • Next you will be prompted for your Mac password, and continue 
  • Save the exported key in the same folder as the CSR.


Step 3: create a new iOS application

  • Create a new XCode iOS project via File > New > Project 
  • From the iOS Application menu, select Single View Application and click ‘Next’ 

  • Enter the following information for: 
    • Product Name: FooBarPushDemo 
    • Organisation Name: your company name
    • Company identifier: be.acuzio.ios.demo
    • Class Prefix: FooBar 
    • Devices: iPhone 

  • The bundle identifier in our example now is be.acuzio.ios.demo.FooBarPushDemo. This bundle identifier will be need in the next steps when you create an App ID. 
  • Press Next to choose the location where to store your project and create the project

Step 4: set-up an AppID

  • Log in to the iOS Development Central (https://developer.apple.com/devcenter/ios/index.action)
  • Select Certificates, Identifiers & Profiles from the iOS Developer Program menu 
  • Select Identifiers from the iOS Apps menu 
  • In the App ID’s option, add a new identifer with this information: 
    • App ID description: FooBarPushDemo 
    • Select “Explicit App ID”, which is a requirement when you are using the Apple Push Notifications Service (APNS) 
    • Enter the bundle identifier: be.acuzio.ios.demo.FooBarPushDemo 
    • Check the box next to Push Notifications in the App Services block 
  • Click Continue and confirm by hitting Submit 
  • Click Done to return to the App IDs overview, you should now see the newly created AppID in the list

Step 5: set-up the Push Notification SSL Certificate


  • Select the newly created AppID from the list, it should look more or less like this: 

  • The option “push notifications” in development is in orange, which means it’s not set-up yet. Click on Edit and scroll down to the Push Notifications Section 
  • Click on “Create Certificate” for the “Development SSL Certificate” group 

  • You will be informed that you will need a CSR file (see Step 1: Generate a Certificate Signing Request (CSR)). 
  • Click “Continue” 
  • Upload the CSR file you created before and click on “Generate” 

  • Download the certificate and move it to the same folder e.g. ~/Desktop/FooBarPushDemo, make sure it is named “aps_development.cer” 

  • Add the certificate by double clicking on the .cer file. It will add a public key to your private key

Step 6: create a PEM file

  • Open a Terminal window and change to the directory where you stored the CSR, P12 key and Development certificate

$ cd ~/Desktop/FooBarPushDemo  

  • Convert the .cer file into a .pem file

$ openssl x509 -in aps_development.cer -inform der -out FooBarPushDemoCert.pem

  • Convert the .p12 key into a .pem file

$ openssl pkcs12 -nocerts -in FooBarPushDemoKey.p12 -out FooBarPushDemoKey.pem
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

  • The first time you will be prompted for the Passphrase, which in this example is “foobar”. 
  • The second and third password will be to protect your newly created key. I chose the same “foobar” password, but you can choose a different one. This password will actually be required to set-up the SSL connection to the Apple Push Notification Service servers. 
  • Concat the 2 .pem files into a combined .pem file

$ cat FooBarPushDemoCert.pem FooBarPushDemoKey.pem > FooBarPushDemo.pem

  • Now check if a connection can be set-up to the servers:

$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert FooBarPushDemoCert.pem -key FooBarPushDemoKey.pem

  • If all goes well, you should see a lot of information on the certificate chain the SSL handshake etc. You will not see you prompt, because the service is actually waiting for you to send a payload over the SSL connection. Just enter “Hello” and enter. The connection will be closed. You’ll see something like this:

…
---
No client certificate CA names sent
---
SSL handshake has read 2731 bytes and written 2189 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES256-SHA
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: BFED02C173464345CE8D6B59CB93D5B383260560ACB87A81C441A8145E646CCBDE5F01BFC545946A147824D2222CF647
    Key-Arg   : None
    Start Time: 1396596337
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
hello
closed
$

  • In development you will be using the sandbox APNs, in production you will need the other one.

Step 7: create a provisioning file

  • Log in to the iOS developer portal, as you did when creating the App ID
  • Navigate to the “Certificates, Identifiers & Profiles” menu and go to the provisioning files
  • Click on the add button to add a new provisioning profile 
  • Select the iOS App Development as type of provisioning file and click “continue” 

  • Select the FooBarPushDemo App ID from the drop-down list and continue 


  • Select the certificates you wish to include in this provisioning profile. To use this profile to install an app, the certificate the app was signed with must be included 
  • Select the devices on which you are going to test. You will need a physical device to test on, as push notifications will not work on your simulator 
  • Enter “FooBarPushDemo Provisioning File” as name and click “Generate” 
  • When the generation finishes, click on “Download” and save the file next to the other certificates etc. 
  • To add the provisioning file to XCode, drag the “.mobileprovision” file and drop it on the XCode icon

Step 8: Making your iOS app Push-Notifications enabled

  • Open the application delegate (in our case FooBarAppDelegate.m” - When the application is launched, it needs to register with APNS in order to be able to receive Remote Notification (Push Messages). - You’ll need to add the registration code to your “didFinishLaunchingWithOptions” method

@implementation FooBarAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
        (UIRemoteNotificationTypeAlert |
         UIRemoteNotificationTypeBadge |
         UIRemoteNotificationTypeSound)];
    
    
    return YES;
}

  • This registration will show the user, when the application is first launched, a message that this app uses remote notifications. 
  • A device token is used to send messages to a specific device. This can be accomplished by adding these lines of code:

 
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSLog(@" Registered device for remote notifications: %@", 
    [deviceToken.description stringByReplacingOccurrencesOfString:@" " withString:@""]);
}

- (void) application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@" Failed to register for remote notifications: %@", error);
}

  • Start the application on a physical device, you log should print out a message resembling this (the device token is 64 characters in length):

2014-04-04 10:39:26.764 FooBarPushDemo[2369:60b]  Registered device for remote notifications: 

  • We now know when the application is registered, of failed to register. The AppDelegate also captures the remote notification itself. In order to process the information, implement the following method:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    NSLog(@" Received remote notifcation: %@", userInfo);
}

Step 9: change build settings

  • Make sure that the correct “team” is selected, it should match the signing identity you used for signing your certificate and provisioning file 

  • Enable the “Remote notifications” background mode: 

  • Open the build settings and look for the entry “provisioning profile” 

  • Change it to match the added Provisioning Profile

Step 10: sending push notifications


  • When you want to send a Push notification to a device, you need to open an SSL connection to the APNs servers using the generated certificate. 
  • When the connection is established, you can transmit a binary payload. 
  • If no feedback is returned, it means that the message was sent successfully! 
  • An example PHP script:

<?php

$device_tokens = array("e2f8cd253f2…d5b78ba7055e44f6…d1a77fddf350");

$passphrase = "foobar";

$message = "FooBarPushDemo test!";

$cert = dirname(__FILE__) . '/FooBarPushDemo.pem';

///////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();

stream_context_set_option($ctx, 'ssl', 'local_cert', $cert);
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);


for($i = 0; $i < count($device_tokens); $i++) {
    $device_token = mb_convert_encoding($device_tokens[$i], 'utf-8');


    echo "Device token : $device_token, length <" . strlen($device_token) . ">" . PHP_EOL;

    // Open a connection to the APNS server
    $fp = stream_socket_client(
        'ssl://gateway.sandbox.push.apple.com:2195', $err,
        $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

    stream_set_blocking($fp, 0);
    stream_set_write_buffer($fp, 0);

    if (!$fp)
        exit("Failed to connect: $err $errstr" . PHP_EOL);

    echo 'Connected to APNS' . PHP_EOL;

    // Create the payload body
    $body['aps'] = array(
        'alert' => $message
    );

    // Encode the payload as JSON
    $payload = mb_convert_encoding(stripslashes(json_encode($body)), 'utf-8');

    // Build the binary notification

    if(strlen($device_token) != 64) {
        die("Device token has invalid length");
    }

    if(strlen($payload) < 10) {
        die('Invalid payload size');
    }

    $msg = chr(0)
            .pack('n', 32) //token length
            .pack('H*', $device_token) //token
            .pack('n', strlen($payload)) //length of payload
            .$payload;


    // Send it to the server
    $result = fwrite($fp, $msg /*, strlen($msg) */);

    if (!$result)
        echo 'Message not delivered' . PHP_EOL;
    else {
        echo 'Message successfully delivered, result:' . $result . PHP_EOL;

        echo "Sent {" . strlen($msg) . "} to server, received {" . $result . "}" . PHP_EOL;

        $response = fread($fp, 6);
        var_dump($response);

        $messageResult = unpack('Ccommand/CstatusCode/Nidentifier', $response);
        var_dump($messageResult);

    }

    //connect to the APNS feedback servers
    //make sure you're using the right dev/production server & cert combo!
    
    $apns = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $errcode, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    if(!$apns) {
        echo "ERROR $errcode: $errstr\n";
        return;
    }

    echo "Feedback returned: " . PHP_EOL;

    $feedback_tokens = array();
    //and read the data on the connection:
    while(!feof($apns)) {
        $data = fread($apns, 38);
        var_dump($data);
        if(strlen($data)) {
            $feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
        }
    }

    var_dump($feedback_tokens);

    fclose($apns);
    fclose($fp);
}