Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract system APIs for common data stores to place honey data #123

Open
3 of 8 tasks
zner0L opened this issue Dec 12, 2023 · 9 comments
Open
3 of 8 tasks

Abstract system APIs for common data stores to place honey data #123

zner0L opened this issue Dec 12, 2023 · 9 comments

Comments

@zner0L
Copy link
Contributor

zner0L commented Dec 12, 2023

System APIs that should be supported:

Both:

  • Contacts
  • Hostname
  • Calendar
  • Phone Calls
  • Messages
  • Geolocation

iOS:

  • Health data
  • Apple Home data
@zner0L
Copy link
Contributor Author

zner0L commented Dec 12, 2023

For iOS, creating events could be done using the Events and Reminders API

On Android, you might be able to send an Intent: https://stackoverflow.com/questions/7160231/how-to-add-a-calendar-event-on-an-android-device-with-a-given-date

@zner0L
Copy link
Contributor Author

zner0L commented Dec 12, 2023

On Android, you can trigger many actions using common intents: https://developer.android.com/guide/components/intents-common

You can trigger these events using am start -a android.intent.action.INSERT -t <type of data> -e <extras>. This sometimes requires user interaction.

For the calendar, this looks like this:

adb shell "am start -a android.intent.action.INSERT -t 'vnd.android.cursor.dir/event' -e beginTime 1702381968000 -e allDay true -e title 'Test Event' -e endTime 1705981968000"
# press the back button to close the dialog and save the event
adb shell input keyevent 4
adb shell input keyevent 4

This only works, if the user already has a configured calendar. If not, it will fail with an error message in the GUI.

For the contacts, this is similar. We can call up the contacts with

adb shell "am start -a android.intent.action.INSERT -t vnd.android.cursor.dir/contact -e name 'Bo Lawson' -e phone 123456789"

But we can not affirmatively close the then opened save dialog, because keyevent 4 just cycles around with the "Discard changes" dialog:
screen

@zner0L
Copy link
Contributor Author

zner0L commented Dec 12, 2023

There is also the possibility to import contacts as vcards (https://stackoverflow.com/questions/23488290/android-importing-contacts-through-vcards-via-adb), however, this has a similar problem, because it requires interaction with the GUI. different versions of Android/different contacts apps act very differently here, so it is hard implement a general solution.

@zner0L
Copy link
Contributor Author

zner0L commented Dec 12, 2023

Another option for adding contacts without having to interact with the GUI might be via the database. According to this article: https://www.dev2qa.com/android-contacts-database-structure/, contacts are saved in the /data/data/com.android.providers.contacts/databases/contacts2.db.

@zner0L
Copy link
Contributor Author

zner0L commented Dec 13, 2023

Ok, with the help of this article I managed to write a frida skript that can be attached to com.android.contacts to add contacts without user interaction:

// This follows https://github.com/frida/frida/issues/1049
function loadMissingClass(className) {
    const loaders = Java.enumerateClassLoadersSync();
    let classFactory;
    for (const loader of loaders) {
        try {
            loader.findClass(className);
            classFactory = Java.ClassFactory.get(loader);
            break;
        } catch {
            // There was an error while finding the class, try another loader;
            continue;
        }
    }
    return classFactory.use(className);
}

function addContact(name) {
    const appContext = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();

    const ContentProviderOperation = Java.use('android.content.ContentProviderOperation');
    const ContactsContract = loadMissingClass('android.provider.ContactsContract');
    const RawContacts = loadMissingClass('android.provider.ContactsContract$RawContacts');
    const SyncColumns = loadMissingClass('android.provider.ContactsContract$SyncColumns');
    const Data = loadMissingClass('android.provider.ContactsContract$Data');
    const DataColumns = loadMissingClass('android.provider.ContactsContract$DataColumns');
    const StructuredName = loadMissingClass('android.provider.ContactsContract$CommonDataKinds$StructuredName');

    const ops = Java.use('java.util.ArrayList').$new();
    ops.add(
        ContentProviderOperation.newInsert(RawContacts.CONTENT_URI.value)
            .withValue(SyncColumns.ACCOUNT_TYPE.value, null)
            .withValue(SyncColumns.ACCOUNT_NAME.value, null)
            .build()
    );

    ops.add(
        ContentProviderOperation.newInsert(Data.CONTENT_URI.value)
            .withValueBackReference(DataColumns.RAW_CONTACT_ID.value, 0)
            .withValue(DataColumns.MIMETYPE.value, StructuredName.CONTENT_ITEM_TYPE.value)
            .withValue(StructuredName.DISPLAY_NAME.value, name)
            .build()
    );

    try {
        appContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY.value, ops);
    } catch (e) {
        console.log(e);
    }
}

