How can I read the "attributedBody" column in macOS' iMessage database? - objective-c

Apple changed the Messages database schema in the latest macOS Ventura update, and sent messages seem to no longer store their body/content in the text column. The attributedBody column has the content, but it's stored as an encoded blob.
Has anyone had any luck getting plaintext out of this?

The attributedBody column is a serialized NSMutableAttributedString — packed using NSArchiver. It can be unpacked and read using NSUnarchiver but must first be extracted from the Messages sqlite database without losing any of its non-printable characters.
To preserve the column's content when performing a query, you can use sqlite3's HEX() function. The resulting bytes can then be read back into their original state by iterating over them and building a new NSString.
In the example below, NSData is extended with two helper methods to handle reading a file with hex-encoded data. Using dataWithContentsOfHexEncodedFile, a message record's attributedBody can be passed to NSUnarchiver, which will handle decoding the serialized NSAttributedString. This can then be converted to a normal NSString by accessing the string property.
#import <Foundation/Foundation.h>
#implementation NSData (NSDataExtended)
+ (NSData *)dataWithContentsOfHexEncodedString:(NSString *) string {
const char * chars = [string UTF8String];
int i = 0;
NSMutableData *data = [NSMutableData dataWithCapacity: string.length / 2];
char byteChars[3] = {'\0', '\0', '\0'};
unsigned long wholeByte;
while (i < string.length) {
byteChars[0] = chars[i++];
byteChars[1] = chars[i++];
wholeByte = strtoul(byteChars, NULL, 16);
[data appendBytes:&wholeByte length:1];
}
return data;
}
+ (NSData *)dataWithContentsOfHexEncodedFile:(NSString *) filePath {
return [self dataWithContentsOfHexEncodedString:[NSString
stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil]];
}
#end
int main(int argc, const char * argv[]) {
system([[[NSString alloc] initWithFormat:#"%s %s > %s",
"/usr/bin/sqlite3 ~/Library/Messages/chat.db",
"'SELECT HEX(attributedBody) FROM message ORDER BY ROWID DESC LIMIT 1'",
"/private/tmp/msgbody"] UTF8String]);
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSMutableAttributedString *msg = [[[NSUnarchiver alloc]
initForReadingWithData:[NSData dataWithContentsOfHexEncodedFile:#"/private/tmp/msgbody"]
] decodeTopLevelObjectAndReturnError:nil];
NSLog(#"%#", [msg string]);
return 0;
}

Related

AES Encryption CryptLib in iOS 13 not working

My application uses AES 256 encryption to encrypt a string. The same code that was used before is generating a different result. This problem started when iOS 13 was released. And it happens only to applications that are shipped to the store or built with Xcode 11.
Here is the code used for the encryption:
- (NSData *)encrypt:(NSData *)plainText key:(NSString *)key iv:(NSString *)iv {
char keyPointer[kCCKeySizeAES256+2],// room for terminator (unused) ref: https://devforums.apple.com/message/876053#876053
ivPointer[kCCBlockSizeAES128+2];
BOOL patchNeeded;
bzero(keyPointer, sizeof(keyPointer)); // fill with zeroes for padding
patchNeeded= ([key length] > kCCKeySizeAES256+1);
if(patchNeeded)
{
NSLog(#"Key length is longer %lu", (unsigned long)[[self md5:key] length]);
key = [key substringToIndex:kCCKeySizeAES256]; // Ensure that the key isn't longer than what's needed (kCCKeySizeAES256)
}
//NSLog(#"md5 :%#", key);
[key getCString:keyPointer maxLength:sizeof(keyPointer) encoding:NSUTF8StringEncoding];
[iv getCString:ivPointer maxLength:sizeof(ivPointer) encoding:NSUTF8StringEncoding];
if (patchNeeded) {
keyPointer[0] = '\0'; // Previous iOS version than iOS7 set the first char to '\0' if the key was longer than kCCKeySizeAES256
}
NSUInteger dataLength = [plainText length];
//see https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CCryptorCreateFromData.3cc.html
// For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
size_t buffSize = dataLength + kCCBlockSizeAES128;
void *buff = malloc(buffSize);
size_t numBytesEncrypted = 0;
//refer to http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h
//for details on this function
//Stateless, one-shot encrypt or decrypt operation.
CCCryptorStatus status = CCCrypt(kCCEncrypt, /* kCCEncrypt, etc. */
kCCAlgorithmAES128, /* kCCAlgorithmAES128, etc. */
kCCOptionPKCS7Padding, /* kCCOptionPKCS7Padding, etc. */
keyPointer, kCCKeySizeAES256, /* key and its length */
ivPointer, /* initialization vector - use random IV everytime */
[plainText bytes], [plainText length], /* input */
buff, buffSize,/* data RETURNED here */
&numBytesEncrypted);
if (status == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];
}
free(buff);
return nil;
}
- (NSString *) encryptPlainTextWith:(NSString *)plainText key:(NSString *)key iv:(NSString *)iv {
return [[[[CryptLib alloc] init] encrypt:[plainText dataUsingEncoding:NSUTF8StringEncoding] key:[[CryptLib alloc] sha256:key length:32] iv:iv] base64EncodedStringWithOptions:0];
}
/**
* This function computes the SHA256 hash of input string
* #param key input text whose SHA256 hash has to be computed
* #param length length of the text to be returned
* #return returns SHA256 hash of input text
*/
- (NSString*) sha256:(NSString *)key length:(NSInteger) length{
const char *s=[key cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData=[NSData dataWithBytes:s length:strlen(s)];
uint8_t digest[CC_SHA256_DIGEST_LENGTH]={0};
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
NSData *out=[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSString *hash=[out description];
hash = [hash stringByReplacingOccurrencesOfString:#" " withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#"<" withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#">" withString:#""];
if(length > [hash length])
{
return hash;
}
else
{
return [hash substringToIndex:length];
}
}
##
I would like to know if something in the code path has changed in the way it works. The method called to do the encryptions is "encryptPlainTextWith". Thanks in advance.
Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash=[out debugDescription];
And everything got back to normal. Cheers Happy coding.
Alternative Solution as per #Rob Napier
create separate function for converting NSData to Hex
#pragma mark - String Conversion
-(NSString*)hex:(NSData*)data{
NSMutableData *result = [NSMutableData dataWithLength:2*data.length];
unsigned const char* src = data.bytes;
unsigned char* dst = result.mutableBytes;
unsigned char t0, t1;
for (int i = 0; i < data.length; i ++ ) {
t0 = src[i] >> 4;
t1 = src[i] & 0x0F;
dst[i*2] = 48 + t0 + (t0 / 10) * 39;
dst[i*2+1] = 48 + t1 + (t1 / 10) * 39;
}
return [[NSString alloc] initWithData:result encoding:NSASCIIStringEncoding];
}
After that Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash = [self hex:out];
I suspect that your key is longer than 32 UTF-8 bytes. In that case, this code is incorrect. Your patchNeeded conditional is basically creating a garbage key. The contents of buffer aren't promised if this function return returns false, but you're relying on them.
There is no secure way to truncate a key you were given, so I'm not really certain what behavior you want here. It depends on what kinds of strings you're passing.
This code is also incorrect if iv is shorter than 16 UTF-8 bytes. You'll wind up including random values from the stack. That part can be fixed with:
bzero(ivPointer, sizeof(ivPointer));
But if your previous version relied on random values, this will still be different.
Assuming you need to match the old behavior, the best way to debug this is to run your previous version in a debugger and see what keyPointer and ivPointer wind up being.
(Note that this approach to creating a key is very insecure. It's drastically shrinking the AES keyspace. How much depends on what kind of strings you're passing, but it's dramatic. You also should never reuse the same key+iv combination in two messages when using CBC, which this looks like it probably does. If possible, I recommend moving to a correct AES implementation. You can look at RNCryptor for one example of how to do that, or use RNCryptor directly if you prefer.)

How to read input in Objective-C?

I am trying to write some simple code that searches two dictionaries for a string and prints to the console if the string appears in both dictionaries. I want the user to be able to input the string via the console, and then pass the string as a variable into a message. I was wondering how I could go about getting a string from the console and using it as the argument in the following method call.
[x rangeOfString:"the string goes here" options:NSCaseInsensitiveSearch];
I am unsure as to how to get the string from the user. Do I use scanf(), or fgets(), into a char and then convert it into a NSSstring, or simply scan into an NSString itself. I am then wondering how to pass that string as an argument. Please help:
Here is the code I have so far. I know it is not succinct, but I just want to get the job done:
#import <Foundation/Foundation.h>
#include <stdio.h>
#include "stdlib.h"
int main(int argc, const char* argv[]){
#autoreleasepool {
char *name[100];
printf("Please enter the name you wish to search for");
scanf("%s", *name);
NSString *name2 = [NSString stringWithFormat:#"%s" , *name];
NSString *nameString = [NSString stringWithContentsOfFile:#"/usr/share/dict/propernames" encoding:NSUTF8StringEncoding error:NULL];
NSString *dictionary = [NSString stringWithContentsOfFile:#"/usr/share/dict/words" encoding:NSUTF8StringEncoding error:NULL];
NSArray *nameString2 = [nameString componentsSeparatedByString:#"\n"];
NSArray *dictionary2 = [dictionary componentsSeparatedByString:#"\n"];
int nsYES = 0;
int dictYES = 0;
for (NSString *n in nameString2) {
NSRange r = [n rangeOfString:name2 options:NSCaseInsensitiveSearch];
if (r.location != NSNotFound){
nsYES = 1;
}
}
for (NSString *x in dictionary2) {
NSRange l = [x rangeOfString:name2 options:NSCaseInsensitiveSearch];
if (l.location != NSNotFound){
dictYES = 1;
}
}
if (dictYES && nsYES){
NSLog(#"glen appears in both dictionaries");
}
}
}
Thanks.
Safely reading from standard input in an interactive manner in C is kind of involved. The standard functions require a fixed-size buffer, which means either some input will be too long (and corrupt your memory!) or you'll have to read in a loop. And unfortunately, Cocoa doesn't offer us a whole lot of help.
For reading standard input entirely (as in, if you're expecting an input file over standard input), there is NSFileHandle, which makes it pretty succinct. But for interactively reading and writing like you want to do here, you pretty much have to go with the linked answer for reading.
Once you have read some input into a C string, you can easily turn it into an NSString with, for example, +[NSString stringWithUTF8String:].

How to insert \x into NSString object

I am trying to print data to a bluetooth printer. In order to do this I need to send hex to the printer. I have been successfully doing this by sending commands such as
#"\x1b\x7b\x01"
#"\x1b\x7b\x00"
These are all static commands. I now need to take some data, a NSString, convert it to hex, and then send it to the printer. I tried several different ways, most of which from this site, but cannot get it to work.
I know this doesn't work but I would like to do the following:
for(NSUInteger i = 0; i < [str length]; i++ )
{
[hexString appendString:[NSString stringWithFormat:#"\x%#",[str characterAtIndex:i]]];
}
hexString is the string I am trying to create and return. str is the string I am trying turn into hex. For example, 11111111 or abcdefg.
I have tried a variety of things all with no luck.
Any help would be greatly appreciated!!!
dreamlax is right, you should use NSData. You could do something like this:
NSDictionary *hexTable = #{#"0" : #0,
#"1" : #1,
#"2" : #2,
#"3" : #3,
#"4" : #4,
#"5" : #5,
#"6" : #6,
#"7" : #7,
#"8" : #8,
#"9" : #9,
#"a" : #10,
#"b" : #11,
#"c" : #12,
#"d" : #13,
#"e" : #14,
#"f" : #15};
NSMutableData *data = [[NSMutableData alloc] init];
for(NSUInteger i = 0; i < str.length; i+=2 ) {
NSString *substring1 = [str substringWithRange:NSMakeRange(i, 1)];
NSString *substring2 = [str substringWithRange:NSMakeRange(i+1, 1)];
char valueHigh = [hexTable[substring1] charValue];
char valueLow = [hexTable[substring2] charValue];
char result = valueHigh*0x10 + valueLow;
[data appendBytes:&result length:1];
}
for using "\" you need to escape one "\" more in your format it should be looking like this "\\x%#"
basically \ is a escape character using in many of the languages
e.g if you use
NSLog(#"\\"); // output is one backslash
NSLog(#"\\\\"); // output is two backslashes
Strings are for storing text. If you want to store arbitrary bytes use NSData instead. In particular, when interfacing with external hardware, I think in general you want to avoid using NSString especially because strings are always assumed to contain text, but it appears as though your printer is expecting binary data (which you are simply only representing in hexadecimal). Things like NUL bytes (\x00 or \0) do not play nicely with NSString but they do play nicely with NSData.
const char boldOnBytes[] = { 0x1b, 0x7b, 0x01 };
NSData *boldOn = [NSData dataWithBytes:boldOnBytes length:sizeof boldOnBytes];
const char boldOffBytes[] = { 0x1b, 0x7b, 0x00 };
NSData *boldOff = [NSData dataWithBytes:boldOffBytes length:sizeof boldOffBytes];
(I have no idea what your actual codes do)
To get the bytes again, you can use for example:
// assume you have a function called sendFunction which accepts a handle to the
// printer, the bytes to send, and a length of how many bytes to send.
sendFunction(printerHandle, [boldOn bytes], [boldOn length]);

Converting NSData bytes to NSString

I am trying to create a 16 byte and later 32 byte initialization vector in objective-c (Mac OS). I took some code on how to create random bytes and modified it to 16 bytes, but I have some difficulty with this. The NSData dumps the hex, but an NSString dump gives nil, and a cstring NSLog gives the wrong number of characters (not reproduced the same in the dump here).
Here is my terminal output:
2012-01-07 14:29:07.705 Test3Test[4633:80f] iv hex <48ea262d efd8f5f5 f8021126 fd74c9fd>
2012-01-07 14:29:07.710 Test3Test[4633:80f] IV string: (null)
2012-01-07 14:29:07.711 Test3Test[4633:80f] IV char string t^Q¶�^��^A
Here is the main program:
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//NSString *iv_string = [NSString stringWithCString:iv encoding:NSUTF8StringEncoding];
testclass *obj = [testclass alloc];
NSData *iv_data = [obj createRandomNSData];
//[iv_string dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"iv hex %#",iv_data);
//NSString *iv_string = [[NSString alloc] initWithBytes:[iv_data bytes] length:16 encoding:NSUTF8StringE$
NSString *iv_string = [[NSString alloc] initWithData:iv_data encoding:NSUTF8StringEncoding];
NSLog(#"IV string: %#",iv_string);
NSLog(#"IV char string %.*s",[iv_data bytes]);
return 0;
]
(I left in the above some commented code that I tried and did not work also).
Below is my random number generater, taken from a stack overflow example:
#implementation testclass
-(NSData*)createRandomNSData
{
int twentyMb = 16;
NSMutableData* theData = [NSMutableData dataWithCapacity:twentyMb];
for( unsigned int i = 0 ; i < twentyMb/4 ; ++i )
{
u_int32_t randomBits = arc4random();
[theData appendBytes:(void*)&randomBits length:4];
}
NSData *data = [NSData dataWithData:theData];
[theData dealloc];
return data;
}
#end
I am really quite clueless as to what could be the problem here. If I have data as bytes, it should convert to a string or not necessarily? I have looked over the relevant examples here on stackoverflow, but none of them have worked in this situation.
Thanks,
Elijah
An arbitrary byte sequence may not be legal UTF8 encoding. As #Joachim Isaksson notes, there is seldom reason to convert to strings this way. If you need to store random data as a string, you should use an encoding scheme like Base64, serialize the NSData to a plist, or similar approach. You cannot simply use a cstring either, since NULL is legal inside of a random byte sequence, but is not legal inside of a cstring.
You do not need to build your own random byte creator on Mac or iOS. There's one built-in called SecRandomCopyBytes(). For example (from Properly encrypting with AES with CommonCrypto):
+ (NSData *)randomDataOfLength:(size_t)length {
NSMutableData *data = [NSMutableData dataWithLength:length];
int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
NSAssert(result == 0, #"Unable to generate random bytes: %d",
errno);
return data;
}
When converting NSData to NSString using an UTF8 encoding, you won't necessarily end up with the same number of bytes since not all binary values are valid encodings of characters. I'd say using a string for binary data is a recipe for problems.
What is the use of the string? NSData is exactly the datatype you want for storing binary data to begin with.

Very basic Objective-C/C Problem

Here's my code:
#import <Foundation/Foundation.h>
void PrintPathInfo() {
const char *path = [#"~" fileSystemRepresentation];
NSLog(#"My home folder is at '%#'", path);
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
PrintPathInfo();
[pool drain];
return 0;
}
And here's my problem:
Program received signal: “EXC_BAD_ACCESS”.
I really think the problem is my NSLog but I don't know how to solve it.
Could someone help me please? Thanks!
path is not an NSString, which is why that crashes. %# in a formatting string expects an object, and asks it for a description to get a string to print... because you are using a C style string, you need to use the standard C string formatters OR convert the const char * back to an NSString using the initWithCString:encoding: class method of NSString.
Staying with a const char *, you can use:
NSLog(#"My home folder is at '%s'", path);
which would work.
%# is for objects. (Like NSString). for const char* you will want the good old %s from the c's printf format codes.
See http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html
For the format specifies and their meanings