📜 ⬆️ ⬇️

iOS Console App - writing a program for iOS without a GUI

Good day to all habravchanam!

Today I will tell you how you can easily, quickly and simply write a console program to run on an iOS device. Of course, we will need for this jailbreak-nuty device, without it, alas, no way: iOS AppStore (also known as iTunesStore) does not allow distribution of console utilities.


')
Writing HelloWorld is not particularly interesting. Therefore, we will write a useful utility that allows you to view some information about the system, obtained through private API.

For example, information about installed programs and their versions.

In principle, you can still steal passwords and other personal data, but I will leave this as an optional task.

So, under the cut - a description of the process of creating a console program right in Xcode.



Create a project in Xcode: select the template “Empty Application”, invent a program name (in my case, hackup). Well, we got an empty GUI application, but we didn't want that, did we? Feel free to remove unnecessary files from the project!


What else? We do not need a GUI, so remove the extra frameworks too.


Now we go to the properties of the project, more precisely - to the properties of the target. There we remove the signing code.


Further, we do not need Info.plist, feel free to demolish and mention it.


We do not need a bundle either: so let the final program be in a fake bandle with the extension ".console"


Well, now let's tweak the code in main.m a bit: remove the UIKit and AppDelegate . In .pch also remove too much, leaving only Foundation.h . We also remove the UIApplication from the main() UIApplication , just put return 0.

Well, we can now try to build a project for the simulator! Yes, everything is just that simple. But how to start now our wonderful program making nothing? After all, there is no terminal in the simulator? Yes, everything is simple: it's a simulator, not an emulator, so our program is an ordinary program for makosi. But to run it even from the terminal is not an easy thing, because it requires frameworks built for the simulator!

We start to remember the theory. All the frameworks and frameworks are searched and loaded by the dyld program, which relies on some flags, a full description of which is contained in the mana . We are interested in the DYLD_ROOT_PATH parameter, that is, the path to which the dyld “attaches” all the paths to the frameworks or libs in the file.

Well, go to the terminal in the folder with the program collected for the simulator. To do this, select “Show In Finder” for our target.


Then you can simply drag hackup.console to the terminal with the pre-typed cd .

 $ pwd /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console $ ./hackupdyld: Symbol not found: _OBJC_CLASS_$_NSString Referenced from: /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console/./hackup Expected in: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation in /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console/./hackup [1] 61835 trace trap ./hackup $ otool -L hackup hackup: /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 992.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 65.0.0) /usr/lib/libSystem.dylib (compatibility version 1.0.0, current version 125.0.0) 

We see that now our program is looking for the Foundation framework in the standard system folder. Of course, it differs from the framework we need for the simulator.

We treat: we find our iOS SDK in the Xcode.app bundle, we prescribe:

 $ DYLD_ROOT_PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk" ./hackup 

No errors! The program started, but still does nothing. Fix it!

To begin with, we will write a simple function for outputting something to stdout , replacing NSLog . I called my NSPrintf , here is its code:

 void NSPrintf(NSString *format, ...) { va_list args; va_start(args, format); NSString *message = [[[NSString alloc] initWithFormat:format arguments:args] autorelease]; va_end(args); std::cout << [message cStringUsingEncoding:NSUTF8StringEncoding]; } 

The attentive reader will notice that the actual output is done via std::cout , in fact, therefore, do not forget to rename main.m to main.mm and connect iostream .

Well, now we can type in the console, but what will we type?

This is where the fun begins. Suppose we want to get a list of installed programs. Public APIs do not allow us to do this. What about private ones? And where to get them? But what about the lack of documentation?

Oh, difficult questions. Documentation for private APIs, of course, no. At least some reasonable description - too. But at our disposal there is a wonderful thing - reverse engineering! With it, the missing headers for public and private frameworks were obtained. Actually, there is such a cognitive repository: iOS-Runtime-Headers (and there is a tool with which these heders were obtained: RuntimeBrowser , thanks to the good man Nicolas Seriot ). A little honored cheders and look for something in them related to the programs. Sooner or later, but we stumble upon the method - (id)applications the class ISSoftwareMap , included in the iTunesStore private framework. Well, let's set ourselves to call this method and print out whatever it returns to us!


Note that you can not just take and add a private framework to the project. Therefore, we will load it on the fly, which is very convenient to do using the NSBundle class. Let's write an auxiliary function that will try to load a private framework:

 BOOL loadPrivateFramework(NSString *framework) { NSString *path = [NSString stringWithFormat:@"/System/Library/PrivateFrameworks/%@.framework", framework]; NSBundle *b = [NSBundle bundleWithPath:path]; BOOL success = [[[b retain] autorelease] load]; if (!success) { NSPrintf(@"Failed to load private framework %@!\n", framework); } return success; } 

If the function returns YES , then we can work with the loaded framework, or rather, the classes available in it become available to us. Now we need to get the ISSoftwareMap class, which can be done in this way:

 Class ISSoftwareMap = NSClassFromString(@"ISSoftwareMap"); 

As we already understood from the headers, we need to call + (id)currentMap or + (id)loadedMap to get an instance of the class.

 id isSoftwareMap = [ISSoftwareMap performSelector:@selector(currentMap)]; if (!isSoftwareMap) { isSoftwareMap = [ISSoftwareMap performSelector:@selector(loadedMap)]; } 

