跳至主要内容

记一次混淆算法逆向分析

原文来自乌云,备份地址

0x00 前言


小弟最近整理之前的资料,偶然发现半年前的混淆对抗研究以及一道CTF练习题目,故分享以作记录。限于水平,难免会有疏漏或者错误之处,望各位读者批评指正。

0x01 基本分析


jeb打开文件,找到方法校验方法。逻辑很简单,校验函数既是Native函数check.
public native boolean check(String arg1) {
}

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(2130903040);
    this.inputCode = this.findViewById(2131099648);
    this.btn_submit = this.findViewById(2131099649);
    this.btn_submit.setOnClickListener(new View$OnClickListener() {
        public void onClick(View v) {
            if(MainActivity.this.check(MainActivity.this.inputCode.getText().toString())) {
                MainActivity.this.startActivity(new Intent(MainActivity.this, ResultActivity.class));
            }
            else {
                Toast.makeText(MainActivity.this.getApplicationContext(), "Incorrect Password!", 
                        0).show();
            }
        }
    });
}
直接使用IDA默认Loader打开直接崩溃,存在畸形ELF文件对抗,使用自定义LOADER加载,也是然并卵的节奏。
使用Tracer动态打印check函数地址,挂起进程,dump出对应的代码段加载到IDA,找到check函数。
seg000:4561E4E8 check
seg000:4561E4E8                 LDR             PC, =sub_4561E4EC
seg000:4561E4E8 ; End of function check
seg000:4561E4E8
seg000:4561E4EC ; =============== S U B R O U T I N E   seg000:4561E4EC sub_4561E4EC                            ; CODE XREF: check j
seg000:4561E4EC                                         ; DATA XREF:    seg000:4561E4EC                 STMFD           SP!, {R0-R12,LR}
seg000:4561E4F0                 LDR             R0, =6
seg000:4561E4F4                 B               loc_4561E444
通过分析发现,其实为一个汇编stub,通过此stub跳到真正的check函数。
seg013:80A0135C sub_80A0135C                            ; DATA XREF: seg013:80A13F98 o
seg013:80A0135C                 B               sub_80A065B8
seg013:80A0135C ; End of function sub_80A0135C
seg013:80A01360
seg013:80A01360 ; =============== S U B R O U T I N E   seg013:80A01360 ; Attributes: thunk
seg013:80A01360
seg013:80A01360 sub_80A01360                            ; DATA XREF: sub_80A065C4+C o
seg013:80A01360                 B               sub_80A065F8
seg013:80A01360 ; End of function sub_80A01360
seg013:80A01364 ; =============== S U B R O U T I N E   seg013:80A01364 ; Attributes: thunk
seg013:80A01364
seg013:80A01364 sub_80A01364                            ; CODE XREF: sub_80A06620 j
seg013:80A01364                 B               sub_80A0663C
seg013:80A01364 ; End of function sub_80A01364
以80A0135C(B sub_80A065B8)为例子,跟进sub_80A065B8,可以看到如下指令:
// 0x80A0135C
seg013:80A065B8                 BEQ             loc_80A0658C
seg013:80A065BC                 BNE             loc_80A0658C

