zoukankan      html  css  js  c++  java
  • 4. 移动安全渗透测试-(Android逆向基础)

    4.1 smali 基础

    1、注释


    smali中使用#来代表注释一行
    例如:
    # const-string v0, "aaa" #这句不会被执行


    2、数据类型


    V void,只能用于返回值类型
    Z boolean
    B byte
    S short
    C char
    I int
    J long(64位)
    F float
    D double(64位)


    lit4、lit8、lit16、lit32、lit64表示字面值(直接赋值),数字是值所占用位的长度


    long和double的值占用两个寄存器、例:一个在v0寄存器的double值实际占用v0,v1两个寄存器


    boolean值的存储实际是1和0,1位真、0为假;boolean型的值实际是转成int型的值进行操作


    3、对象类型


    对象的表示则以L作为开头,格式是LpackageName/objectName;(注意必须有个分号跟在最后)

    例如String对象在smali中为:Ljava/lang/String;,

    其中java/lang对应java.lang包,String就是定义在该包中的一个对象。

    那类里面的内部类又如何在smali中引用呢?

    答案是:LpackageName/objectName$subObjectName;。也就是在内部类前加“$”符号。


    4、数组类型


    数组的表示方式是:在数据类型前加上前中括号“[”,

    [I 表示一个整型一维数组,相当于java中的int[]。

    对于多维数组,只要增加[就行了。[[I相当于int[][],[[[I相当于int[][][]。注意每一维的最多255个。

    对象数组的表示:[Ljava/lang/String;表示一个String对象数组String[]。


    5、方法


    表示形式:Lpackage/name/ObjectName;->MethodName(III)Z

    Lpackage/name/ObjectName;表示类型,MethodName是方法名。III为参数(在此是3个整型参数),Z是返回类型(bool型)。
    方法的参数是一个接一个的,中间没有隔开。

    foo ()V ->没错,这就是void foo()。
    foo (III)Z ->这个则是boolean foo(int, int, int)。
    foo (Z[I[ILjava/lang/String;J)Ljava/lang/String; ->看出来这是String foo (boolean, int[], int[], String, long) 了吗?


    6、参数


    表示形式:Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
    即包名,字段名和各字段类型


    7、寄存器


    在smali里的所有操作都必须经过寄存器来进行:

    本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2、...

    参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2、...

    特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…

    而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)

    本地寄存器没有限制,理论上是可以任意使用的

    vx vy vz表示某个寄存器 ,根据不同的指令可以访问16、256、64k寄存器


    8、寄存器操作指令


    return vx
    返回在vx寄存器的值。
    0F00 - return v0
    返回v0寄存器中的值。
    10
    return-wide vx
    返回在vx,vx+1寄存器的double/long值。
    1000 - return-wide v0
    返回v0,v1寄存器中的double/long值。
    11
    return-object vx
    返回在vx寄存器的对象引用。
    1100 - return-object v0
    返回v0寄存器中的对象引用。
    12
    const/4 vx, lit4
    存入4位常量到vx。
    1221 - const/4 v1, #int 2
    存入int型常量2到v1。目的寄存器在第二个字节的低4位,常量2在更高的4位。
    13
    const/16 vx, lit16
    存入16位常量到vx。
    1300 0A00 - const/16 v0, #int 10
    存入int型常量10到v0。
    14
    const vx, lit32
    存入int 型常量到vx。
    1400 4E61 BC00 - const v0, #12345678 // #00BC614E
    存入常量12345678到v0。
    15
    const/high16 v0, lit16
    存入16位常量到最高位寄存器,用于初始化float值。
    1500 2041 - const/high16 v0, #float 10.0 // #41200000
    存入float常量10.0到v0。该指令最高支持16位浮点数。
    16
    const-wide/16 vx, lit16
    存入int常量到vx,vx+1寄存器,扩展int型常量为long常量。
    1600 0A00 - const-wide/16 v0, #long 10
    存入long常量10到v0,v1寄存器。
    17
    const-wide/32 vx, lit32
    存入32位常量到vx,vx+1寄存器,扩展int型常量到long常量。
    1702 4e61 bc00 - const-wide/32 v2, #long 12345678 // #00bc614e
    存入long常量12345678到v2,v3寄存器。
    18
    const-wide vx, lit64
    存入64位常量到vx,vx+1寄存器。
    1802 874b 6b5d 54dc 2b00- const-wide v2, #long 12345678901234567 // #002bdc545d6b4b87
    存入long常量12345678901234567到v2,v3寄存器。
    19
    const-wide/high16 vx, lit16
    存入16位常量到最高16位的vx,vx+1寄存器,用于初始化double 值。
    1900 2440 - const-wide/high16 v0, #double 10.0 // #402400000
    存入double常量10.0到v0,v1。
    1A
    const-string vx, 字符串ID
    存入字符串常量引用到vx,通过字符串ID或字符串。
    1A08 0000 - const-string v8, "" // string@0000
    存入string@0000(字符串表#0条目)的引用到v8。
    1B
    const-string-jumbo
    未知注4
    1C
    const-class vx, 类型ID
    存入类对象常量到vx,通过类型ID或类型(如Object.class)。
    1C00 0100 - const-class v0, Test3 // type@0001
    存入Test3.class(类型ID表#1条目)的引用到v0。
    1D
    monitor-enter vx
    获得vx寄存器中的对象引用的监视器。
    1D03 - monitor-enter v3
    获得v3寄存器中的对象引用的监视器。

    9、数据运算指令


    add-type 加法指令
    sub-type 减法指令
    mul-type 乘法指令
    div-type 除法指令
    rem-type 取余
    neg-type 取反
    and-type 与运算指令
    or-type 或运算指令
    xor-type 异或元算指令
    shl-type 有符号左移指令
    shr-type 有符号右移指令
    ushr-type 无符号右移指令


    10、类型转换指令


    int-to-long 整形转为长整型
    float-to-int 单精度浮点型转为整形
    int-to-byte 整形转为字节类型
    neg-int 求补指令,对整数求补
    not-int 求反指令,对整数求反


    11、比较指令


    cmpl-float vAA,vBB,vCC 比较两个单精度的浮点数.如果vBB寄存器中的值大于vCC寄存器的值,则返回-1到vAA中,相等则返回0,小于返回1
    cmpg-float vAA,vBB,vCC 比较两个单精度的浮点数,如果vBB寄存器中的值大于vCC的值,则返回1,相等返回0,小于返回-1
    cmpl-double vAA,vBB,vCC 比较两个双精度浮点数,如果vBB寄存器中的值大于vCC的值,则返回-1,相等返回0,小于则返回1
    cmpg-double vAA,vBB,vCC 比较双精度浮点数,和cmpl-float的语意一致
    cmp-double vAA,vBB,vCC 等价与cmpg-double vAA,vBB,vCC指令

    12、if指令


    if-eq vA,vB,target vA,vB寄存器中的相等,等价于java中的if(a==b),比如if-eq v3,v10,002c表示如果条件成立,则跳转到current position+002c处.其余的类似
    if-ne vA,vB,target 等价与java中的if(a!=b)
    if-lt vA,vB,target vA寄存器中的值小于vB,等价于java中的if(a<b)
    if-gt vA,vB,target 等价于java中的if(a>b)
    if-ge vA,vB,target 等价于java中的if(a>=b)
    if-le vA,vB,target 等价于java中的if(a<=b)

    除了以上指令之外,Davilk还提供可一个零值条件指令,该指令用于和0比较,可以理解为将上面指令中的vB寄存器的值固定为0.

    if-eqz vAA,target 等价于java中的if(a==0)或者if(!a)
    if-nez vAA,target 等价于java中的if(a!=0)或者if(a)
    if-ltz vAA,target 等价于java中的if(a<0)
    if-gtz vAA,target 等价于java中的if(a>0)
    if-lez vAA,target 等价于java中的if(a<=0)
    if-gtz vAA,target 等价于java中的if(a>=0)


    13、数据传送指令


    move vA,vB 将vB寄存器的值赋值给vA寄存器,vA和vB寄存器都是4位
    move/from16 vAA,VBBBB 将vBBBB寄存器(16位)的值赋值给vAA寄存器(7位),from16表示源寄存器vBBBB是16位的
    move/16 vAAAA,vBBBB 将寄存器vBBBB的值赋值给vAAAA寄存器,16表示源寄存器vBBBB和目标寄存器vAAAA都是16位
    move-object vA,vB 将vB寄存器中的对象引用赋值给vA寄存器,vA寄存器和vB寄存器都是4位
    move-result vAA 将上一个invoke指令(方法调用)操作的单字(32位)非对象结果赋值给vAA寄存器
    move-result-wide vAA 将上一个invoke指令操作的双字(64位)非对象结果赋值给vAA寄存器
    mvoe-result-object vAA 将上一个invoke指令操作的对象结果赋值给vAA寄存器
    move-exception vAA 保存上一个运行时发生的异常到vAA寄存器


    14、数组操作指令


    new-array vA,vB,type@CCCC 创建指定类型与指定大小(vB寄存器指定)的数组,并将其赋值给vA寄存器
    fill-array-data vAA,+BBBBBBBB 用指定的数据填充数组,vAA代表数组的引用(数组第一个元素的地址)


    15、对象操作指令


    new-instance vAA,type@BBBB 构造一个指定类型的对象将器引用赋值给vAA寄存器.此处不包含数组对象
    instance-of vA,vB,type@CCCC 判断vB寄存器中对象的引用是否是指定类型,如果是,将v1赋值为1,否则赋值为0
    check-cast vAA,type@BBBB 将vAA寄存器中对象的引用转成指定类型,成功则将结果赋值给vAA,否则抛出ClassCastException异常.

    16、字段操作指令


    1.普通字段读写操作
    iget-byte vX,vY,filed_id 读取vY寄存器中的对象中的filed_id字段值赋值给vX寄存器
    iput-byte vX,vY,filed_id 设置vY寄存器中的对象中filed_id字段的值为vX寄存器的值
    iget-boolean vX,vY,filed_id
    iput-boolean vX,vY,filed_id
    iget-long vX,vY,filed_id
    iput-long vX,vY,filed_id
    2.静态字段读写操作
    sget-byte vX,vY,filed_id
    sput-byte vX,vY,filed_id
    sget-boolean vX,vY,filed_id
    sput-boolean vX,vY,filed_id
    sget-long vX,vY,filed_id
    sput-long vX,vY,filed_id


    17、方法调用指令


    invoke-direct{parameters},methodtocall 调用实例的直接方法,即private修饰的方法.此时需要注意{}中的第一个元素代表的是当前实例对象,即this,后面接下来的才是真正的参数.比如指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2当前实例对象,而v1,v4才是方法参数
    invoke-static{parameters},methodtocall 调用实例的静态方法,此时{}中的都是方法参数
    invoke-super{parameters},methodtocall 调用父类方法
    invoke-virtual{parameters},methodtocall 调用实例的虚方法,即public和protected修饰修饰的方法
    invoke-interface{parameters},methodtocall 调用接口方法

    invoke-direct/range,
    invoke-static/range,
    invoke-super/range,
    invoke-virtual/range,
    invoke-interface/range指令

    该类型指令和以上指令唯一的区别

    就是后者可以设置方法参数,可以使用的寄存器的范围,

    在参数多于四个时候使用.

    18、方法返回指令


    return-void 什么也不返回
    return vAA 返回一个32位非对象类型的值
    return-wide vAA 返回一个64位非对象类型的值
    return-object vAA 反会一个对象类型的引用


    19、同步指令


    指令 说明
    monitor-enter vAA 为指定对象获取锁操作
    monitor-exit vAA 为指定对象释放锁操作

    20、异常指令


    throw vAA 抛出vAA寄存器中指定类型的异常

    21、跳转指令


    goto +AA 无条件跳转到指定偏移处(AA即偏移量)
    packed-switch vAA,+BBBBBBBB 分支跳转指令.vAA寄存器中的值是switch分支中需要判断的,BBBBBBBB则是偏移表(packed-switch-payload)中的索引值,
    spare-switch vAA,+BBBBBBBB 分支跳转指令,和packed-switch类似,只不过BBBBBBBB偏移表(spare-switch-payload)中的索引值
    if-test vA,vB,+CCCC 条件跳转指令,用于比较vA和vB寄存器中的值,如果条件满足则跳转到指定偏移处(CCCC即偏移量),test代表比较规则,可以是eq.lt等.


    22、关键词


    关键词 说明
    .filed 定义字段
    .method…end method 定义方法
    .annotation…end annotation 定义注解
    .implements 定义接口指令
    .local 指定了方法内局部变量的个数
    .registers 指定方法内使用寄存器的总数
    .prologue 表示方法中代码的开始处
    .line 表示java源文件中指定行
    .paramter 指定了方法的参数
    .param 和.param


    23、类的基本信息


    .class public Lcom/disney/WMW/WMWActivity;
    .super Lcom/disney/common/BaseActivity;
    .source "WMWActivity.java"

    # interfaces
    .implements Lcom/burstly/lib/ui/IBurstlyAdListener;

    它是com.disney.WMW这个package下的一个类
    继承自com.disney.common.BaseActivity
    这是一个由WMWActivity.java编译得到的smali文件

    WMWActivity实现了一个com.burstly.lib.ui这个package下的IBurstyAdListener接口

    public class WMWActivity extends BaseActivity implements IBurstlyAdListener
    {
    }


    .class public Lcom/example/test/MainActivity;
    .super Landroid/app/Activity;
    .source "MainActivity.java"

    它是com.example.test这个package下的一个类
    继承自android.app.Activity
    这是一个由MainActivity.java编译得到的smali文件

    public class MainActivity extends Activity
    {
    }

    .class public Lcom/example/utils/PhoneUtil;
    .super Ljava/lang/Object;
    .source "PhoneUtil.java"

    它是com.example.utils这个package下的一个类
    这是一个由PhoneUtil.java编译得到的smali文件


    public class PhoneUtil
    {
    }

    24、接口描述


    #interfaces
    .implements <接口名称>

    # interfaces
    .implements Landroid/view/View$OnClickListener;

    25、字段


    普通字段
    此处非权限修饰符则可是final,volidate,transient.
    #instance fields
    .field <访问权限修饰符> [非权限修饰符] <字段名>:<字段类型>
    # instance fields
    .field private TAG:Ljava/lang/String;

    静态字段

    smali文件还为静态字段,普通字段分别添加#static field和#instan filed注释.
    #static fields
    .field <访问权限> static [修饰词] <字段名>:<字段类型>
    # static fields
    .field private static final pi:F = 3.14f

    26、定义的内部类


    # annotations
    .annotation system Ldalvik/annotation/MemberClasses;
    value = {
    Lcom/disney/WMW/WMWActivity$MessageHandler;,
    Lcom/disney/WMW/WMWActivity$FinishActivityArgs;
    }
    .end annotation


    它有两个成员内部类——MessageHandler和FinishActivityArgs

    .annotation build Landroid/annotation/SuppressLint;
    value = {
    "SimpleDateFormat"
    }
    .end annotation

    屏蔽android lint错误
    @SuppressLint("SimpleDateFormat")
    public static String getCallHistoryList(Context context, ContentResolver cr){
    }

    # annotations
    .annotation system Ldalvik/annotation/MemberClasses;
    value = {
    Lcom/example/test/R$attr;,
    Lcom/example/test/R$dimen;,
    Lcom/example/test/R$drawable;,
    Lcom/example/test/R$id;,
    Lcom/example/test/R$layout;,
    Lcom/example/test/R$menu;,
    Lcom/example/test/R$string;,
    Lcom/example/test/R$style;
    }
    .end annotation

    内部类可以分为成员内部类、静态嵌套类、方法内部类、匿名内部类。


    内部类作为一个独立的类,它也拥有自己独立的smali文件,只是内部类的文件名形式为 "[外部类]$[内部类].smali"


    27、直接方法


    parameter的个数和方法参数的数量相对应,即有几个参数便有几个.parameter,默认从1开始,即p1,p2,p2….
    该类型的方法有个默认的参数指向当前对象,在smali中,方法的默认对象参数用p0表示.
    直接方法即所谓的direct methods,Davilk中方法调用指令invoke-direct
    #direct methods
    .method <访问权限修饰符> [非访问权限修饰符] <方法原型>
    <.locals>
    [.parameter]
    [.prologue]
    [.line]
    <代码逻辑>
    .end

    # direct methods
    .method public constructor <init>()V
    .registers 2

    .prologue
    .line 8
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    .line 10
    const-string v0, "MainActivity"

    iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;

    .line 13
    const/4 v0, 0x0

    iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Z

    return-void
    .end method


    28、虚方法


    虚方法的定义会和直接方法唯一的不同就是注释不同:#virtual methods

    #virtual methods
    .method <访问权限> [修饰关键词] <方法原想>
    <.locals>
    [.parameter1]
    [.parameter2]
    [.prologue]
    [.line]
    <代码逻辑>
    .end

    4.2 ELF 文件解析

    Android上的so(elf)与linux上的99%的相似,结构是通用的
    Linux下使用比较方便,但ndk也提供了readelf可以在windows下使用

    NDK目录 oolchainsarm-linux-androideabi-4.6prebuiltwindowsin


    arm-linux-androideabi-readelf.exe -a xx.so > f:1.txt

    1、-h或者--file-header
    显示在ELF文件头里包含的所有信息

    2、-l或者--program-headers或者--segments
    显示程序头表信息,包扩有几个段,每个段的属性,以及每个段中包含有哪几个节(Section)
    3、-S或者--section-headers或者--sections
    显示节区表内的所有信息,包括每个节的属性,注意这里是用的是大写的“S”
    4、-t或者--section-details
    用来显示所有节的详细信息,感觉上但从信息量上来说,和前面的“-S”没有什么大的不同
    5、-e或者--headers
    显示所有头的信息,包括ELF文件头、程序头和节头,也就是“-h -l -S”的组合。
    6、-s或者--syms或者--symbols
    显示符号表的信息,包含静态符号表(.symtab)和动态符号表(.dynsym)
    如果只关心动态符号表,可以直接使用“--dyn-syms”。
    7、-r或者--relocs
    显示所有重定位入口的信息
    8、-d或者--dynamic
    显示动态节区的内容
    9、-x或者--hex-dump=<number|name>
    显示某个节区的二进制码,具体哪个节可以用该节的编号或者名字来指定,例如“-x .text”:

    结构定义;
    unsigned :无符号
    char: 1个字节
    short: 2个字节
    int: 4个字节


    typedef unsigned short __u16;
    typedef int __s32;
    typedef unsigned int __u32;

    typedef __u32 Elf32_Addr;
    typedef __u16 Elf32_Half;
    typedef __u32 Elf32_Off;
    typedef __s32 Elf32_Sword;
    typedef __u32 Elf32_Word;

    /* 32-bit ELF base types. */
    typedef uint32_t Elf32_Addr;
    typedef uint16_t Elf32_Half;
    typedef uint32_t Elf32_Off;
    typedef int32_t Elf32_Sword;
    typedef uint32_t Elf32_Word;
    /* 64-bit ELF base types. */
    typedef uint64_t Elf64_Addr;
    typedef uint16_t Elf64_Half;
    typedef int16_t Elf64_SHalf;
    typedef uint64_t Elf64_Off;
    typedef int32_t Elf64_Sword;
    typedef uint32_t Elf64_Word;
    typedef uint64_t Elf64_Xword;
    typedef int64_t Elf64_Sxword;

    #define EI_NIDENT 16


    typedef struct elf32_hdr{
    unsigned char e_ident[EI_NIDENT];
    Elf32_Half e_type;
    Elf32_Half e_machine;
    Elf32_Word e_version;
    Elf32_Addr e_entry; /* Entry point */
    Elf32_Off e_phoff;
    Elf32_Off e_shoff;
    Elf32_Word e_flags;
    Elf32_Half e_ehsize;
    Elf32_Half e_phentsize;
    Elf32_Half e_phnum;
    Elf32_Half e_shentsize;
    Elf32_Half e_shnum;
    Elf32_Half e_shstrndx;
    } Elf32_Ehdr;

    typedef struct elf32_phdr{
    Elf32_Word p_type;
    Elf32_Off p_offset;
    Elf32_Addr p_vaddr;
    Elf32_Addr p_paddr;
    Elf32_Word p_filesz;
    Elf32_Word p_memsz;
    Elf32_Word p_flags;
    Elf32_Word p_align;
    } Elf32_Phdr;

    typedef struct {
    Elf32_Word sh_name;
    Elf32_Word sh_type;
    Elf32_Word sh_flags;
    Elf32_Addr sh_addr;
    Elf32_Off sh_offset;
    Elf32_Word sh_size;
    Elf32_Word sh_link;
    Elf32_Word sh_info;
    Elf32_Word sh_addralign;
    Elf32_Word sh_entsize;
    } Elf32_Shdr;

    4.3 查看调试信息

    adb connect 127.0.0.1:62001

    请求支付,计费id:30000916146302
    package com.lotuseed.android;

    import java.util.HashMap;
    import java.util.Map;

    public class LSGAVirtualCurrency
    {
    public static void onChargeRequest(String paramString1, String paramString2, double paramDouble1, String paramString3, double paramDouble2, String paramString4)
    {
    HashMap localHashMap = new HashMap();
    localHashMap.put("i", paramString1);
    if ((paramString2 != null) && (paramString2.length() > 0)) {
    localHashMap.put("f", paramString2);
    }
    localHashMap.put("a", Double.toString(paramDouble1));
    if ((paramString3 != null) && (paramString3.length() > 0) && (!paramString3.equalsIgnoreCase("CNY"))) {
    localHashMap.put("t", paramString3);
    }
    if (paramDouble2 > 0.0D) {
    localHashMap.put("v", Double.toString(paramDouble2));
    }
    if ((paramString4 != null) && (paramString4.length() > 0)) {
    localHashMap.put("c", paramString4);
    }
    g.onEvent("^pr", localHashMap, true);
    }

    public static void onChargeSuccess(String paramString)
    {
    g.onEvent("^ps", paramString, true);
    }

    public static void onReward(double paramDouble, String paramString)
    {
    HashMap localHashMap = new HashMap();
    localHashMap.put("r", paramString);
    localHashMap.put("v", Double.toString(paramDouble));
    g.onEvent("^v", localHashMap, true);
    }
    }

    Ljava/lang/String;

    .method public static onResult(II)V
    .locals 2
    .param p0, "id" # I
    .param p1, "success" # I

    .prologue
    .line 369
    if-nez p1, :cond_0

    .line 370
    sget-object v0, Lorg/cocos2dx/cpp/AppActivity;->context:Lorg/cocos2dx/cpp/AppActivity;

    iget-object v0, v0, Lorg/cocos2dx/cpp/AppActivity;->mOrderID:Ljava/lang/String;

    invoke-static {v0}, Lcom/lotuseed/android/LSGAVirtualCurrency;->onChargeSuccess(Ljava/lang/String;)V

    .line 372
    :cond_0
    sget-object v0, Lorg/cocos2dx/cpp/AppActivity;->context:Lorg/cocos2dx/cpp/AppActivity;

    new-instance v1, Lorg/cocos2dx/cpp/AppActivity$2;

    invoke-direct {v1, p0, p1}, Lorg/cocos2dx/cpp/AppActivity$2;-><init>(II)V

    invoke-virtual {v0, v1}, Lorg/cocos2dx/cpp/AppActivity;->runOnGLThread(Ljava/lang/Runnable;)V

    .line 378
    return-void
    .end method
    public static void onResult(int paramInt1, final int paramInt2)
    {

    Int paramInt2 = 0;

    if (paramInt2 == 0) {
    LSGAVirtualCurrency.onChargeSuccess(context.mOrderID);
    }
    context.runOnGLThread(new Runnable()
    {
    public void run()
    {
    AppActivity.onResultNative(this.val$id, paramInt2);
    }
    });
    }

    const/4 p1, 0x0

    4.4 方法调用替换

    4.5 方法修改

    4.6 方法内容替换

  • 相关阅读:
    [MetaHook] Find a function signature
    [MetaHook] GameUI hook
    [MetaHook] BaseUI hook
    一些常用软件的网络端口协议分类介绍
    Visual C++中最常用的类与API函数
    Ubuntu常用软件安装
    C++字符串完全指引
    C++资源之不完全导引
    超过 130 个你需要了解的 vim 命令
    Little-endian和Big-endian
  • 原文地址:https://www.cnblogs.com/hack404/p/10985341.html
Copyright © 2011-2022 走看看