目录

前言 1.1

Block概览 1.2

Block详解 1.3

Block正向开发 1.3.1

Block定义 1.3.2

Block类型 1.3.2.1

flags 1.3.2.2

invoke 1.3.2.3

signature 1.3.2.3.1

descriptor 1.3.2.4

imported variables 1.3.2.5

Block分析实例 1.4

YouTube逆向 1.4.1

Block心得 1.5

动态调试Block 1.5.1

相关objc函数 1.5.2

附录 1.6

参考资料 1.6.1

前言

iOS逆向开发：Block匿名函数

最新版本：v0.7

更新时间：20230707

简介

介绍iOS逆向期间会涉及到的Block匿名回调函数。先解释为何要研究Block；再详细介绍Block，包括正向开发时的如何写Block代码，然后是逆向时Block的结构定义，包括定义的代码和定义的结构图，再分别介绍每一部分的含义，包括Block类型、flags、invoke，以及其中的signature函数签名，以及descriptor、imported variables引用的量；再介绍iOS逆向期间所涉及到的Block最核心的内容是哪些，然后举例详细阐述如何通过Block搞懂函数调用逻辑，以及导入变量的参数传递，顺带介绍如何优化IDA伪代码。然后再整理Block相关的心得，比如动态调试期间，如何查看Block信息、和Block相关的objc函数。

源码+浏览+下载

本书的各种源码、在线浏览地址、多种格式文件下载如下：

HonKit源码

crifan/ios_re_objc_block: iOS逆向开发：Block匿名函数

如何使用此HonKit源码去生成发布为电子书

详见：crifan/honkit_template: demo how to use crifan honkit template and demo

在线浏览

iOS逆向开发：Block匿名函数 book.crifan.org

iOS逆向开发：Block匿名函数 crifan.github.io

离线下载阅读

iOS逆向开发：Block匿名函数 PDF

iOS逆向开发：Block匿名函数 ePub

iOS逆向开发：Block匿名函数 Mobi

版权和用途说明

此电子书教程的全部内容，如无特别说明，均为本人原创。其中部分内容参考自网络，均已备注了出处。如发现有侵权，请通过邮箱联系我 admin 艾特 crifan.com，我会尽快删除。谢谢合作。

各种技术类教程，仅作为学习和研究使用。请勿用于任何非法用途。如有非法用途，均与本人无关。

鸣谢

感谢我的老婆陈雪的包容理解和悉心照料，才使得我crifan有更多精力去专注技术专研和整理归纳出这些电子书和技术教程，特此鸣谢。

其他

作者的其他电子书

本人crifan还写了其他150+本电子书教程，感兴趣可移步至：

crifan/crifan_ebook_readme: Crifan的电子书的使用说明

关于作者

关于作者更多介绍，详见：

关于CrifanLi李茂 – 在路上

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-07-07 09:47:24

Block概览

Block概览

Block

名称

官方标准叫法其实是：Blocks

大家常简称为：Block=代码块

是什么：

Objc的对象

iOS的ObjC中的一个将数据与相关行为相结合的对象

(ObjC对）C语言扩展

Blocks是添加到C、Objective-C和C++的语言级功能，它允许您创建不同的代码段，这些代码段可以像值一样传递给方法或函数。

它们还具有从封闭范围捕获值的能力

类似：其他语言中的闭包=closure、lambda

功能和用途：用于创建匿名函数，实现函数的异步（或同步）调用

相关底层机制

Block可以从局部变量中获取值； 发生这种情况时，必须动态分配内存。

初始分配是在堆栈Stack上完成的，但是运行时提供了一个Block_copy函数，给定一个块指针，该函数要么将底层块对象复制到堆Heap，将其引用计数设置为 1 并返回新的块指针，要么（如果Block对象已经在堆Heap上）将其引用计数增加 1

配对函数是Block_release，它将引用计数减少 1，如果计数达到零并且在堆Heap上，则销毁该对象

官网

Working with Blocks

Objective-C Automatic Reference Counting (ARC) — Clang 16.0.0git documentation

为何要研究ObjC中的Block？

iOS逆向期间，最初是要找到某函数被调用的地方，通过函数调用堆栈，找到最后，发现找不下去了

之后发现其实是Block的机制实现的：Block = 实现了函数（同步或）异步的回调

TODO：

找到Block的函数如何被调用的 = Block函数的上层调用函数

【整理】iOS逆向调试心得：如何找到Block的_pthread_wqthread和_dispatch_call_block_and_release的函数调用最初来源

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 15:16:04

Block详解

Block详解

此处详细介绍Block的相关内容。

主要包括：

iOS的ObjC的正向开发时：Block代码是什么样的

iOS的ObjC的逆向时：Block的详细定义，以及每个字段的含义是什么

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:36:50

Block正向开发

Block正向开发

Block函数定义：

double (^multiplyTwoValues)(double, double);

Block函数写法：

^ (double firstValue, double secondValue) {
    return firstValue * secondValue;
}

显示指定返回值类型：

^ double (double firstValue, double secondValue) {
    return firstValue * secondValue;
}

调用：

double (^multiplyTwoValues)(double, double) =
                          ^(double firstValue, double secondValue) {
                              return firstValue * secondValue;
                          };

double result = multiplyTwoValues(2,4);

NSLog(@"The result is %f", result);

可以从作用域内，直接引用值：

- (void)testMethod {
    int anInteger = 42;

    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };

    testBlock();
}

TODO：

继续整理 *【整理】iOS的ObjC的基础知识：正向开发时Block相关知识

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:34:17

Block定义

Block定义

TODO：

【记录】iOS中的Block的定义画出结构图

Block结构体定义

来源=官网源码

Block_private.h

旧：https://opensource.apple.com/source/libclosure/libclosure-38/Block_private.h

新：https://opensource.apple.com/source/libclosure/libclosure-63/Block_private.h

TODO：更新版，记得还有额外的属性，抽空也去整理过来

-> 合并后的最新的理解：

Block定义的代码

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

// merged new layout
struct Block_descriptor {
    // Block_descriptor_1
    uintptr_t reserved;
    uintptr_t size;

    // Block_descriptor_2
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);

    // Block_descriptor_3
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

Block定义的结构图

自己的详细图

在线浏览

iOS的Block定义的结构图 | ProcessOn免费在线作图

别人的简单的图

TODO：

【整理】iOS逆向心得：ObjC中Block的定义

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-07-07 09:42:47

Block类型

Block类型

Block有多种类型：

Block类型

大类：3类

global

malloc

stack

具体：很多种

_NSConcreteGlobalBlock

_NSConcreteMallocBlock

_NSConcreteStackBlock

_NSConcreteAutoBlock

_NSConcreteFinalizingBlock

其他相关

_NSConcreteWeakBlockVariable

最常见的3种Block类型

文字

_NSConcreteGlobalBlock

_NSConcreteMallocBlock

_NSConcreteStackBlock

图

Block类型识别和转换

访问了auto变量的block是__NSStackBlock__类型

没有访问auto变量的block是__NSGlobalBlock__类型

而对__NSStackBlock__类型进行copy操作就会变为__NSMallocBlock__类型

iOS逆向中，IDA伪代码中最常看到的是：_NSConcreteStackBlock。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-07-07 09:45:26

flags

flags

Block中的flags是个标签，一个int型数值，不同的位bit表示不同的含义。

具体定义：

Block_private.h

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

struct Block_layout {
...
    volatile int32_t flags; // contains ref count
...
};

详见整理的Block结构图中的定义：

reference引用

    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

If the block’s flags includes BLOCK_NEEDS_FREE then the block is a heap block (you’ll see why shortly). In this case, all that needs doing is the reference count needs incrementing and then the same block returned.

如果flags中有BLOCK_NEEDS_FREE，则意味着：是个heap的block，即类型是_NSConcreteMallocBlock

    // 8
    result->isa = _NSConcreteMallocBlock;

Here the block’s isa pointer is set to be _NSConcreteMallocBlock, which means it’s a heap block

最后设置为：_NSConcreteMallocBlock

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 17:07:04

invoke

invoke

TODO：

【已解决】iOS逆向心得：Block的invoke函数调用时的函数参数

【整理】iOS逆向调试心得：Block的invoke函数调用

Block中的invoke，是核心的内容，决定了：具体要调用的（子）函数。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 15:57:57

signature

signature

TODO：

【整理】iOS逆向心得：Block的invoke函数的签名signature的含义

【已解决】iOS逆向：__block_literal_global

signature

v8@?0

函数定义：void ^();

v32@?0@"NSError"8@16@"TTHttpResponse"24

函数定义：void ^(NSError *, id, TTHttpResponse *);

v12@0:4@8

函数定义：- (void) setSomething:(id) anObject;

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-07-07 09:47:03

descriptor

descriptor

Block中的descriptor描述符，决定了Block有哪些额外的属性和具体定义。

Block_private.h

中的，3种Block_descriptor：Block_descriptor_1、Block_descriptor_2、Block_descriptor_3

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

合并后，就是前面Block详细定义所整理的：

// merged new layout
struct Block_descriptor {
    // Block_descriptor_1
    uintptr_t reserved;
    uintptr_t size;

    // Block_descriptor_2
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);

    // Block_descriptor_3
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

TODO：

Block_descriptor_1、Block_descriptor_2、Block_descriptor_3

【整理】iOS逆向心得：Block中Block_descriptor_1、Block_descriptor_2、Block_descriptor_3的计算逻辑和位置

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 17:06:56

imported variables

imported variables

TODO：

【已解决】iOS逆向心得：Block的invoke函数调用时的函数参数

【整理】iOS逆向心得：Block被调用时的函数参数

【已解决】研究抖音关注逻辑：Block函数调用时的参数传递逻辑之sub_93413B8调用sub_9341898

举例

【未解决】研究抖音关注逻辑：sub_93413B8调用___lldb_unnamed_symbol583654即sub_9341898的调用逻辑

计算（引用的变量）参数的个数

【已解决】iOS逆向心得：Block中能否和如何计算invoke函数的参数的个数

Block是通过imported variables=引用变量，去实现参数传递的。

最新的理解：parameters vs imported variables

parameters

Block（的invoke函数）被调用时，传入的各个参数

x0=Block本身

x1、x2、x3等等：才是invoke函数的参数

imported variables

Block中的额外引用的变量 的叫法：

Block中0x20之后额外引用的变量

标准叫法=Block定义中的注释的说法：imported variables = 导入的变量

=Block引用的变量

=Block额外引用的变量

=Block额外的参数

Block所引用的变量

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:41:28

Block分析实例

Block分析实例

Block最核心的部分

iOS逆向期间，和搞懂代码逻辑相关的，所涉及到的Block机制最核心的部分是

invoke = 调用了什么函数

知道A函数调用了B函数

明白后续会调用到B函数 = B函数后续会同步或异步执行

imported variables = 额外传入了什么参数

imported variables=引用的变量=导入的变量=额外传入的参数

A函数调用B函数，往往有些额外的参数，由于是匿名函数，无法像普通函数调用传递参数，所以底层实现机制是，在descriptor之后，按照顺序去摆放所引用的参数=导入的变量=imported variables

具体的说就是，在block+0x20之后，可能有0或多个，引用的参数

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:27:42

YouTube逆向

YouTube逆向

此处通过具体实例来说明：

iOS逆向YouTube期间，如何搞懂Block异步函数调用，包括如何传递参数引用变量=imported variables。

YouTube的函数调用逻辑是：

当 去掉广告过滤 时，调用到 MLServerABRLoader onesieDataLoader:didCompleteChunks:withError: 的顺序：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]
    sub_10380F8 = -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block
        sub_10381F8 = -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block + 344 =
            -[HAMBaseChunkLoadTask didCompleteWithError:] + 412
                -[MLServerABROnesieDataLoader chunks:didCompleteLoadingWithError:] + 92
                    MLServerABRLoader onesieDataLoader:didCompleteChunks:withError:
                        参数举例：
                            [case 1]:
                                arg1=0x0000000282b42000,
                                arg2=1 element,
                                arg3=domain: "com.google.ios.hamplayer" - code: 10000
                            [case 2]:
                                arg1=<MLServerABROnesieDataLoader: 0x282bd5e80>,
                                arg2=<__NSSingleObjectArrayI 0x2807d1510>(
                                        <HAMMP4InitializationChunk: 0x282bc3b80>
                                    )
                                arg3=NULL

其中的：

sub_10380F8 = -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

就是函数：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:] 的 Block回调函数

后续会异步执行

而后续继续调用了其他的Block回调函数：

sub_10381F8 = -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block

下面就从对应的IDA伪代码中，去分析和解释具体逻辑和过程：

Block通过invoke调用子函数

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]

第一层：-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]

IDA伪代码：

MLOnesieChunkLoadTask *__cdecl -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:](MLOnesieChunkLoadTask *self, SEL a2, id a3, id a4, id a5, id a6, id a7, id a8, id a9)
{
...
  id StackBlock; // [xsp+20h] [xbp-C0h]
  __int64 blockFlags; // [xsp+28h] [xbp-B8h]
  __int64 (__fastcall *blockInvoke)(_QWORD *, __int64, char, char); // [xsp+30h] [xbp-B0h]
  void *blockDescriptor; // [xsp+38h] [xbp-A8h]
...

  StackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10380F8;
  blockDescriptor = &unk_4EDE2F8;
  v21 = objc_retain_3E842C8(v19);
...
  StackBlock_ = objc_retainBlock_3E842F8(&StackBlock);
  if ( objc_msgSend_3E84298(v17, "requestDataWithRange:dataBlock:", v25, StackBlock_) & 1 )
...
}

而其中的Block中被调用的，后续会异步执行的函数是：

sub_10380F8

就是此处的，iOS逆向期间，调试找出的，函数调用堆栈中的：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

对应的真正触发调用子函数的代码是：

objc_msgSend_3E84298(v17, "requestDataWithRange:dataBlock:", v25, StackBlock_)

对应的v17 requestDataWithRange:dataBlock:的内部，会有dispatch_async之类的真正触发调用此处的Block的invoke函数

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

第二层：-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

IDA伪代码是：

void __fastcall sub_10380F8(_QWORD *a1, __int64 a2, char a3, char a4)
{
...
  struct objc_object *v7; // x23
  struct objc_object *v8; // x19
  id stackBlock; // [xsp+8h] [xbp-88h]
  __int64 blockFlags; // [xsp+10h] [xbp-80h]
  void *(__fastcall *blockInvoke)(__int64); // [xsp+18h] [xbp-78h]
  void *blockDescriptor; // [xsp+20h] [xbp-70h]
...

  stackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10381F8;
  blockDescriptor = &unk_4EDE2C8;
...
  v7 = v6[4];
...
  dispatch_async_3E82B64(v7, &stackBlock);
...
}

而此Block中被调用的函数是：

sub_10381F8

也正好对应着，调试时通过函数调用堆栈找到的：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block

而真正触发子函数调用的代码是：

dispatch_async_3E82B64(v7, &stackBlock);

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block

第三层：-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block

IDA伪代码是：

void __fastcall sub_10381F8(__int64 a1)
{
  __int64 v1; // x19
  void *v2; // x0
  __int64 v3; // x2
  struct objc_object *v4; // x0
  id v5; // x0
  struct objc_object *v6; // x20
  struct objc_object *v7; // x0
  struct objc_object *v8; // x21
  struct objc_object *v9; // x20
  struct objc_object *v10; // x0
  __int64 v11; // [xsp+0h] [xbp-40h]
  __objc2_class *v12; // [xsp+8h] [xbp-38h]
  __int64 v13; // [xsp+10h] [xbp-30h]
  __objc2_class *v14; // [xsp+18h] [xbp-28h]

  v1 = a1;
  v2 = *(a1 + 32);
  if ( v2 )
  {
    *(*(*(v1 + 72) + 8LL) + 24LL) += objc_msgSend_3E84298(v2, "length");
    v3 = *(v1 + 32);
    v13 = *(v1 + 40);
    v14 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&v13, "didReceiveData:error:", v3, 0LL);
    if ( *(v1 + 48) )
    {
      if ( *(v1 + 80) )
      {
        v4 = objc_msgSend_3E84298(*(v1 + 56), "firstObject");
        v5 = objc_retainAutoreleasedReturnValue_3E842EC(v4);
        v6 = v5;
        v7 = objc_msgSend_3E84298(v5, "format");
        v8 = objc_retainAutoreleasedReturnValue_3E842EC(v7);
        objc_release_3E842BC(v6);
        objc_msgSend_3E84298(*(v1 + 40), "logLatencyTick:format:", *(v1 + 48), v8);
        objc_release_3E842BC(v8);
      }
    }
  }
  if ( *(v1 + 81) )
  {
    if ( objc_msgSend_3E84298(*(v1 + 64), "isBounded")
      && objc_msgSend_3E84298(*(v1 + 64), "length") == *(*(*(v1 + 72) + 8LL) + 24LL) )
    {
      v9 = 0LL;
    }
    else
    {
      v10 = objc_msgSend_3E84298(&OBJC_CLASS___NSError, "HAMErrorWithCode:", 10000LL);
      v9 = objc_retainAutoreleasedReturnValue_3E842EC(v10);
    }
    v11 = *(v1 + 40);
    v12 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&v11, "didCompleteWithError:", v9, v11, &OBJC_CLASS___MLOnesieChunkLoadTask);
    objc_release_3E842BC(v9);
  }
}

进一步的，从伪代码中，可以看出此函数的大概逻辑是：

判断收到的数据？是否出错，如果出错，则报错。

-》由此实现，iOS逆向期间，一点点找到被调用的函数，期间可能会涉及到Block的异步调用，搞懂函数的代码逻辑。

Block通过imported variables传递参数

接下来继续分析：Block的引用变量的传递过程

而如何搞懂此处Block的函数异步调用时，传递了哪些额外参数= imported variables=引用变量？

核心思路是：

从block+0x20往后，如果有连续的变量赋值，那基本上就是对应所引用的参数

同时，也去被调用的函数中，互相对照，才能确定

被调用函数中，如果从block+0x20之后位置获取值，也就是，获取对应的imported variables，就的确表示对应的值是引用的变量了

对应此处具体的过程就是：

MLOnesieChunkLoadTask initWithRange传递给sub_10380F8

先确认block+0x20的位置

先从Block的起始位置去算起：

MLOnesieChunkLoadTask *__cdecl -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:](MLOnesieChunkLoadTask *self, SEL a2, id a3, id a4, id a5, id a6, id a7, id a8, id a9)
{
...
  id StackBlock; // [xsp+20h] [xbp-C0h]
  __int64 blockFlags; // [xsp+28h] [xbp-B8h]
  void (__fastcall *blockInvoke)(_QWORD *, __int64, char, char); // [xsp+30h] [xbp-B0h]
  void *blockDescriptor; // [xsp+38h] [xbp-A8h]

  StackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10380F8;
  blockDescriptor = &unk_4EDE2F8;
  StackBlock = _NSConcreteStackBlock;

可以算出：

[block+0x0] = Block自身 = _NSConcreteStackBlock

此处：[xsp+20h] == sp+0x20

[block+0x08] = flags = 0xC2000000LL

此处：[xsp+28h] == sp+0x28

[block+0x10] = invoke = sub_10380F8

此处：[xsp+30h] == sp+0x30

[block+0x18] = descriptor = &unk_4EDE2F8

此处：[xsp+38h] == sp+0x38

继续计算出imported variable：

[block+0x20] = 第1个引用变量

此处：[xsp+40h] = sp+0x40 的 v39

[block+0x28] = 第2个引用变量

此处：[xsp+48h] = sp+0x48 的 v40

[block+0x30] = 第3个引用变量

此处：[xsp+50h] = sp+0x50 的 v41

[block+0x38] = 第4个引用变量

此处：[xsp+58h] = sp+0x58 的 v42

[block+0x40] = 第5个引用变量

此处：[xsp+60h] = sp+0x60 的 object

注：此处变量名之所以叫object，而不是叫做v43，其实是反编译器尝试理解代码逻辑，并给变量命令，结果由于（本身就很难）完全猜出代码逻辑，所以变量名命名的不是很合适，此种情况属于正常现象

[block+0x48] = 第6个引用变量

此处：[xsp+68h] = sp+0x68 的 v44

[block+0x50] = 第7个引用变量

此处：[xsp+70h] = sp+0x70 的 v45

[block+0x58] = 第8个引用变量

此处：[xsp+78h] = sp+0x78 的 v46

[block+0x60] = 第9个引用变量

此处：[xsp+80h] = sp+0x80 的 v47

[block+0x68] = 第10个引用变量

此处：[xsp+88h] = sp+0x88 的 v48

需要说明的是，一般Block的引用变量的个数，也就2，3个左右，此处看起来引用变量多达10个

而目前暂时不是完全确定，后续变量的确是引用这么多变量

->需要后续看Block中被调用函数的实际使用情况，才能确定，到底引用了几个变量

继续分析Block的引用变量：

代码稍微优化（改名）后是：

void __fastcall sub_10380F8(_QWORD *inputBlock, __int64 a2, char a3, char a4)
{
  char v4; // w19
  char v5; // w20
  _QWORD *curBlock; // x21
  struct objc_object *v7; // x23
  struct objc_object *v8; // x19
  id stackBlock; // [xsp+8h] [xbp-88h]
  __int64 blockFlags; // [xsp+10h] [xbp-80h]
  void (__fastcall *blockInvoke)(__int64); // [xsp+18h] [xbp-78h]
  void *blockDescriptor; // [xsp+20h] [xbp-70h]
  id v13; // [xsp+28h] [xbp-68h]
  id v14; // [xsp+30h] [xbp-60h]
  id v15; // [xsp+38h] [xbp-58h]
  id v16; // [xsp+40h] [xbp-50h]
  id object; // [xsp+48h] [xbp-48h]
  __int64 v18; // [xsp+50h] [xbp-40h]
  char v19; // [xsp+58h] [xbp-38h]
  char v20; // [xsp+59h] [xbp-37h]

  v4 = a4;
  v5 = a3;
  curBlock = inputBlock;
  stackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10381F8;
  blockDescriptor = &unk_4EDE2C8;
  v13 = objc_retain_3E842C8(a2);
  v18 = curBlock[9];
  v7 = curBlock[4];
  v14 = objc_retain_3E842C8(curBlock[5]);
  v15 = objc_retain_3E842C8(curBlock[6]);
  v19 = v4;
  v16 = objc_retain_3E842C8(curBlock[7]);
  v20 = v5;
  object = objc_retain_3E842C8(curBlock[8]);
  v8 = objc_retain_3E842C8(v13);
  dispatch_async_3E82B64(v7, &stackBlock);
...
}

此处可以看出其中的：

curBlock[4]

curBlock[5]

curBlock[6]

curBlock[7]

curBlock[8]

curBlock[9]

就是：

来自上层的Block所传入的引用变量=imported variables

同时也要清楚，为何是从curBlock[4]的4开始，而不是从0开始，则是因为：

从0开始的0到3，都是Block相关的属性

curBlock[0] = block + 0x0 = Block

curBlock[1] = block + 0x8 = flags

curBlock[2] = block + 0x10 = invoke

curBlock[3] = block + 0x18 = descriptor

所以后续的imported variables才是：

curBlock[4] = block + 0x20

curBlock[5] = block + 0x28

curBlock[6] = block + 0x30

curBlock[7] = block + 0x38

curBlock[8] = block + 0x40

curBlock[9] = block + 0x48

至此，就清楚了，从：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]

调用了Block，其中：

被调用的函数是：sub_10380F8

== -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

传入的参数=引用的变量

[block+0x20] = 第1个 引用变量 ：当时的 [xsp+40h] = sp +0x40 的 v39

被调用函数sub_10380F8中所引用的：curBlock[4]

[block+0x28] = 第2个引用变量 ：当时的 [xsp+48h] = sp +0x48 的 v40

被调用函数sub_10380F8中所引用的：curBlock[5]

[block+0x30] = 第3个引用变量 ：当时的 [xsp+50h] = sp +0x50 的 v41

被调用函数sub_10380F8中所引用的：curBlock[6]

[block+0x38] = 第4个引用变量 ：当时的 [xsp+58h] = sp +0x58 的 v42

被调用函数sub_10380F8中所引用的：curBlock[7]

[block+0x40] = 第5个引用变量 ：当时的 [xsp+60h] = sp +0x60 的 object

被调用函数sub_10380F8中所引用的：curBlock[8]

[block+0x48] = 第6个引用变量 ：当时的 [xsp+68h] = sp +0x68 的 v44

被调用函数sub_10380F8中所引用的：curBlock[9]

至此，其中函数调用的大体逻辑，就清楚了：

-[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]

其中用Block回调了函数：sub_10380F8 （= -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block ）

且通过imported variables传入了6个参数变量

优化IDA伪代码

接着，也就可以继续去优化代码了：

给对应的变量改名字-》变成人类易读的代码-》让看代码的你自己更容易读懂代码逻辑

MLOnesieChunkLoadTask initWithRange

MLOnesieChunkLoadTask *__cdecl -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:](MLOnesieChunkLoadTask *self, SEL a2, id a3, id a4, id a5, id a6, id a7, id a8, id a9)
{
...
  id StackBlock; // [xsp+20h] [xbp-C0h]
  __int64 blockFlags; // [xsp+28h] [xbp-B8h]
  void (__fastcall *blockInvoke)(_QWORD *, __int64, char, char); // [xsp+30h] [xbp-B0h]
  void *blockDescriptor; // [xsp+38h] [xbp-A8h]
  id importedVar1; // [xsp+40h] [xbp-A0h]
  id importedVar2; // [xsp+48h] [xbp-98h]
  id importedVar3; // [xsp+50h] [xbp-90h]
  id importedVar4; // [xsp+58h] [xbp-88h]
  id importedVar5; // [xsp+60h] [xbp-80h]
  __int64 *importedVar6; // [xsp+68h] [xbp-78h]
...

  StackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10380F8;
  blockDescriptor = &unk_4EDE2F8;
  v21 = objc_retain_3E842C8(v19);
  importedVar1 = v21;
  importedVar6 = &v45;
  v22 = objc_retain_3E842C8(v14);
  importedVar2 = v22;
  v23 = objc_retain_3E842C8(v20);
  importedVar3 = v23;
  v24 = objc_retain_3E842C8(v16);
  importedVar4 = v24;
  v25 = objc_retain_3E842C8(v15);
  importedVar5 = v25;
  StackBlock_ = objc_retainBlock_3E842F8(&StackBlock);
...
}

sub_10380F8

void __fastcall sub_10380F8(_QWORD *inputBlock, __int64 a2, char a3, char a4)
{
...
  _QWORD *curBlock; // x21
  struct objc_object *inputImportedVar1; // x23
  struct objc_object *v8; // x19
  id stackBlock; // [xsp+8h] [xbp-88h]
  __int64 blockFlags; // [xsp+10h] [xbp-80h]
  void (__fastcall *blockInvoke)(__int64); // [xsp+18h] [xbp-78h]
  void *blockDescriptor; // [xsp+20h] [xbp-70h]
  id v13; // [xsp+28h] [xbp-68h]
  id inputImportedVar2; // [xsp+30h] [xbp-60h]
  id inputImportedVar3; // [xsp+38h] [xbp-58h]
  id inputImportedVar4; // [xsp+40h] [xbp-50h]
  id inputImportedVar5; // [xsp+48h] [xbp-48h]
  __int64 inputImportedVar6; // [xsp+50h] [xbp-40h]
...

  curBlock = inputBlock;
  stackBlock = _NSConcreteStackBlock;
  blockFlags = 0xC2000000LL;
  blockInvoke = sub_10381F8;
  blockDescriptor = &unk_4EDE2C8;
  v13 = objc_retain_3E842C8(a2);
  inputImportedVar6 = curBlock[9];
  inputImportedVar1 = curBlock[4];
  inputImportedVar2 = objc_retain_3E842C8(curBlock[5]);
  inputImportedVar3 = objc_retain_3E842C8(curBlock[6]);
  v19 = v4;
  inputImportedVar4 = objc_retain_3E842C8(curBlock[7]);
  v20 = v5;
  inputImportedVar5 = objc_retain_3E842C8(curBlock[8]);
  v8 = objc_retain_3E842C8(v13);
  dispatch_async_3E82B64(inputImportedVar1, &stackBlock);
...
}

sub_10381F8

以及，用上述同样的方法去分析，之后的

sub_10380F8

= -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block

所调用的Block函数：

sub_10381F8

= -[MLOnesieChunkLoadTask initWithRange:chunks:dataBuffer:cache:delegate:queue:latencyLogger:]_block_block

中的Block引用变量，然后分析完毕后，再去优化代码，效果是这样的：