seg013:80A0658C                 STMFD           SP!, {R3-R8,R10,LR} 真实指令
seg013:80A06590                 STMFD           SP!, {R8,LR}
seg013:80A06594                 LDR             R8, loc_80A065A4
seg013:80A06598                 LDR             R8, loc_80A065A8
seg013:80A0659C                 LDR             R8, loc_80A065AC
seg013:80A065A0                 LDR             R8, locret_80A065B0
seg013:80A065A4                 LDR             R8, =(sub_80A065C4 - 0x80A065B0)
seg013:80A065A8                 ADD             R8, PC, R8 ; sub_80A065C4
seg013:80A065AC                 STR             R8, [SP,#4]
seg013:80A065B0                 LDMFD           SP!, {R8,PC}

seg013:80A065C4                 STMFD           SP!, {R8,LR}
seg013:80A065C8                 LDR             R8, =0xFFFFAD25
seg013:80A065CC                 EOR             R8, R8, #0xAD
seg013:80A065D0                 ADD             R8, PC, R8 ; loc_80A01360 //返回到80A01360
seg013:80A065D4                 STR             R8, [SP,#8+var_4]
seg013:80A065D8                 LDMFD           SP!, {R8,PC}
通过分析可以得到真实指令(STMFD SP!, {R3-R8,R10,LR}),其余指令为混淆指令,最终返回到下一条B即80A01360(B sub_80A065F8)指令。通过分析其他B指令,可以得到类似的混淆指令中夹在一条真实指令,只是存在多种混淆的方式。 至此,我们可以得到此混淆的思路:执行"一个B指令"即一条真实的指令,混淆抽象为:
  • 执行前跳转混淆
  • 真实指令
  • 执行后跳转混淆
不难发现,如果仅仅靠一条一条的寻找真实指令,是非常费时费力的。由于执行前后都存在多种模式的混淆,但总的模式是有限的,那么通过提取指令特征匹配即可以自动化实现去混淆,找出真实指令。

0x02 基于指令特征匹配对抗混淆


通过分析找到所有的混淆模式,最后大概几种。限于篇幅,列举一些做说明
// 0x80A0135C
seg013:80A065B8                 BEQ             loc_80A0658C
seg013:80A065BC                 BNE             loc_80A0658C

seg013:80A0658C                 STMFD           SP!, {R3-R8,R10,LR} 真实指令

seg013:80A06590                 STMFD           SP!, {R8,LR}
seg013:80A06594                 LDR             R8, loc_80A065A4
seg013:80A06598                 LDR             R8, loc_80A065A8
seg013:80A0659C                 LDR             R8, loc_80A065AC
seg013:80A065A0                 LDR             R8, locret_80A065B0
seg013:80A065A4                 LDR             R8, =(sub_80A065C4 - 0x80A065B0)
seg013:80A065A8                 ADD             R8, PC, R8 ; sub_80A065C4
seg013:80A065AC                 STR             R8, [SP,#4]
seg013:80A065B0                 LDMFD           SP!, {R8,PC}

seg013:80A065C4                 STMFD           SP!, {R8,LR}
seg013:80A065C8                 LDR             R8, =0xFFFFAD25
seg013:80A065CC                 EOR             R8, R8, #0xAD
seg013:80A065D0                 ADD             R8, PC, R8 ; loc_80A01360
seg013:80A065D4                 STR             R8, [SP,#8+var_4]
seg013:80A065D8                 LDMFD           SP!, {R8,PC}
执行前混淆:B(连续两条条件完全相反的指令) next_jmp 执行后混淆:这里有两组STMFD--LDMFD构成的跳转stub,但其是为一种模式。那如何计算next_jmp呢?这里我采用取巧的方式,通过从LDMFD所在地址反向找到ADD指令,得到";loc_80a01360",再解析出地址80a01360。当然,存在多种prefix,需要作简单处理获取地址。
def prefix_match(str):
    pattern = ['sub_', 'loc_', 'unk_', 'locret_']
    for prefix in pattern:
        if str.find(prefix) > -1:
            substr = str[str.find(prefix) + len(prefix):]
            return string.atoi(substr, 16)
    return 0xffffffff;
真实指令:通过解析跳转遍历完整个混淆后,通过堆栈平衡原理,提取出真实指令。以上述分析为例,遍历回到下一条指令80a01360后,对指令进行分组即(B)(STMFD SP!, {R3-R8,R10,LR})(STMFD-LDMFD)(STMFD-LDMFD),非常容易获取真实指令。实现时,可将分组过程可融入到指令的遍历即可。
再接着看另一组混淆,以80A01360(B sub_80A065F8)为例。
// 0x80A01360
seg013:80A065F8                 STMFD           SP!, {R0,LR}
seg013:80A065FC                 LDR             R0, loc_80A0660C
seg013:80A06600                 LDR             R0, loc_80A06610
seg013:80A06604                 LDR             R0, loc_80A06614
seg013:80A06608                 LDR             R0, locret_80A06618
seg013:80A0660C                 LDR             R0, =(loc_80A065E0 - 0x80A06618)
seg013:80A06610                 ADD             R0, PC, R0 ; loc_80A065E0
seg013:80A06614                 STR             R0, [SP,#4]
seg013:80A06618
seg013:80A06618                 LDMFD           SP!, {R0,PC}

seg013:80A065E0                 LDR             R3, [R0] //真实指令

seg013:80A065E4                 STMFD           SP!, {R0,LR}
seg013:80A065E8                 MOV             LR, PC
seg013:80A065EC                 BL              loc_80A065F0
seg013:80A065F0
seg013:80A065F0 loc_80A065F0                            ; CODE XREF: seg013:80A065EC j
seg013:80A065F0                 LDMFD           SP!, {R0,LR}

seg013:80A065F4                 B               sub_80A06620

seg013:80A06620                 B               sub_80A01364
执行前混淆:STMFD-LDMFD跳转到loc_80A065E0。获取next_jmp和上述一致。
执行后混淆:通过STMFD-LDMFD和两次B直接跳转返回到下一条B指令地址sub_80A01364。
真实指令:和上述类似,遍历混淆指令时,对指令进行分组(STMFD-LDMFD)、(LDR R3, [R0])、(B)、(B)。易获取真实指令(LDR R3, [R0])。
通过上述方法,大概分析20个多有的B指令即可找到所有的混淆模式,总的来说混淆的模式是有限的。
通过编写IDAPython脚本,即可实现自动打印真实指令。
0x80a0135c            PUSH            {r3, r4, r5, r6, r7, r8, sl, lr}
0x80a01360            LDR             r3, [r0]
0x80a01364            MOV             r1, r2
0x80a01368            MOV             r6, r2
0x80a0136c            LDR             r3, [r3, #0x2a4]
0x80a01370            MOV             r2, #0
0x80a01374            MOV             r4, r0
0x80a01378            BLX             r3
0x80a0137c            MOV             r7, r0
但存在问题,当IDA并没有识别出指令时,无法通过GetMnem等API获取信息。
p1
由于混淆对IDA指令识别的影响,致使IDA无法自动将指令反汇编出来。可能已经有读者意识到,遇到这种情况直接调用MakeCode将数据转化为指令即可。然而,实际使用MakeCode自动处理时,并不能完成手动按'C'识别指令的功能。那么,是否遇到这种情况后,就手动去完成指令反汇编呢?答案是否定的。由于存在很多这种情况,手动转化也很费时(测试环境IDA6.8)。
到这里可以看到,单纯依靠简单的字符串匹配比较的方法,并不能完全满足自动化提取指令对抗混淆的需求。

0x03 基于指令解析执行对抗混淆


通过上述分析,由于IDA存在无法自动反汇编一部分opcode数据,故单纯依靠IDAPython是无法满足指令解析指令的需求的。为了实现对指令的解析,可采用两种途径:
  1. 对照arm汇编手册,编写常见的opcode解析脚本。以笔者的经验,这部分内容是比较耗时的。
  2. 引入现有的反汇编引擎,且这种反汇编引擎具备对指令的想尽分析的能力。这里,我选用Capstone。
Capstone是一款支持多种架构的反汇编引擎,支持对汇编指令粗略和详细的分析,支持多种语言。当然,Capstone还有很多其他优点,这里就不赘述了。
3.1 ARM处理器模拟
可能有读者马上会问,模拟arm处理器执行不又是一大工程呢。的确,完全模拟确实包含许多工作量。但结合此混淆的一些特性,整个模拟执行可简化许多。
首先,此混淆并不存在流程分支扁平化(与OLLVM相对比)。结合上述分析也可以看到,所有的混淆执行并不会影响条件标志即CPSR寄存器。
再者,结合堆栈平衡原理,SP寄存器仅仅只需要保存堆栈的变化,比如stmfd仅仅对SP寄存器进行减法操作。
最后,根据上述找到的混淆模式,可以发现使用的指令其实很少,实际编写模拟函数工作量也比较小。
def do_emulate(code, base, Rx):
    ret_addr = 0xffffffff
    emu = ARM_emu()
    md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
    md.detail = True

    for i in md.disasm(code, base):
        emu.regs[PC] = i.address + 2 * inst_size
        dst = i.operands[0]
        src = i.operands[1]

        if (i.mnemonic).upper() == 'LDR':
            if dst.type == ARM_OP_REG and src.type == ARM_OP_MEM:
                Rd = conv_reg(dst.value.reg)    
                Rs = conv_reg(src.value.mem.base)
                addr = emu.regs[Rs] + src.value.mem.disp
                emu.regs[Rd] = Dword(addr & 0xffffffff)

                if Debug:
                    print ('\t LDR %s :\t0x%x' %(i.op_str, emu.regs[Rd]))

        elif (i.mnemonic).upper() == 'ADD':
            if i.operands[0].type == ARM_OP_REG and i.operands[1].type == ARM_OP_REG and i.operands[2].type == ARM_OP_REG:
                Rd = conv_reg(i.operands[0].value.reg)
                R1 = conv_reg(i.operands[1].value.reg)
                R2 = conv_reg(i.operands[2].value.reg)
                emu.regs[Rd] = (emu.regs[R1] + emu.regs[R2]) & 0xffffffff

                if Debug:
                    print ('\t ADD %s :\t0x%x' %(i.op_str, emu.regs[Rd]))
        ...
在模拟执行一条真实指令时,首先将所有寄存器的初始值设置为0,通过主流程中的B指令进入到混淆指令。
3.2 真实指令提取
模拟执行时,将混淆中的每条指令都存储到一个指令堆栈中。结合之前直接字符串模式的思路,来实现对真实指令的提取。
以80A01364为例子来说明真实指令的提取方法。
seg013:80A01364 sub_80A01364                            ; CODE XREF: sub_80A06620 j
seg013:80A01364                 B               sub_80A0663C

seg013:80A0663C                 BMI             loc_80A06648
seg013:80A06640                 BPL             loc_80A06644
seg013:80A06644
seg013:80A06644 loc_80A06644                            ; CODE XREF: sub_80A0663C+4 j
seg013:80A06644                                         ; sub_80A0663C:loc_80A06648 j
seg013:80A06644                 B               loc_80A06624
seg013:80A06648 ; 
seg013:80A06648
seg013:80A06648 loc_80A06648                            ; CODE XREF: sub_80A0663C j
seg013:80A06648                 B               loc_80A06644

seg013:80A06624 loc_80A06624                            ; CODE XREF: 
seg013:80A06624                 MOV             R1, R2
seg013:80A06628                 STMFD           SP!, {R0,LR}
seg013:80A0662C                 MOV             LR, PC
seg013:80A06630                 BL              loc_80A06634
seg013:80A06634 ; 
seg013:80A06634
seg013:80A06634 loc_80A06634                            ; CODE XREF: sub_80A0663C-C j
seg013:80A06634                 LDMFD           SP!, {R0,LR}
seg013:80A06638                 B               sub_80A0664C

seg013:80A0664C sub_80A0664C                            ; CODE XREF: sub_80A0663C-4 p
seg013:80A0664C                 B               sub_80A01368
若不在模拟执行中对指令堆栈修正,那么执行完后存储指令如下所示:
80A0663C    BMI     loc_80A06648
80A06648    B       loc_80A06644
80A06644    B       loc_80A06624
80A06624    MOV     R1, R2
80A06628    STMFD   SP!, {R0,LR}
80A06630    BL      loc_80A06634
80A06634    LDMFD   SP!, {R0,LR}
80A06638    B       sub_80A0664C
80A0664C    B       sub_80A01368
对于BMI,虽然形式上和之前分析的(BEQ loc_80A0658C,BNE loc_80A0658C)直接跳到next_jmp,但检测下一条指令即可根据条件相反去处。
对于STM-LDM,当遇到LDM指令时,将STM-LDM及其之间的指令出栈移除。
剩余(B B MOV B)这些指令,根据上述人工分析的结果可知,因为只存在一条真实指令,那么MOV必定是真实指令。另外,存在这种情况(B B BNE B),产生这种情况的根本原因是混淆前这条指令是if或者循环语句的判定点,直接取出BNE指令即可。
3.3 函数识别
不管是基于指令名称匹配还是解析执行,都需要对函数进行识别。先来看一个函数混淆片段:
seg013:80A067F0 loc_80A067F0                    ; CODE XREF: seg013:loc_80A06838 j
seg013:80A067F0                 ADR             LR, sub_80A06814
seg013:80A067F4                 STMFD           SP!, {R8,R9,LR}
seg013:80A067F8                 LDR             R8, loc_80A067FC
seg013:80A067FC
seg013:80A067FC loc_80A067FC                            ; DATA XREF: seg013:80A067F8 r
seg013:80A067FC                 LDR             R9, =0x1A6016A4
seg013:80A06800                 ADD             R8, R9, R8
seg013:80A06804                 ADD             R8, PC, R8 ; j_strlen
seg013:80A06808                 STR             R8, [SP,#8]
seg013:80A0680C                 LDMFD           SP!, {R8,R9,PC}
对于未混淆的指令,函数通常被编译为BL或者BLX(指令模式切换)。由于B指令本身的跳转地址范围很有限,那么混淆后代码膨胀必定需要对其指令修正,有点类似InlineHook指令修正。另外,函数的返回地址需要显式存放到LR寄存器。
这样,上述代码在模拟执行时,当LR寄存器值不为0时,将后续的函数调用转化为'Call sub_xxx'指令,将PC置为next_jmp(sub_80A06814)接着模拟。
另外,便于更加清晰的分析,将libc.so加载到和进程一致的基地址,通过IDAPython GetFunctionName获取函数名称。
至此,即可提取出真实指令,check函数流程:
0x80a0135c            PUSH            {r3, r4, r5, r6, r7, r8, sl, lr}
0x80a01360            LDR             r3, [r0]
0x80a01364            MOV             r1, r2
0x80a01368            MOV             r6, r2
0x80a0136c            LDR             r3, [r3, #0x2a4]
0x80a01370            MOV             r2, #0
0x80a01374            MOV             r4, r0
0x80a01378            BLX             r3
0x80a0137c            MOV             r7, r0
0x80a01380            call  j_strlen
0x80a01384            ADD             sl, r0, #1
0x80a01388            MOV             r8, r0
0x80a0138c            MOV             r0, sl
0x80a01390            call  j_malloc_0
0x80a01394            MOV             r1, r7
0x80a01398            MOV             r2, sl
0x80a0139c            MOV             r5, r0
0x80a013a0            call  j_memcpy
0x80a013a4            LDR             r3, [r4]
0x80a013a8            MOV             r2, #0
0x80a013ac            STRB            r2, [r5, r8]
0x80a013b0            LDR             r3, [r3, #0x2a8]
0x80a013b4            MOV             r2, r7
0x80a013b8            MOV             r0, r4
0x80a013bc            MOV             r1, r6
0x80a013c0            BLX             r3
0x80a013c4            LDR             R0, =0x12BC4
0x80a013c8            MOV             r1, #0x80
0x80a013cc            LDR             R0, [PC,R0]
0x80a013d0            call  0x80a01048
0x80a013d4            ADD             r5, r5, r0
0x80a013d8            MOV             r0, r5
0x80a013dc            call  0x80a010c0
0x80a013e0            MOV             r4, r0
0x80a013e4            MOV             r0, r5
0x80a013e8            call  j_free_1
0x80a013ec            MOV             r0, r4
0x80a013f0            POP             {r3, r4, r5, r6, r7, r8, sl, pc}

0x04 算法逆向分析


通过简单分析check即可看到算法的核心流程在0x80a010c0这个函数,而0x80a01048函数的功能是对指令路径上的断点进行检测,和其他平台的反调试思路类似,这里把重点放在0x80a010c0的逆向上。自动化分析得到0x80a010c0函数:
0x80a010c0            PUSH            {r4, r5, r6, r7, r8, sb, sl, lr}
0x80a010c4            LDR             R7, =0x12EB4
0x80a010c8            SUB             sp, sp, #0x308
0x80a010cc            ADD             r6, sp, #4 
0x80a010d0            LDR             R7, [PC,R7]
0x80a010d4            LDR             r3, [r7]
0x80a010d8            MOV             r4, r0
0x80a010dc            MOV             r1, #0
0x80a010e0            MOV             r2, #0x100
0x80a010e4            MOV             r0, r6
0x80a010e8            ADD             r5, sp, #0x104 
0x80a010ec            STR             r3, [sp, #0x304]
0x80a010f0            call  j_memset
0x80a010f4            MOV             r1, #0
0x80a010f8            MOV             r2, #0x100
0x80a010fc            MOV             r0, r5
0x80a01100            call  j_memset
0x80a01104            MOV             r0, r4
0x80a01108            call  j_strlen
0x80a0110c            SUBS            sb, r0, #0
0x80a01110            MOVEQ           r0, sb
0x80a01114            BNE             #0x80a01130
0x80a01118            LDR             r2, [sp, #0x304]
0x80a0111c            LDR             r3, [r7]
0x80a01120            CMP             r2, r3
0x80a01124            BNE             #0x80a01334
0x80a01128            ADD             sp, sp, #0x308
0x80a0112c            POP             {r4, r5, r6, r7, r8, sb, sl, pc}

//获取代码段起始256字节作为key
0x80a011bc            LDR             R0, =0x12DC0   //读取代码段起始地址
0x80a011c0            LDR             LR, =0x66666667
0x80a011c4            MOV             r4, #0
0x80a011c8            LDR             R0, [PC,R0]
0x80a011cc            MOV             r3, r0
0x80a011d0            SMULL           r2, ip, lr, r4
0x80a011d4            ASR             r2, r4, #0x1f
0x80a011d8            LDRB            r1, [r3]
0x80a011dc            RSB             r2, r2, ip, asr #1
0x80a011e0            ADD             r2, r2, r2, lsl #2
0x80a011e4            RSB             r2, r2, r4
0x80a011e8            STRB            r1, [r6, r4]
0x80a011ec            ADD             r4, r4, #1
0x80a011f0            CMP             r4, #0x100
0x80a011f4            ADD             r3, r3, r2
0x80a011f8            BNE             #0x80a011d0

//key变换流程
0x80a01218            MOV             r3, #0
0x80a0121c            MOV             r0, r3
0x80a01220            ADD             r4, r4, #1
0x80a01224            ADD             r6, sp, #0x308
0x80a01228            AND             r4, r4, #0xff
0x80a0122c            ADD             r1, r6, r4
0x80a01230            LDRB            r2, [r1, #-0x304] 
0x80a01234            LDRB            r8, [r5, r3] 
0x80a01238            AND             ip, r3, #7 
0x80a0123c            ADD             r0, r2, r0
0x80a01240            AND             r0, r0, #0xff
0x80a01244            ADD             r6, r6, r0
0x80a01248            LDRB            sl, [r6, #-0x304]
0x80a0124c            ASR             sb, r8, #5
0x80a01250            ORR             r8, sb, r8, lsl #3 
0x80a01254            STRB            sl, [r1, #-0x304] 
0x80a01258            STRB            r2, [r6, #-0x304]
0x80a0125c            LDRB            r6, [r1, #-0x304]
0x80a01260            ADD             sl, sp, #0x308
0x80a01264            RSB             r1, ip, #8 // 8 - [0, 7]
0x80a01268            ADD             r2, r2, r6
0x80a0126c            AND             r2, r2, #0xff
0x80a01270            ADD             r2, sl, r2
0x80a01274            LDRB            r2, [r2, #-0x304]
0x80a01278            EOR             r2, r2, r8
0x80a0127c            AND             r2, r2, #0xff
0x80a01280            LSL             r1, r2, r1 
0x80a01284            ORR             ip, r1, r2, asr ip // 循环左移(8 - i)位
0x80a01288            STRB            ip, [r5, r3] 
0x80a0128c            ADD             r3, r3, #1
0x80a01290            CMP             r3, #0x100
0x80a01294            BNE             #0x80a01220

for(i = 0; i < 0x100; i++){
    left_rotate(right_rotate(mid_code[i], 5) ^ key_stream[i], 8 - (i % 8));
...
限于篇幅,就不在一一分析。其中包括RC4算法。最后得到算法编码主流程:
char gen_mid_code[N];
char key_stream[N];

for(i = 0; i < N; i++){
    gen_mid_code[i] = left_rotate(str[gen_index(i, strlen(str))], 8 - (i % 8));
}
gen_key_stream(ori_key, key_stream);
RC4_encrypt(gen_mid_code, key_stream, final_code);
for(i = 0; i < N; i++){
    if(final_code[i] != check_code[i]){
            ...
    }
}
最后,得到flag:Hello Tomorrow!
至此,此ctf题目大致分析完毕。
题目下载地址:http://pan.baidu.com/s/1hrqZH9E

Popular posts from 产品随想的博客

UNstudio实习经验分享

再过一周,我就将离开UNstudio阿姆斯特丹总部,到其上海分部了,鉴于上海分部目前还不承担设计任务,因此可以视为我UNstudio参与设计的体验即将告一段落。这个实习,原定3个月,后来被要求延长到了6个月,后来又延长到9个月,现在看来最终大概有11个月——那天一问,发现我的合同已经到了9月份了,赶紧声明不能这么长,我8月得回学校了。

360T7 刷机步骤及固件

https://cmi.hanwckf.top/p/360t7-firmware/   360T7的固件支持由immortalwrt-mt798x项目提供支持,请参考: https://cmi.hanwckf.top/p/immortalwrt-mt798x https://github.com/hanwckf/immortalwrt-mt798x 刷机步骤 参考 此处 的办法开启原厂固件的UART和telnet功能 在以下链接下载360T7测试固件(纯净版,无任何插件) https://wwd.lanzout.com/b0bt9idwd 密码:ezex (此固件已过时,请选择其它更新的固件) 接下来将刷入修改版uboot。修改版uboot的优点有: 固件分区可达108MB,原厂uboot只能使用36M 自带一个简单的webui恢复页面 到以下仓库的Release页面下载uboot,目前暂时仅支持360T7,后续将支持更多mt798x路由器。 推荐使用 mt7981_360t7-fip-fixed-parts.bin , fixed-parts 代表uboot分区表在编译期间固定,不会随着uboot环境变量变化。 https://github.com/hanwckf/bl-mt798x/releases/latest 将 mt7981_360t7-fip-fixed-parts.bin 通过HFS等方式上传到路由器,使用以下命令刷入uboot mtd write mt7981_360t7-fip-fixed-parts.bin fip 确认刷入完毕后,拔掉路由器电源。然后将电脑的IP地址设置为固定的 192.168.1.2 ,接着按住路由器的RESET按钮后通电开机,等待8s后用浏览器进入 192.168.1.1 在uboot恢复页面选择要刷入的固件。immortalwrt-mt798x目前编译两个版本的360T7固件。 建议修改版uboot直接使用 immortalwrt-mediatek-mt7981-mt7981-360-t7-108M-squashfs-factory.bin ,两种固件区别如下: mt7981-360-t7-108M 为108M固件分区,原厂uboot不可启动,需要修改版u...

产品随想 | 陪读《乔布斯传》:1-17章

乔布斯经典照片集 坐在麗莎電腦旁。他說:「毕加索曾說:「好的藝術家懂得模仿,佛大的藝術家善於偷取。」因此,窃取偉大的點子沒有什麼好羞耻的。 與蓋茲在電話中達成協議:「比爾,謝謝你支持蘋果。因為你的支持,世界將變得更美好。」 1997年蘋果在波士頓舉行的麥金塔世界大會,蓋茲透過衛星連線在巨大的螢幕上出現。質伯斯說:「我真是笨死了,竟然讓蓋兹以這種方式現身。他讓我看起來好沙小。」 ──时刻自省 前言 The people who are crazy enough to think they can change the world are the ones who do. 只有那些瘋狂到以為自己可以改變世界的人, 才能改變這個世界。 這麼些年來,賈伯斯說起話來的認真與專注態度,著實打動不少人。我們一直保持連絡,即使在他被逐出蘋果之後,我們還有來往。每次他有新產品要推出時,像 NeXT 電腦或皮克斯 (Pixar)的電影,他就會來找我。他常帶我去曼哈頓下城一家壽司店用餐,講起他的產品,渾身散發出光和熱,眉飛色舞的說這是他登峰造極之作。我喜歡這個人。 ──对自己产品深深的爱 他的堅持教我疑惑。人人都知道賈伯斯不道餘力捍衛隱私, 而且我不知道他是否看過我寫的任何一本傳記。我還是不敢立刻答應,只說或許再等等。然而到了 2009年,我接到他太太蘿琳. 鮑威爾打來的電話。她直截了當說:「如果你還想為史帝夫寫傳,最好趕快動筆。」這是他第二次因病向公司請長假。我坦言他早在2004 年得知自己罹患胰臟癌的時候就曾主動邀我寫傳,但我當時對他罹癌的事一無所知。蘿琳解釋說,他們盡量保密,因此當時根本沒幾個人知道。他是在動手術的前夕打電話給我的。 ──和Make Something Wonderful对照起来看 他還說,自從他回到蘋果重新掌權,這十二年來是他創造新產品的高峰期,但他還有更重要的目標,也就是效法惠普的惠立和普克(David Packard),締造一家創新動力無限的公司,進而超越惠普。 ──苹果公司才是乔布斯最得意的产品 他說:「我一直認為,自己是個偏向人文的孩子,但我也喜歡電子的東西。後來,我讀到寶麗來(Polaroid)創辦人蘭德 (Edwin Land)曾說過,一個人能站在人文和科學的交會口,兼容贯通,才是真正的人才。在那當下,我决定要當這樣的人。」他似乎在暗示我,這可以做為傳...

2018各行业应届生薪资不完全样本往期汇总-职场红领巾

文章来源自职场红领巾公众号2018.4.21日推送,在此表示感谢 产品岗 百度商业产品 14K*14 拼多多产品管培 12K*14 今日头条产品 16K*18 头条PM整个Package接近300K/年 美团产品Offer 14K*16 base上海 百度产品研究生 11.5K*14.6 base 上海 京东产品17K*13 百度产品 220K/年 网易 产品培训生 硕士 15K*18 SP base杭州 不知名互联网公司校招PM 12K*15 base北京 技术岗 微软 软件工程师 本科 260K/年 蚂蚁金服算法工程师 20K*16 拼多多开发本科400K/年 商汤科技本科技术岗 14K/月 税前 海康威视研究院 算法工程师 220K/年 微信算法岗 SP 360K/年 的package 今日头条 程序员 研究生 10K/月 base北京 滴滴程序员 16K*16 亚马逊 小四年经验 研发 50K/月 Facebook应届毕业生  软件开发工程师   打包 115k$/年(30%-40%税) base湾区 京东算法 普通Offer 234K/年 运营岗 滴滴北京运营岗 硕士 12K*15 奖金另算 网易游戏运营 150K/年 左右 网易运营 8K*13(奖金0~3个月) 网易新闻运营8K/月 腾讯游戏运营 本科6K/月 上海京东时尚本科8K/月 京东运营岗 11K/月 base北京亦庄总部 今日头条 渠道营销运营 6K/月(加房补) 网易考拉 活动运营 13K*16 OFO城市运营管培13K*14 爱范儿运营 8K/月 滴滴长三角某二线城市运营管培生 薪资 7.6K*13 +每个月40%绩效 货车帮 数据运营 12K/月 卡宾电商 管培 10K/月 含浮动绩效 曹操专车 运营管培生  加各种补贴税前5.4K/月  base杭州 京东金融海龟回来8K/月 北京蓝港互动...

巴菲特致股东信-1975年

 笔记: 华盛顿邮报已成为伯克希尔第一重仓股 翻译: 雪球:https://xueqiu.com/6217262310/131409324 备份:https://archive.ph/4hgK3 原文: To the Stockholders of Berkshire Hathaway Inc.: Last year, when discussing the prospects for 1975, we stated “the outlook for 1975 is not encouraging.” This forecast proved to be distressingly accurate. Our operating earnings for 1975 were $6,713,592, or $6.85 per share, producing a return on beginning shareholders ’ equity of 7.6%. This is the lowest return on equity experienced since 1967. Furthermore, as explained later in this letter, a large segment of these earnings resulted from Federal income tax refunds which will not be available to assist performance in 1976. On balance, however, current trends indicate a somewhat brighter 1976. Operations and prospects will be discussed in greater detail below, under specific industry titles. Our expectation is that significantly better results in textiles, earnings added from recent acquisitio...

产品爱好者周刊 第10期:乔布斯逝世10年

  产品爱好者周刊 第10期: 2021.10.14 Products 经济学人、卫报、Wired等电子周刊: https://github.com/hehonghui/the-economist-ebooks 每周均更新 Immersed: https://immersed.com/ 桌面端软件,可以虚拟出5个VR桌面,能模拟出在海滩、宇宙等地方,工作 它的员工写了篇自己工作流,非常酷 Working From Orbit: https://blog.immersed.team/working-from-orbit-39bf95a6d385 bilibili视频下载工具(开源) 哔哩下载姬 downkyi: https://github.com/leiurayer/downkyi Windows Only BilibiliVideoDownload: https://github.com/blogwy/BilibiliVideoDownload 多平台 BiliDuang:  https://github.com/kengwang/BiliDuang Versus: https://versus.com/cn 万物皆可比较 官网的范例,基本都是数码产品,可能是标准化产品有具体参数,容易比较些? News Jony Ive on What He Misses Most About Steve Jobs:  https://www.wsj.com/articles/jony-ive-steve-jobs-memories-10th-anniversary-11633354769 中文译本: https://archive.ph/EZihd Ideas Weather Spark:  https://weatherspark.com 全球城市的全年气候,用于去陌生城市、旅行规划了解城市天气,会有帮助 Long Now Foundation:  https://twitter.com/longnow 以10000年的跨度,去思考未来的可能性,真是一件很浪漫的事情 Outside interests Notion会私自查看用户的内容

《Becoming Steve Jobs》Chapter 16 Blind Spots, Grudges, and Sharp Elbows

Steve could be pretty thin-skinned when someone prominent criticized the aesthetics of his products. He took great umbrage that Neil would, as Steve put it, “pop off in public like that without coming to talk to us about his technical concerns first.” From that point on he had rebuffed all of Neil’s attempts to smoke the peace pipe. 有趣 He had blind spots, grating behavioral habits, and a tendency to give in to emotional impulse that persisted his entire life. These characteristics are often used to make the case that Steve was an “asshole” or a “jerk,” or perhaps simply “binary”—that odd adjective often used to convey the sense that he was half asshole/half genius from birth to death. These aren’t useful, interesting, or enlightening descriptions. What’s more illuminating is to take a look at the specific ways in which Steve failed to do an effective job of tempering some of his weaknesses and antisocial traits, and to consider how, when, and why some of them continued to flare up even...

产品随想 | 周刊 第85期:e-Residency与数字游民

  David Shambaugh   https://www.google.com/search?q=David+Shambaugh 中国问题研究专家,著作极多 郭玉闪   https://zh.wikipedia.org/wiki/郭玉闪?useskin=vector 中国公共知识分子 我只想好好观影   github.com/BetterWorld-Liuser/autoMovies 刘煜辉:中国资本市场灵魂出窍 最有活力的公司几乎不在A股   https://finance.sina.com.cn/stock/marketresearch/2017-06-23/doc-ifyhmtek7705574.shtml 回看17年的专家讲话,还是挺有水平的,挺多都认可 纽约文化沙龙   https://www.youtube.com/@user-cu2hl5tf6y/videos 视频质量出奇的高,推荐 透视中国政治by吴国光、程晓农 备忘下,貌似评价挺好的一本书 CAPI China Chair Wu Guoguang (吴国光 / 吳國光)   https://www.youtube.com/playlist?list=PLIt1szHhnm_Hso3jGUbfGpnEAbsPOuEVV 因为热爱中国,我们越要看懂中国 AI Canon   https://a16z.com/2023/05/25/ai-canon/ in this post, we’re sharing a curated list of resources we’ve relied on to get smarter about modern AI. We call it the “AI Canon” because these papers, blog posts, courses, and guides have had an outsized impact on the field over the past several years. 希望中国的投資機構,也能有更多的分享與輸出,提升整個社會的認知 Cantonese Font 粵語字體   https://visual-fonts.com/zh/...

产品随想 | 周刊 第39期:《自由秩序原理》

Products Exodus   https://github.com/Exodus-Privacy/exodus-android-app Exodus is an Android application that let you know what trackers are embedded in apps installed on your smartphone using the εxodus plateform. It let you also know the permissions required by any apps on your smartphone. 检测你Android机子获取权限、嵌入追踪SDK的 simplewall   https://github.com/henrypp/simplewall Simple tool to configure Windows Filtering Platform (WFP) which can configure network activity on your computer. Mem Reduct Lightweight real-time memory management application to monitor and clean system memory on your computer. 内存清理工具,Windows上挺多这类小工具,很有意思 Squad303   https://1920.in/ 提供大家一个方式,告诉俄罗斯人,乌克兰在发生什么 Chameleon   https://github.com/sereneblue/chameleon WebExtension port of Random Agent Spoofer 帮助隐匿浏览器信息 CanvasBlocker   https://github.com/kkapsner/CanvasBlocker 浏览器指纹识别是丧心病狂 Alpine Linux   https://zh.wikipedia.org/zh-hans/Alpine_Linux 以安全为理念的Linux分支 Wirecutter   https://www.nytimes.com...