跳至主要内容

记一次混淆算法逆向分析

原文来自乌云,备份地址

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 产品随想的博客

Steve Jobs: `There's Sanity Returning', 1998

Nobody can doubt the charisma of Steven P. Jobs. The interim CEO of Apple Computer Inc., who returned to the company last July after his ignominious 1985 ouster, has brought back his legendary vision, impatience, and infectious passion for the Macintosh. Jobs spoke to Business Week Correspondent Andy Reinhardt in Apple's stark, fourth-floor boardroom, just after the company rolled out its new software strategy on May 11. Note: This is an extended, online-only version of the Q&A that appears in the May 25, 1998, issue of Business Week. Q: Now that you've introduced the new, bold-looking iMac, are you going to do some radically different products? A: There's a lot of talk about such things -- about handhelds, set-top boxes. A lot of computer companies have been searching for a consumer product. My view is that the personal computer has been the most successful consumer product of the last 10 years. What we have to do, what the industry stopp...

CAD 天正与 AI 工作流

基本步骤   1. 天正导出 T3 .dwg 文件,尽量在2007版本   2. AI 打开,打开时候可以选择比例,因为 AI 画布是基于真实尺度   3. AI 打开 .dwg 后,更改画布到所需要的大小   4. 之后用 PS 打开 AI 文件,可以维持想要的大小和比例   5. 而且 AI 可以读取天正的图层 课外了解   1. ETH 风格平面图   2. 日本创新性的停车方式   3. AI中的实时上色功能去了解

BG投资十问(Baillie Gifford)

  1,公司是否能正面推动经济、社会或者文化发 展? 如果做不到,BG可能就不会再往下看了。比如军火、赌博类公司再便宜,BG不会去看。 2,公司销售额在5年之内能否翻倍? 为什么要讨论销售额?因为BG关注公司成长,销售额是比较简单直观的指标,而利润可有一些方法调节。所以,BG主要先关注销售额,再看现金流。 3,公司5年之后会怎么样? 这是未来5到10年或者5到15年的中间维度,一是看公司的成长空间究竟还有多大,二是考虑5年之后,公司的短期估值会不会下降,会不会变得成熟。 4,公司的竞争优势到底是什么?随着时间会变 强还是变弱? BG认为,如果公司的竞争优势是不断变弱的,将来在更远期挣回现金流的可能性就更低了。 5,公司是否有非常独特的文化?文化是否能够进化? 毕竟要投资这么长时间,对创始人和公司管理文化是非常关注的。特别喜欢创始人领导的公司,而不是说创始人什么都不干,创始人在做房地产,找了一些人在做新公司,要尽量避免这样的公司。 谈到进化在BG看来,大部分公司都会有大公司病,需要看它怎么能维持比较灵活的管理,变大之后还能够保持非常快速的成长,比如亚马逊的day one文化(注:就是始终处在 创业启动状态,充满迷茫和压力,但是充满创造力和颠覆思想 )。 6,公司过去的回报率怎么样? 一方面是考虑到公司的ROE(净资产收益率),二是考虑公司的Margins(利润),看它的过去是不是值得投资,如果每年ROE都不到10个点,可能长期来看也没什么意思。 7,ROE能不能随着时间增长? 有些公司一开始没有盈利,一直在烧钱,5年之后说不定ROE会到20%或者更高,它是一个动态过程。这样的公司BG也会投资,包括一些早期项目,很多公司都是没有盈利的。 8,公司怎么分配资本? 资本现金流无非就是5种方法:再投资、收购、还债、分红、回购股份,要看公司在某个阶段的资本分配是不是最优的。 如果公司明明可以发展更多,结果乱分红了;或者是明明到了特别成熟的时候,还不愿意分红,都是错误的分配资本。 9,公司怎样才能长成5倍? BG关注的是怎样,而不是能不能。这会强迫BG每个人去设想一些未来的可能性,包括公司现在的底层架构能不能够延伸到更多的业务。 比如,早期的亚马逊或者是阿里,连一个概念都没有,BG会考虑这个公司有没有这种机遇,以后去扩展到可触及的市场。 10,市场对公司有哪些误解,哪些事...

产品随想 | 周刊 第62期:求索于主义与问题间

Products OpenRailwayMap   https://www.openrailwaymap.org/ 全世界的铁路基础设施地图 中国铁路地图   http://cnrail.geogv.org/zhcn/about 中文播客榜   https://xyzrank.com/#/hot-podcasts 欣慰,梁文道排名还挺靠前 NetGuard   https://github.com/M66B/NetGuard/ NetGuard provides simple and advanced ways to block access to the internet - no root required. Applications and addresses can individually be allowed or denied access to your Wi-Fi and/or mobile connection. ffmpeg buddy   https://evanhahn.github.io/ffmpeg-buddy/ a friend to help you build ffmpeg commands MaoXian Web Clipper   https://github.com/mika-cn/maoxian-web-clipper?auto_subscribed=false A web extension to clip information from web page. Save it to your local machine to avoid information invalidation. Not bored registration, Not charged. Pake   https://github.com/tw93/Pake 很简单的用 Rust 打包网页生成很小的 Mac App 潮流周刊   https://weekly.tw93.fun/ 挺不错的周刊,推荐之 Mackup   https://github.com/lra/mackup Keep your application settings in sync (OS ...

产品爱好者周刊 第36期:走进Linux

Products OpenShot   https://github.com/OpenShot/openshot-qt OpenShot Video Editor is an award-winning free and open-source video editor for Linux, Mac, and Windows 开源的视频剪辑工具,跨平台 Run   https://github.com/The-Run-Philosophy-Organization/run 润学全球官方指定GITHUB,整理润学宗旨、纲领、理论和各类润之实例 Dozer   https://github.com/Mortennn/Dozer Hide menu bar icons on macOS ThisIsWin11   https://github.com/builtbybel/ThisIsWin11 Win11的隐私保护 RoundedTB   https://github.com/torchgm/RoundedTB Add margins, rounded corners and segments to your taskbars! Droptop Four   https://github.com/Droptop-Four/Basic-Version Droptop Four is the fourth iteration of the popular dropdown app launcher for Windows & Rainmeter. LibreTube   https://github.com/Libre-tube/LibreTube An alternative frontend for YouTube, for Android. nheko   https://github.com/Nheko-Reborn/nheko Quaternion   https://github.com/quotient-im/Quaternion 多平台的Matrix客户端 FluffyChat   https://fluffychat.im/ Phone端的Matrix...

书房拾遗-第42至45期

  书房拾遗-第42至45期 42期 网络说:持有平安的人,大概率持有万科,而持有平安和万科的人,大概率也持有格力。 后来想通了,这类投资者,内心基本是将“龙头+白马+低市盈率+从股价高点已经大幅调整”这几个条件叠加,然后将其等同于正宗的“价值投资”,所以自然而然地汇总到同一条路上了。 网友:uta:老唐,请问企业价值为什么用现金流折现,而不是用净利润折现? 唐朝:因为净利润不是钱,净利润是账。账不是价值,钱才是价值。 地魔:请问在你的投资体系里,一家公司的ROE低于多少就不值得研究或者投资了? 唐朝:通常低于8%我就不大关注了。 43期 唐朝:银行非常常见的操作。放贷款的同时拉存款,降低了实际贷款额,提高了真实贷款利率。 WDS:没看懂,怎么提高贷款利率? 唐朝 :你获得银行贷款100万,利率5%。但同时要求你存款30万,存款利息2%,你实际贷款多少,成本多少? 唐朝:你一年支付5万利息,收到0.6万利息,实际使用资金70万,所以,你的贷款利率是4.4/70=6.29%。说这个是小学数学,不算夸张吧? 44期 Uta:是不是投资港股市场的股票风险要比A股的股票大? 唐朝:核心在于自己的理念。理念不对,A股港股都是风大浪大;理念对了,港股A股都是闲庭信步。 唐朝:主要是招股书披露的基础知识特别详细,一般我会看看,学习一些行业基础知识。和普通年报一样,没啥特别要注意的地方。 45期 数字货币替代的是现金,微信支付宝可以作为现金支付手段,也可以作为数字货币支付手段。货币的形式和支付的形式,是两码事。 数字货币不是微信支付宝的对手,它是运钞车、ATM机、点钞员、印刷厂……的对手。 Lei:老唐,对于初学者来说的话是先多看财报,还是先多看书籍比较好。谢谢 唐朝:我认为多看财报,不懂的时候查书,是进步最快的方式。 老唐从2017年1月首次介入腾讯至今,只有买入没有卖出,最低买过197.3港币,最高买过607港币,平均买入位置大概在465~470港币之间(就是去年底约480港币的平均买入位置减去所分京东股票价值),目前持有腾讯的市值是八位数,亏损是七位数。 但不装不端,我真没有任何压力。如前所述,真要是爆发核战争,拿股票现金黄金房子都是一码事,没区别;如果没有核战争,等上几年后我几乎100%确定我会是赚钱的,区别无外乎也就是赚几千万还是赚几百万而已。

毕设采访问卷自己的回答

1.      提一个作品中的有意思的设计点。       毕设中,自己觉得最有趣的一点是,尝试将商业建筑的一层开放给城市绿色空间,让其 作为 公园的一部分,让建筑的入侵感变弱。       很期待上海以后能出现这样的设计,尽管这样的设计短期看对于开发商没有利益,但我认 为 长期看对于城市使用者、对于城市管理者是有好处的,而我也还在思考,怎样去弥补这样 的 设计对于开发商造成的损失,希望能塑造共赢的局面,这个思考、设计的过程自己非常开 心,觉得有意思。 2.      如何看待城市发展过程中新旧交织的困局?设计师能做什么?        城市发展一般是从无到有,发展到一定阶段,最开始的就成了旧的,不可避免地会和新的产生冲突。        作为建筑设计师,面对旧的,我们常思考的是,它好不好?如果好,那新的要与他保持和谐,形式可能有新旧,但内核、设计语言是一致的;如果旧的不好,我们的思维是,趁着新的发展,一并改造、更新,让新旧作为整体面向城市。总的来说,就是设计师要整体考虑,对于旧的不能一味保留,也不能一味摈弃。 3.      完成毕设过程中遇到的困难以及如何解决的?从中收获了什么?        遇到最大的困难是城市总平面设计中,对于基地新旧建筑的考虑,自己常希望能多保留一些,但设计中又发现如何把握度有些困难,后来解决方法是制定一个标准,旧的能不能提供新价值,如果不能,果断抛弃;如果能,留下。        按这样思路,困难算成功克服,小小收获是当你很贪心地想多留东西时,想想它是不是累赘,想清楚就不累了。 4.      导师在你完成毕设过程中的角色?(可以举一个例子)        我的毕设导师是王林教授,她一直是我毕设中的鼓励者和点拨人。     ...

靠谱DNS

常用靠谱DNS 阿里DNS:223.5.5.5    223.6.6.6 114DNS:114.114.114.114  ---有DNS劫持和广告嵌入,域名屏蔽作为 腾讯DNS:119.29.29.29 V2EX DNS:199.91.73.222     178.79.131.110 OpenDNS:208.67.222.222    208.67.220.220    208.67.222.220    208.67.220.222 台湾中华电讯DNS: 168.95.192.1    168.95.192.2 香港宽带: 203.80.96.9    203.80.96.10 无污染DNS  中科大DNS----相对来说国内最少被污染的DNS 202.38.64.1 202.112.20.131 202.141.160.95 202.141.160.99 202.141.176.95 202.141.176.99 202.141.176.93 202.141.162.123 202.38.93.153 202.38.93.153 (教育网) 202.38.93.94 202.141.176.93 (中国移动) 202.141.162.123 (中国电信)  添加两个国人工程师自建的DNS,貌似假设在腾讯网段 115.159.146.99  南方 123.206.21.48  北方   网址 AIXYZ DNS

对话元气森林唐彬森:富翁、海盗与产品经理

https://mp.weixin.qq.com/s/15gAqbUJk-TTYP9mtkZFnw 战略要向二级市场投资人学习,管理要像 “海盗” 学习,产品要向游戏公司学习。 我们在 B 站晚会上花的钱和某些冠名商一样,但你记得他们的广告吗?不记得。我们要冠名就冠名这一天最好的节目,就只说一个产品——气泡水。当时团队都反对,但我没觉得这是一个重要的事情,多花点钱少花点钱不是主要矛盾。 如果你每年都有好牌,那说明这个牌不够好。你每天都有机会就不是机会。 三十而立,就是要多试多闯。四十不惑,要找到并专注你喜爱的事情。但最后你会发现就是命。你做的买卖本身好不好,决定了谁牛*。中国、韩国、俄罗斯之前最大的三家互联网企业,都是搜索引擎、社交、电商。 有一点我相信,饮料这个市场最终跑不赢游戏,实物消费增长长期一定跑不赢虚拟。人类社会满足高阶需求的产品,会越来越重要,满足基础需求的产品在 GDP 占比会越来越低。 ————Silas:高阶需求,比如Metaverse? 人在一个真实世界里实现自我,太消耗环境资源了么?游戏是让人在虚拟世界里实现自我,环保高效。 ————类似于建筑的3D建模 招这人是因为这是个对产品有爱的人,有爱,且不为钱发愁,才能把产品做出来。 看一个创始人有没有挣过钱,是一个最简单粗暴、证明他有没有能力的方法。 今天你需要 Java,明天需要 PHP,过两年又需要别的,那些东西是不值得学习的。真正有价值的是那些系统、语言、数学等最基本的东西。 (你也参加了中欧商学院、湖畔学院。)我从来没觉得课重要,我交朋友去了。 ————太真实了。 上商学院应该有四个原则:第一是不要面向机会,要面向未来大趋势。第二不要交生意上的伙伴,要交心灵的朋友,好多人去商学院做生意,这种人越做越差。我觉得一个 CEO 是很孤独的,需要一些人喝喝酒聊聊天。第三是不要只寻找变化的东西,要寻找不变的东西。 我就喜欢研究那些挣过钱的人说的话。 ————我也喜欢!

[海外建筑生活] MIT建筑系

原文链接: [海外建筑生活] MIT建筑系 发表于:寄托天下 2009-1-18 作者:dadazhe 前些日子和版大聊天,他让我写点关于mit的情况. 趁着寒假的尾巴,随便介绍点. mit的建筑系分为marchI marchII 和Smarch, 分别是三年半,两年半和两年. 前两这都是naab认证的,也就是说毕业以后可以考注册建筑师. smarch比较特殊,分为五个方向,你必须选择一个,分别是 urbanism, computation, theory&history, building technology, visual arts.