Open zner0L opened 11 months ago
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
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:
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.
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.
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);
}
}
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.
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());
}
}
I implemented setting the device name here 42c5f6a
(#134).
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.
System APIs that should be supported:
Both:
iOS: