飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 36086|回复: 44

[iOS] 【非越狱环境】钉钉躺床上打卡之水印篇

[复制链接]
  • TA的每日心情
    开心
    2023-1-18 15:50
  • 签到天数: 227 天

    [LV.7]常住居民III

    发表于 2017-3-30 10:49:02 | 显示全部楼层 |阅读模式
    钉钉iOS客户端签到图片修改(非越狱环境)

    本文续自上一篇【非越狱环境】钉钉躺床上打卡之WIFI篇https://www.chinapyg.com/forum.php?mod=viewthread&tid=89902

    一、背景
    最近一直有朋友抱怨外勤人员不仅要移动打卡,还要每天在考勤地上传一张照片,照片上有位置信息、日期信息等水印,如果可以提前多拍几张图片,以后每天选一张用于签到岂不是很high?顺手抽空处理了一下,最终实现从相册中随便选取照片,签到拍摄照片时直接替换掉,确保水印被打在相册中选取的那张图片上并上传。

    二、工具准备
    见上一篇。

    三、分析
    看了钉钉的签到拍照的界面,因为在图片上增加了位置信息和日期等水印,而且自定义了拍摄的UI,所以通用的技术是对AVFoundation框架下的AVCaptureStillImageOutput类下手,该类获取摄像头图片的方式是jpegStillImageNSDataRepresentation:,此方法理论上对所有app通用有效。

    四、编码
        我们在上一篇的代码基础上增加签到信息修改的相关代码,从相册选取图片的代码就不重复写了,直接github上搞了个:QBImagePickerController
    1、创建一个类DTCameraSettingViewController用于选取相册中的图片,代码如下:
    [Objective-C] 纯文本查看 复制代码
    #import "DTCameraSettingViewController.h"
    
    #import "QBImagePickerController.h"
    
    #import "JF_Helper.h"
    
    #import "UIImage+JFUtil.h"
    
    #import "ALAsset+JFUtil.h"
    
    #import "DTCameraHook.h"
    
    static NSString *image_base64_button = @"iVBORw0KGgoAAAANSUhEUgAAALQAAABACAYAAACzzl09AAAAAXNSR0IArs4c6QAA\
    AAlwSFlzAAAWJQAAFiUBSVIk8AAAABxpRE9UAAAAAgAAAAAAAAAgAAAAKAAAACAA\
    AAAgAAACF+d7zw0AAAHjSURBVHgB7JixTcNQFEVdUbEJA5AABWIH9qKgyg4YRB8x\
    hVdggQzw+c/EN0oq3yq3OEiIG+k9c3VyYn1nGC5+Hr/b3fazvW3GNm3Hdth8tMYv\
    DFIcmJ0sN7uj5eqFvqeXr1O76cPvKcXpwYdojQPlbLl7Mrmno8x7XWBsu6evdv/8\
    027PBnkBgSsTKCfLzX6C2C2+dqn3Z1Ivd+b+9/dhbC9X7sy/h8AqAuVqOVtil8Pz\
    Up1DFtOReRVHhoIIlLOLv/OZen4ArAe/fgsP6kkVCKwmsBw/yuWhvs0ow+tcsvoK\
    DEIgiMB8pv6/KU9DP3vMX83xABj0DlHFIlDuHs/Rh2E5f1hXYBgCYQTksUJYQepA\
    wCEgjxWcbWYhEEZAHiuEFaQOBBwC8ljB2WYWAmEE5LFCWEHqQMAhII8VnG1mIRBG\
    QB4rhBWkDgQcAvJYwdlmFgJhBOSxQlhB6kDAISCPFZxtZiEQRkAeK4QVpA4EHALy\
    WMHZZhYCYQTksUJYQepAwCEgjxWcbWYhEEZAHiuEFaQOBBwC8ljB2WYWAmEE5LFC\
    WEHqQMAhII8VnG1mIRBGQB4rhBWkDgQcAvJYwdlmFgJhBOSxQlhB6kDAISCPFZxt\
    ZiEQRkAeK4QVpA4EHAKLx38AAAD//zKoC3IAAAGsSURBVO2YsU0DQRQFLyKiEwrA\
    BgJED/RF4Mg9cCByiyrcghuggGX/6jwyooF78liy9E528P5ovPrrafPeWr0nXxII\
    JoDHhOBhrC4BPCbIRALBBPCYEDyM1SWAxwSZSCCYAB4TgoexugTwmCATCQQTwGNC\
    8DBWlwAeE2QigWACeEwIHsbqEsBjgkwkEEwAjwnBw1hdAnhMkIkEggngMSF4GKtL\
    AI8JMpFAMAE8JgQPY3UJ4DFBJhIIJoDHhOBhrC4BPCbIRALBBPCYEDyM1SWAxwSZ\
    SCCYAB4TgoexugTwmCATCQQTwGNC8DBWlwAeb+f2Uw/P3+1WLBJIJFDulsPl8rSZ\
    27Eenj7bfeIwdpZAuTtO6O7ytP1ob8vDXjQSSCTQD+X9OKG7y9PjV7sbQvdT+mFu\
    L4kD2fl6CZSzZ3/L5UGi7x67YfjcTkp9vXKkTV6udndPi7s7+r8e203/4HA2vY7w\
    2ku8KILIsBIC5eTYmZc1Y5H5UA7/qbhIPU5qxO5riFkGa3agtot/Ml+aXXvIuCj2\
    G2P/8vhLb80D2e26fnDDyXKzXwDZmS8E/gW5cu3DvOjXFwAAAABJRU5ErkJggg==";
    
    @interface DTCameraSettingViewController () <QBImagePickerControllerDelegate>
    
    @property (nonatomic, strong) NSMutableArray *array_photos;
    
    @property (nonatomic, strong) UIImageView *imageView_photo;
    
    @property (nonatomic, strong) UIButton *button_clean;
    
    @property (nonatomic, strong) UIButton *button_commit;
    
    @end
    
    @implementation DTCameraSettingViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.array_photos = [NSMutableArray array];
        
        UIImage *image = [UIImage imageWithContentsOfFile:jf_photoPath];
        
        if(image) {
            
            [self.array_photos addObject:image];
        }
        
        [self makeSubviews];
    }
    
    - (void)makeSubviews {
        
        self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)];
        
        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"选取" style:UIBarButtonItemStylePlain target:self action:@selector(selectPhotos:)];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        _imageView_photo = [[UIImageView alloc] init];
        _imageView_photo.frame = CGRectMake(0, 80, [UIScreen mainScreen].bounds.size.width, 200);
        [self.view addSubview:self.imageView_photo];
        
        NSData *decodedImageData = [[NSData alloc] initWithBase64EncodedString:image_base64_button options:NSDataBase64DecodingIgnoreUnknownCharacters];
        UIImage *decodedImage = [UIImage imageWithData:decodedImageData];
        
        self.button_clean = [UIButton buttonWithType:UIButtonTypeCustom];
        self.button_clean.frame = CGRectMake(25, _imageView_photo.frame.origin.y + _imageView_photo.frame.size.height + 15, 125, 44);
        [self.button_clean setBackgroundImage:[decodedImage stretchableImageWithLeftCapWidth:10 topCapHeight:10] forState:UIControlStateNormal];
        self.button_clean.titleLabel.font = [UIFont systemFontOfSize:15];
        [self.button_clean setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        [self.button_clean setTitle:@"清除" forState:UIControlStateNormal];
        [self.button_clean addTarget:self action:@selector(cleanPhoto:) forControlEvents:UIControlEventTouchUpInside];
        self.button_clean.hidden = YES;
        [self.view addSubview:self.button_clean];
        
        self.button_commit = [UIButton buttonWithType:UIButtonTypeCustom];
        self.button_commit.frame = CGRectMake(self.button_clean.frame.origin.x + self.button_clean.frame.size.width + 20, self.button_clean.frame.origin.y, 125, 44);
        [self.button_commit setBackgroundImage:[decodedImage stretchableImageWithLeftCapWidth:10 topCapHeight:10] forState:UIControlStateNormal];
        self.button_commit.titleLabel.font = [UIFont systemFontOfSize:15];
        [self.button_commit setTitleColor:_jf_HexRGB(0x38ADFF) forState:UIControlStateNormal];
        [self.button_commit setTitle:@"确定" forState:UIControlStateNormal];
        [self.button_commit addTarget:self action:@selector(commit:) forControlEvents:UIControlEventTouchUpInside];
        self.button_commit.hidden = YES;
        [self.view addSubview:self.button_commit];
        
        [self updatePhoto];
    }
    
    - (void)updatePhoto {
        
        if(self.array_photos && [self.array_photos count] > 0) {
            
            self.button_clean.hidden = NO;
            
            self.button_commit.hidden = NO;
            
            UIImage *image = [self.array_photos firstObject];
            
            self.imageView_photo.image = [image jf_scaleToSize:self.imageView_photo.bounds.size];
            
            self.button_clean.hidden = NO;
            
            self.button_commit.hidden = NO;
        }
        else {
            
            self.imageView_photo.image = nil;
            
            self.button_clean.hidden = YES;
            
            self.button_commit.hidden = YES;
        }
    }
    
    #pragma mark - QBImagePickerControllerDelegate
    
    - (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didSelectAsset:(ALAsset *)asset {
        
        [self.array_photos removeAllObjects];
        
        UIImage *image = [asset jf_fullResolutionImage];
        
        NSData *data = UIImagePNGRepresentation(image);
        image = [UIImage imageWithData:data];
        
        [self.array_photos addObject:image];
        
        [self updatePhoto];
        
        [imagePickerController.navigationController popToRootViewControllerAnimated:YES];
    }
    
    - (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets {
        
        [self.array_photos removeAllObjects];
        
        for (ALAsset *asset in assets) {
            
            UIImage *image = [asset jf_fullResolutionImage];
            
            [self.array_photos addObject:image];
        }
        
        [self updatePhoto];
        
        [imagePickerController.navigationController popToRootViewControllerAnimated:YES];
    }
    
    - (void)qb_imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController {
        
        [imagePickerController.navigationController popToRootViewControllerAnimated:YES];
    }
    
    #pragma mark -
    
    - (void)cancel:(id)sender {
        
        [self.navigationController dismissViewControllerAnimated:YES completion:nil];
    }
    
    - (void)selectPhotos:(id)sender {
        
        QBImagePickerController *imagePickerController = [[QBImagePickerController alloc] init];
        imagePickerController.delegate = self;
        imagePickerController.filterType = QBImagePickerControllerFilterTypePhotos;
        imagePickerController.allowsMultipleSelection = YES;
        imagePickerController.minimumNumberOfSelection = 1;
        imagePickerController.maximumNumberOfSelection = 1;
        [self.navigationController pushViewController:imagePickerController animated:YES];
    }
    
    - (void)cleanPhoto:(id)sender {
        
        [self.array_photos removeAllObjects];
        
        [[NSFileManager defaultManager] removeItemAtPath:jf_photoPath error:nil];
        
        [self updatePhoto];
    }
    
    - (void)commit:(id)sender {
        
        if(self.array_photos && [self.array_photos count] > 0) {
            
            UIImage *image = [self.array_photos firstObject];
            
            [DTCameraHook hookCameraWith:image];
        }
        
        [self.navigationController dismissViewControllerAnimated:YES completion:nil];
    }
    
    @end

    2、类DTCameraHook用户存储选取的图片和hook类AVCaptureStillImageOutput,代码如下:
    .h文件
    [Objective-C] 纯文本查看 复制代码
    #import <Foundation/Foundation.h>[/align]#import <UIKit/UIKit.h>
    
    #define jf_photoDirectory [NSTemporaryDirectory() stringByAppendingPathComponent:@"jf/photo"]
    
    #define jf_photoName @"jf_photo.png"
    
    #define jf_photoPath [NSString stringWithFormat:@"%@/%@", jf_photoDirectory, jf_photoName]
    
    @interface DTCameraHook : NSObject
    
    + (void)hookCameraWith:(UIImage *)image;
    
    @end
    .m文件
    [Objective-C] 纯文本查看 复制代码
    #import "DTCameraHook.h"[/align]
    #import "UIImage+JFUtil.h"
    
    #import "JF_Helper.h"
    
    #import "AVCaptureStillImageOutput+JFUtil.h"
    
    @interface DTCameraHook ()
    
    @end
    
    @implementation DTCameraHook
    
    + (void)createPhotoDirectory {
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        
        // 创建录音文件存放目录
        [fileManager createDirectoryAtPath:jf_photoDirectory
               withIntermediateDirectories:YES
                                attributes:nil
                                     error:nil];
    }
    
    + (void)hookCamera {
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            _jf_Hook_Class_Method(NSClassFromString(@"AVCaptureStillImageOutput"), NSSelectorFromString(@"jpegStillImageNSDataRepresentation:"), NSClassFromString(@"AVCaptureStillImageOutput"), NSSelectorFromString(@"jf_jpegStillImageNSDataRepresentation:"));
        });
    }
    
    + (void)hookCameraWith:(UIImage *)image {
        
        if(!image) return;
        
        [image jf_writeImageAtPath:jf_photoPath];
        
        [self hookCamera];
    }
    
    + (void)load {
        
        // 创建图片目录
        [self createPhotoDirectory];
        
        [self hookCamera];
    }
    
    @end


    五、注入、打包、签名
    操作步骤同上一篇,安装测试:
    IMG_0050.PNG
    IMG_0049.PNG
    IMG_0048.PNG

    可以看到成功替换了图片,此功能对所有拍照都有影响,所以签到完成之后需要把设置的签到图片清除,下次签到时再提前设置好即可。

    六、代码地址
    七、结束语
    好好上班,谨慎使用!

    评分

    参与人数 2威望 +8 收起 理由
    绞尽乳汁6 + 4 图片怎么替换的,我的怎么不变啊
    论坛管理员 + 4 PYG有你更精彩!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情
    开心
    5 天前
  • 签到天数: 361 天

    [LV.8]以坛为家I

    发表于 2017-3-30 12:33:15 | 显示全部楼层
    大神牛叉叉,有成品不?
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2018-12-29 14:24
  • 签到天数: 250 天

    [LV.8]以坛为家I

    发表于 2017-3-30 17:51:02 | 显示全部楼层
    好好上班,谨慎使用!哈哈!
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2021-9-29 15:05
  • 签到天数: 114 天

    [LV.6]常住居民II

    发表于 2017-3-31 08:40:21 | 显示全部楼层
    看来钉钉号称的安全级别是通信过程高
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2017-3-31 10:44
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2017-3-31 10:46:24 | 显示全部楼层
    厉害了,又出新了。
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-6 11:41
  • 签到天数: 1827 天

    [LV.Master]伴坛终老

    发表于 2017-3-31 16:21:20 | 显示全部楼层
    我单位可能也要使用钉钉了

    点评

    那你该庆幸再也不用早起了  详情 回复 发表于 2017-3-31 17:55
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2023-1-18 15:50
  • 签到天数: 227 天

    [LV.7]常住居民III

     楼主| 发表于 2017-3-31 17:55:27 | 显示全部楼层
    hu007 发表于 2017-3-31 16:21
    我单位可能也要使用钉钉了

    那你该庆幸再也不用早起了
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2020-12-30 08:38
  • 签到天数: 919 天

    [LV.10]以坛为家III

    发表于 2017-4-5 10:30:10 | 显示全部楼层
    来膜拜大神
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2023-1-18 15:50
  • 签到天数: 227 天

    [LV.7]常住居民III

     楼主| 发表于 2017-4-6 11:46:28 | 显示全部楼层
    zaq111 发表于 2017-4-6 11:33
    听说最近容易被后台检测到异常

    我这边有朋友用了半年了还没出现问题,不过还是要谨慎使用,好好上班
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表