Well, we learned how to work with private frameworks! My congratulations! =)

Now finally get a list of installed programs:

 id *applications = [isSoftwareMap performSelector:@selector(applications)]; NSPrintf(@"applications:\n%@\n", applications); 

So now let's test our program in the simulator! Something? Does not work? Well, yes, it does not work, because we have absolute paths for downloading frameworks. So let's better run on the device. In my case, this is the first version of the iPad.

I'm sorry, what? Not going? Swears on unsigned? Well, yes, I forgot to say: iOS SDK does not allow collecting binaries for a device without a signature. But we have firmly decided to achieve our goal! We will edit the SDK settings. Find the SDKSettings.plist SDKSettings.plist in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/ and edit it. What did not work out? You are not the owner of the file? Well, yes, security policies and all that ... But we have sudo:

 $ sudo plutil -convert xml1 SDKSettings.plist $ sudo nano SDKSettings.plist $ sudo plutil -convert binary1 SDKSettings.plist 

Of course, instead of nano, you can use vim / mcedit / emacs and even Sublime Text 2. In the editor, you must find the tag CODE_SIGNING_REQUIRED in the XML CODE_SIGNING_REQUIRED and set its value to NO.


Now restart Xcode and rejoice - our problem is solved! Now the project is going. I can not wait to throw it on the device! To do this, we will use OpenSSH (set via Cydia):

 $ pwd /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphoneos/hackup.console $ scp hackup root@192.168.2.2:/private/var/mobile/Documents/ root@192.168.2.2's password: hackup 100% 26KB 26.3KB/s 00:00 $ ssh mobile@192.168.2.2 mobile@192.168.2.2's password: iSilvansky:~ mobile$ ~/Documents/hackup ( "<ISSoftwareApplication: 0x18b4e0>: (ru.mail.agent, 335315530:11499676)", "<ISSoftwareApplication: 0x18d010>: (com.getdropbox.Dropbox, 327630330:11201748)", # ... some more ... "<ISSoftwareApplication: 0x1936f0>: (8HLDK844H7.net.litchie.idos, 377135644:2716751)" ) 

Actually, we see that the - (id)applications method returns us an NSArray containing objects of the type ISSoftwareApplication . The description of this class is also found in the private headers of the same framework. Well, the list of programs received, let's look at them more closely:

 NSArray *applications = [isSoftwareMap performSelector:@selector(applications)]; if (applications) { for (id app in applications) { NSPrintf(@" *** Info for application %@\n", app); LOG_SELECTOR(app, bundleIdentifier) LOG_SELECTOR(app, bundleShortVersionString) LOG_SELECTOR(app, bundleVersion) LOG_SELECTOR(app, accountDSID) LOG_SELECTOR(app, accountIdentifier) LOG_SELECTOR(app, softwareType) LOG_SELECTOR(app, versionIdentifier) LOG_SELECTOR(app, itemIdentifier) LOG_SELECTOR(app, containerPath) LOG_SELECTOR(app, storeFrontIdentifier) LOG_SELECTOR(app, description) } } 

The macro LOG_SELECTOR defined as:

 #define LOG_SELECTOR(obj, sel)\ if ([obj respondsToSelector:@selector(sel)])\ {\ NSPrintf(@" "#sel": %@\n", [obj performSelector:@selector(sel)]);\ } 

It makes it a little easier to get and display information. We are testing!

 iSilvansky:~ mobile$ ~/Documents/hackup *** Info for application <ISSoftwareApplication: 0x16d5d0>: (ru.mail.agent, 335315530:11499676) bundleIdentifier: ru.mail.agent bundleShortVersionString: 4.0 bundleVersion: 3815 accountDSID: 407343733 accountIdentifier: habrahabr.ru/users/silvansky/ softwareType: (null) versionIdentifier: 11499676 itemIdentifier: 335315530 containerPath: /private/var/mobile/Applications/374BF6DB-8773-4063-9D84-F5858DE7AEBE storeFrontIdentifier: 143441 description: <ISSoftwareApplication: 0x16d5d0>: (ru.mail.agent, 335315530:11499676) *** Info for application <ISSoftwareApplication: 0x16f100>: (com.getdropbox.Dropbox, 327630330:11201748) bundleIdentifier: com.getdropbox.Dropbox # ... many more ... 

Detailed information may be useful to us, but it is better to display only the bundle id, so remove all extra output (or disable it until better times).

Now the output of our program is somewhat simplified:

 iSilvansky:~ mobile$ ~/Documents/hackup ru.mail.agent com.getdropbox.Dropbox # ... more and more ... 8HLDK844H7.net.litchie.idos iSilvansky:~ mobile$ exit logout Connection to 192.168.2.2 closed. 

We can now grep this conclusion, we can send ourselves a soap, we can ... Yes, whatever we want, we can! But most importantly: we now know how to use private APIs and write custom programs for iOS. Now you can write something useful and put it in Cydia.

Actually, the full project for Xcode can, as usual, be found on the githabe .

What is planned to do next (of course, with a detailed description in the articles):

Source: https://habr.com/ru/post/163281/


All Articles