void __fastcall sub_10381F8(__int64 inputBlock)
{
  __int64 curBlock; // x19
  void *importedVar1_; // x0
  __int64 importedVar1; // x2
  struct objc_object *v4; // x0
  id v5; // x0
  struct objc_object *v6; // x20
  struct objc_object *v7; // x0
  struct objc_object *v8; // x21
  struct objc_object *v9; // x20
  struct objc_object *v10; // x0
  __int64 importedVar2_; // [xsp+0h] [xbp-40h]
  __objc2_class *v12; // [xsp+8h] [xbp-38h]
  __int64 importedVar2; // [xsp+10h] [xbp-30h]
  __objc2_class *v14; // [xsp+18h] [xbp-28h]

  curBlock = inputBlock;
  importedVar1_ = *(inputBlock + 0x20);
  if ( importedVar1_ )
  {
    *(*(*(curBlock + 0x48) + 8LL) + 0x18LL) += objc_msgSend_3E84298(importedVar1_, "length");
    importedVar1 = *(curBlock + 0x20);
    importedVar2 = *(curBlock + 0x28);
    v14 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&importedVar2, "didReceiveData:error:", importedVar1, 0LL);
    if ( *(curBlock + 0x30) )                   // = importedVar3
    {
      if ( *(curBlock + 0x50) )                 // importedVar7
      {
        v4 = objc_msgSend_3E84298(*(curBlock + 0x38), "firstObject");// importedVar4
        v5 = objc_retainAutoreleasedReturnValue_3E842EC(v4);
        v6 = v5;
        v7 = objc_msgSend_3E84298(v5, "format");
        v8 = objc_retainAutoreleasedReturnValue_3E842EC(v7);
        objc_release_3E842BC(v6);
        objc_msgSend_3E84298(*(curBlock + 0x28), "logLatencyTick:format:", *(curBlock + 0x30), v8);
        objc_release_3E842BC(v8);
      }
    }
  }
  if ( *(curBlock + 0x51) )
  {
    if ( objc_msgSend_3E84298(*(curBlock + 0x40), "isBounded")// importedVar5
      && objc_msgSend_3E84298(*(curBlock + 0x40), "length") == *(*(*(curBlock + 0x48) + 8LL) + 24LL) )// curBlock + 0x48 = importedVar6
    {
      v9 = 0LL;
    }
    else
    {
      v10 = objc_msgSend_3E84298(&OBJC_CLASS___NSError, "HAMErrorWithCode:", 10000LL);
      v9 = objc_retainAutoreleasedReturnValue_3E842EC(v10);
    }
    importedVar2_ = *(curBlock + 0x28);
    v12 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&importedVar2_, "didCompleteWithError:", v9, importedVar2_, &OBJC_CLASS___MLOnesieChunkLoadTask);
    objc_release_3E842BC(v9);
  }
}

sub_10381F8进一步优化

以及，在满足条件：

对于objc_msgSend等逻辑一定的了解

加上此处YouTube的头文件的配合：查找相关函数，可以确定是哪个类

其他相关知识

可以进一步优化为：

void __fastcall sub_10381F8(__int64 inputBlock)
{
  __int64 curBlock; // x19
  void *receivedData; // x0
  __int64 receivedData_; // x2
  struct objc_object *v4; // x0
  id v5; // x0
  struct objc_object *v6; // x20
  struct objc_object *v7; // x0
  struct objc_object *v8; // x21
  struct objc_object *newHamError_; // x20
  struct objc_object *newHamError; // x0
  __int64 curMLOnesieChunkLoadTask_; // [xsp+0h] [xbp-40h]
  __objc2_class *v12; // [xsp+8h] [xbp-38h]
  __int64 curMLOnesieChunkLoadTask; // [xsp+10h] [xbp-30h]
  __objc2_class *v14; // [xsp+18h] [xbp-28h]

  curBlock = inputBlock;
  receivedData = *(inputBlock + 0x20);
  if ( receivedData )
  {
    *(*(*(curBlock + 0x48) + 8LL) + 0x18LL) += objc_msgSend_3E84298(receivedData, "length");
    receivedData_ = *(curBlock + 0x20);
    curMLOnesieChunkLoadTask = *(curBlock + 0x28);
    v14 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&curMLOnesieChunkLoadTask, "didReceiveData:error:", receivedData_, 0LL);
    if ( *(curBlock + 0x30) )                   // = importedVar3 = LatencyTick
    {
      if ( *(curBlock + 0x50) )                 // importedVar7
      {
        v4 = objc_msgSend_3E84298(*(curBlock + 0x38), "firstObject");// importedVar4
        v5 = objc_retainAutoreleasedReturnValue_3E842EC(v4);
        v6 = v5;
        v7 = objc_msgSend_3E84298(v5, "format");
        v8 = objc_retainAutoreleasedReturnValue_3E842EC(v7);
        objc_release_3E842BC(v6);
        objc_msgSend_3E84298(*(curBlock + 0x28), "logLatencyTick:format:", *(curBlock + 0x30), v8);
        objc_release_3E842BC(v8);
      }
    }
  }
  if ( *(curBlock + 0x51) )                     // importedVar8
  {
    if ( objc_msgSend_3E84298(*(curBlock + 0x40), "isBounded")// importedVar5 = HAMRange
      && objc_msgSend_3E84298(*(curBlock + 0x40), "length") == *(*(*(curBlock + 0x48) + 8LL) + 0x18LL) )// curBlock + 0x48 = importedVar6
    {
      newHamError_ = 0LL;
    }
    else
    {
      newHamError = objc_msgSend_3E84298(&OBJC_CLASS___NSError, "HAMErrorWithCode:", 10000LL);
      newHamError_ = objc_retainAutoreleasedReturnValue_3E842EC(newHamError);
    }
    curMLOnesieChunkLoadTask_ = *(curBlock + 0x28);
    v12 = &OBJC_CLASS___MLOnesieChunkLoadTask;
    objc_msgSendSuper2(&curMLOnesieChunkLoadTask_, "didCompleteWithError:", newHamError_);
    objc_release_3E842BC(newHamError_);
  }
}

相对来说，更加明确了代码大概的逻辑：

判断接受到的data数据，是否为空

如果不为空，继续处理

如果有错误，则log记录延迟？lantency的tick

如果curBlock + 0x51不为空

处理HAMRange相关的，判断是isBounded 且length长度符合预期

则表示error为空

否则表示有error错误