@zner0L
Copy link
Contributor Author

zner0L commented Jun 23, 2024

It took some figuring out, but now I also have a script to create events on iOS. I do this while attached to the Calendar app, because it obviously already has the correct permissions. This is the script:

function addCalendarEvent(eventData) {
    const eventStore = ObjC.classes.EKEventStore.alloc().init();
    const NSError = ObjC.classes.NSError;
    const NSISO8601DateFormatter = ObjC.classes.NSISO8601DateFormatter;
    const NSString = ObjC.classes.NSString;
    const EKEvent = ObjC.classes.EKEvent;

    const formatter = NSISO8601DateFormatter.alloc().init();

    const evt = EKEvent.eventWithEventStore_(eventStore);
    evt.setTitle_(NSString.stringWithString_(eventData.title));
    const start = formatter.dateFromString_(NSString.stringWithString_(eventData.startDate));
    evt.setStartDate_(start);
    const end = formatter.dateFromString_(NSString.stringWithString_(eventData.endDate));
    evt.setEndDate_(end);
    evt.setCalendar_(eventStore.defaultCalendarForNewEvents());

    // https://github.com/frida/frida/issues/729
    const errorPtr = Memory.alloc(Process.pointerSize);
    Memory.writePointer(errorPtr, NULL);

    eventStore.saveEvent_span_commit_error_(evt, 0, 1, errorPtr);

    const error = Memory.readPointer(errorPtr);
    if (!error.isNull()) {
        const errorObj = new ObjC.Object(error); // now you can treat errorObj as an NSError instance
        console.error(errorObj.toString());
    }
}

It is based on Apple’s documentation and an example from an article.

@zner0L
Copy link
Contributor Author

zner0L commented Jun 23, 2024

And here is a script for adding contacts (attached to the Contacts app) based on this stackoverflow answer:

function addContact(contactData) {
    const CNMutableContact = ObjC.classes.CNMutableContact;
    const NSString = ObjC.classes.NSString;
    const CNLabeledValue = ObjC.classes.CNLabeledValue;
    const CNPhoneNumber = ObjC.classes.CNPhoneNumber;
    const CNSaveRequest = ObjC.classes.CNSaveRequest;
    const NSMutableArray = ObjC.classes.NSMutableArray;
    const CNContactStore = ObjC.classes.CNContactStore;

    const contact = CNMutableContact.alloc().init();
    contact.setLastName_(NSString.stringWithString_(contactData.lastName));
    if(contactData.firstName) contact.setFirstName_(NSString.stringWithString_(contactData.firstName));

    if (contactData.phoneNumber) {
        const number = CNPhoneNumber.phoneNumberWithStringValue_(NSString.stringWithString_(contactData.phoneNumber));
        const homePhone = CNLabeledValue.labeledValueWithLabel_value_(NSString.stringWithString_('home'), number);
        const numbers = NSMutableArray.alloc().init();
        numbers.addObject_(homePhone);
        contact.setPhoneNumbers_(numbers);
    }

    if(contactData.email) {
         const email = NSString.stringWithString_(contactData.email);
         const homeEmail = CNLabeledValue.labeledValueWithLabel_value_(NSString.stringWithString_('home'), email);
         const emails = NSMutableArray.alloc().init();
         emails.addObject_(homeEmail);
         contact.setEmailAddresses_(emails);
    }

    const request = CNSaveRequest.alloc().init();
    request.addContact_toContainerWithIdentifier_(contact, null);

    // https://github.com/frida/frida/issues/729
    const errorPtr = Memory.alloc(Process.pointerSize);
    Memory.writePointer(errorPtr, NULL);

    const store = CNContactStore.alloc().init();
    store.executeSaveRequest_error_(request, errorPtr);

    const error = Memory.readPointer(errorPtr);
    if (!error.isNull()) {
        var errorObj = new ObjC.Object(error); // now you can treat errorObj as an NSError instance
        console.error(errorObj.toString());
    }
}

@zner0L
Copy link
Contributor Author

zner0L commented Jun 28, 2024

I implemented setting the device name here 42c5f6a (#134).

@zner0L
Copy link
Contributor Author

zner0L commented Jun 28, 2024

We put Geolocation on hold, while we decide on whether we want to use Appium, because it already implements this (see #21).
Apple specific data and calls and messages are out of scope for now.

baltpeter added a commit that referenced this issue Jun 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant