A couple of years ago, I wrote a few category methods for exactly the same reason, transforming a whole tree of user defaults to mutable. Here they are - use them at your own risk! :-)
//
//  SPDeepCopy.h
//
//  Created by Sherm Pendley on 3/15/09.
//
#import <Cocoa/Cocoa.h>
// Deep -copy and -mutableCopy methods for NSArray and NSDictionary
@interface NSArray (SPDeepCopy)
- (NSArray*) deepCopy;
- (NSMutableArray*) mutableDeepCopy;
@end
@interface NSDictionary (SPDeepCopy)
- (NSDictionary*) deepCopy;
- (NSMutableDictionary*) mutableDeepCopy;
@end
//
//  SPDeepCopy.m
//
//  Created by Sherm Pendley on 3/15/09.
//
#import "SPDeepCopy.h"
@implementation NSArray (SPDeepCopy)
- (NSArray*) deepCopy {
    unsigned int count = [self count];
    id cArray[count];
    for (unsigned int i = 0; i < count; ++i) {
        id obj = [self objectAtIndex:i];
        if ([obj respondsToSelector:@selector(deepCopy)])
            cArray[i] = [obj deepCopy];
        else
            cArray[i] = [obj copy];
    }
    NSArray *ret = [[NSArray arrayWithObjects:cArray count:count] retain];
    // The newly-created array retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i)
        [cArray[i] release];
    return ret;
}
- (NSMutableArray*) mutableDeepCopy {
    unsigned int count = [self count];
    id cArray[count];
    for (unsigned int i = 0; i < count; ++i) {
        id obj = [self objectAtIndex:i];
        // Try to do a deep mutable copy, if this object supports it
        if ([obj respondsToSelector:@selector(mutableDeepCopy)])
            cArray[i] = [obj mutableDeepCopy];
        // Then try a shallow mutable copy, if the object supports that
        else if ([obj respondsToSelector:@selector(mutableCopyWithZone:)])
            cArray[i] = [obj mutableCopy];
        // Next try to do a deep copy
        else if ([obj respondsToSelector:@selector(deepCopy)])
            cArray[i] = [obj deepCopy];
        // If all else fails, fall back to an ordinary copy
        else
            cArray[i] = [obj copy];
    }
    NSMutableArray *ret = [[NSMutableArray arrayWithObjects:cArray count:count] retain];
    // The newly-created array retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i)
        [cArray[i] release];
    return ret;
}
@end
@implementation NSDictionary (SPDeepCopy)
- (NSDictionary*) deepCopy {
    unsigned int count = [self count];
    id cObjects[count];
    id cKeys[count];
    NSEnumerator *e = [self keyEnumerator];
    unsigned int i = 0;
    id thisKey;
    while ((thisKey = [e nextObject]) != nil) {
        id obj = [self objectForKey:thisKey];
        if ([obj respondsToSelector:@selector(deepCopy)])
            cObjects[i] = [obj deepCopy];
        else
            cObjects[i] = [obj copy];
        if ([thisKey respondsToSelector:@selector(deepCopy)])
            cKeys[i] = [thisKey deepCopy];
        else
            cKeys[i] = [thisKey copy];
        ++i;
    }
    NSDictionary *ret = [[NSDictionary dictionaryWithObjects:cObjects forKeys:cKeys count:count] retain];
    // The newly-created dictionary retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i) {
        [cObjects[i] release];
        [cKeys[i] release];
    }
    return ret;
}
- (NSMutableDictionary*) mutableDeepCopy {
    unsigned int count = [self count];
    id cObjects[count];
    id cKeys[count];
    NSEnumerator *e = [self keyEnumerator];
    unsigned int i = 0;
    id thisKey;
    while ((thisKey = [e nextObject]) != nil) {
        id obj = [self objectForKey:thisKey];
        // Try to do a deep mutable copy, if this object supports it
        if ([obj respondsToSelector:@selector(mutableDeepCopy)])
            cObjects[i] = [obj mutableDeepCopy];
        // Then try a shallow mutable copy, if the object supports that
        else if ([obj respondsToSelector:@selector(mutableCopyWithZone:)])
            cObjects[i] = [obj mutableCopy];
        // Next try to do a deep copy
        else if ([obj respondsToSelector:@selector(deepCopy)])
            cObjects[i] = [obj deepCopy];
        // If all else fails, fall back to an ordinary copy
        else
            cObjects[i] = [obj copy];
        // I don't think mutable keys make much sense, so just do an ordinary copy
        if ([thisKey respondsToSelector:@selector(deepCopy)])
            cKeys[i] = [thisKey deepCopy];
        else
            cKeys[i] = [thisKey copy];
        ++i;
    }
    NSMutableDictionary *ret = [[NSMutableDictionary dictionaryWithObjects:cObjects forKeys:cKeys count:count] retain];
    // The newly-created dictionary retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i) {
        [cObjects[i] release];
        [cKeys[i] release];
    }
    return ret;
}
@end