则继续去生成错误，错误码是10000

并报错：MLOnesieChunkLoadTask_ didCompleteWithError:

-》如果加上前后文和相关代码逻辑，就会越加的，逐渐的，彻底搞懂：代码逻辑。

如此，iOS逆向中，研究函数调用时，常会用到Block：（匿名的）异步函数调用

加上imported variables，搞清楚参数的传递

和上下文代码的逻辑和类、函数等内容

才能真正的，一点点的，搞懂代码逻辑调用，和具体函数参数等详细的代码逻辑。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:27:09

Block心得

Block心得

TODO：

【已解决】Xcode中lldb调试遇到Block类型变量__NSMallocBlock__

【整理】iOS的ObjC的Block函数：_Block_object_dispose

【整理】IDA使用心得：根据函数列表可以找到iOS的ObjC的Block的回调函数

【整理】iOS逆向心得：Block被调用时的函数参数

特殊情况

嵌套调用

【整理】iOS逆向心得：Block嵌套调用举例

Block中调用其他Block

【整理】iOS逆向心得：Block函数中调用引用变量中的其他Block函数

有些Block只有invoke，没有copy和dispose

举例：

是正常的，因为此时：

BLOCK_HAS_COPY_DISPOSE = 0

表示没有copy和dispose

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:52:15

动态调试Block

动态调试Block

TODO：

【整理】iOS逆向心得：Block的invoke函数的签名signature的含义

把如何动态调试查看函数签名的内容整理过来

查看Block详情

【已解决】研究抖音关注逻辑：用Block的调试函数去查看Block详情

把自己写的，打印Block各个属性详情的代码，整理过来

举例：

(lldb) reg r x0
      x0 = 0x000000014726ef70
(lldb) po 0x000000014726ef70
<__NSMallocBlock__: 0x14726ef70>
 signature: "v32@?0@"NSError"8@16@"TTHttpResponse"24"
 invoke   : 0x111735758 (/private/var/containers/Bundle/Application/1FFDC079-CC8A-4219-955A-E01C73207969/Aweme.app/Frameworks/AwemeCore.framework/AwemeCore`-[MKMapView(AWEMap) awe_screenScope])
 copy     : 0x108c97674 (/private/var/containers/Bundle/Application/1FFDC079-CC8A-4219-955A-E01C73207969/Aweme.app/Frameworks/AwemeCore.framework/AwemeCore`+[AWELaunchMainPlaceholder _generateBootLoaderLogs])
 dispose  : 0x108c9767c (/private/var/containers/Bundle/Application/1FFDC079-CC8A-4219-955A-E01C73207969/Aweme.app/Frameworks/AwemeCore.framework/AwemeCore`+[AWELaunchMainPlaceholder _generateBootLoaderLogs])

继续查看Block详情：

(lldb) po Block_size(0x14726ef70)
0x0000000000000030

(lldb) po _Block_has_signature(0x14726ef70)
0x0000000000000001

(lldb) po _Block_use_stret(0x14726ef70)
 nil
(lldb) po _Block_signature(0x14726ef70)
0x0000000107067dd6

(lldb) po _Block_layout(0x14726ef70)
 nil
(lldb) po _Block_extended_layout(0x14726ef70)
0x0000000000000100

(lldb) po _Block_tryRetain(0x14726ef70)
0x0000000000000001

(lldb) po _Block_isDeallocating(0x14726ef70)
 nil

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:03:44

相关objc函数

相关objc函数

objc_retainBlock

TODO：

【已解决】iOS基础知识：objc_retainBlock

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-10-25 16:04:14

附录

附录

下面列出相关参考资料。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2022-03-17 20:39:28

参考资料

参考资料

Working with Blocks

Objective-C Automatic Reference Counting (ARC) — Clang 16.0.0git documentation

Block 的 Block_descriptor_1 | SeanChense

Block 实现中的 flags | SeanChense

Block Implementation Specification — Clang 15.0.0git documentation (llvm.org)

iOS底层原理篇(十八) ---- Block底层原理_@Block_Smile的博客-CSDN博客

一种查看Block中引用的所有外部对象的实现方法 - K码农 (kmanong.top)

block那些事——block 内部结构(1/5) | 雪峰的blog (zxfcumtcs.github.io)

block那些事——block copy(2/5) | 雪峰的blog (zxfcumtcs.github.io)

iOS Block 总结 | Edgar's Blog (tbfungeek.github.io)

【译】《A look inside blocks Episode 3 (Block_copy)》 - 掘金

A look inside blocks: Episode 3 (Block_copy) - Matt Galloway

A look inside blocks: Episode 1 - Matt Galloway

A look inside blocks: Episode 2 - Matt Galloway

mikeash.com: Friday Q&A 2010-01-22: Toll Free Bridging Internals

objective c - Is there a way to wrap an ObjectiveC block into function pointer? - OGeek|极客中国-技术改变生活,极客改变未来

mikeash.com: Friday Q&A 2010-02-12: Trampolining Blocks with Mutable Code

iOS的Block定义的结构图 | ProcessOn免费在线作图,在线流程图,在线思维导图

data.m

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-07-07 09:45:56