首页
论坛
专栏
课程

[翻译]利用高通EDL的程序员(1):获取访问和PBL内部信息

挽梦雪舞 2018-3-2 02:43 2507
PS:[其中一些加括号()的内容为使文章流畅而补充的连接语及注释]
1:12~实在胃疼的厉害,所以只编辑完一点,算了,明天下午再继续了!
2:16~还好有“胃安宁”!不能断,已经一半了,坚持就是胜利!我的黑客精神爆发吧~
2:43~写完了,药劲儿才发挥出来。睡觉!

今天先在此祝大家元宵节快乐!
足之处,还请多多指出!

国外最新安全推文整理(第19期)最后一篇
Exploiting Qualcomm EDL Programmers
利用高通EDL的程序员
原文链接:https://alephsecurity.com/2018/01/22/qualcomm-edl-1/
原作By:罗伊·海伊 (@roeehay) 和哈达 

目前网上存在许多指南,可能分为7部分,(这里我也不太清楚,欢迎大佬指点)在互联网中关于'unbricking'基于高通公司的移动设备。所有这些指南都使用紧急下载模式(EDL),这是Qualcomm Boot ROM(主引导加载程序)的备用引导模式。要使用这种模式,其操作人员只能是掌握了OEM签名的相关程序员,这些程序员似乎可以公开获得各种这样的设备。虽然他们的公开可用性的原因是未知的,但我们最好的猜测可是这些程序员可能是经常从OEM设备维修实验室泄密一些东西。除此之外,其他如一些OEM(如小米)也在官方论坛上发布。

在下面列出了我们设法获得的一些公开可利用程序员(社工)的部分列表:(表格一个一个输入实在费劲,只好以图片形式表示)


在这篇由5部分组成的博客文章中,我们将讨论泄密的程序员所引发的安全影响。第一部分介绍了PBL,EDL,Qualcomm Sahara和程序员的一些内部组成部分,重点介绍了Firehose。在第2部分中,我们将讨论利用EDL程序员功能的基于存储的攻击 - 我们将看到一些具体的例子,例如解锁小米Note 5A(代号ugglite)引导加载程序,以便安装和加载恶意启动映像,的信任。第3部分,第4部分和第5部分致力于我们研究的主要焦点 - 基于内存的攻击。在第3部分中,我们利用Firehose程序员的隐藏功能来执行具有最高特权的代码(EL3),例如,允许我们转储各种SoC的Boot ROM(PBL)。然后,我们介绍我们的exploit框架,firehorse,它为流水线程序员实现了一个运行时调试器(第4部分)。我们以诺基亚6的完整安全启动旁路攻击结束,该攻击MSM8937使用我们的漏洞利用框架。我们在PBL(或更准确地说,在PBL克隆中)实现代码执行,使我们能够击败信任链,在引导加载链的每个部分获得代码执行,包括TrustZone和高级操作系统(Android)本身。

我们的(具体)研究成果如下:
1、我们描述了Qualcomm EDL(Firehose)和撒哈拉协议。(第1部分)

2、我们创建了firehorse,这是一个基于Firehose的程序员公开的研究框架,能够调试/跟踪程序员(以及其他引导加载程序链,包括某些设备上的Boot ROM本身)。(第3部分和第4部分)

3、我们使用Firehose程序员和我们的研究框架获得并逆向设计了各种Qualcomm芯片组的PBL(MSM8994/MSM8917/MSM8937/MSM8953/MSM8974)。(第3部分)

4、我们获得了Nexus 6P(MSM8994)的RPM和调制解调器PBL。(第3部分)

5、我们设法使用基于存储的攻击解锁并根据各种Android Bootloader(例如小米Note 5A)进行解锁。(第2部分)

6、我们成功地对运行Snapdragon 425(MSM8937)的诺基亚6设备发起了端到端的攻击。我们认为这种攻击也适用于诺基亚5,并可能甚至可以扩展到其他设备,虽然未经验证。(第5部分)


高通安全启动
高通MSM设备启动过程的摘要概述如下:
[Primary Bootloader (PBL)]
|
`---NORMAL BOOT---.
                  [Secondary Bootloader (SBL)]
                  |-. 
                  | [Android Bootloader (ABOOT)]
                  | `-.    
                  |   [boot.img]
                  |   |-- Linux Kernel
                  |   `-- initramfs
                  |       `-.
                  |         [system.img]
                  |
                  `-[TrustZone]


                

在PBL从ROM-在装置之后是启动的。它很快将数字签名加载SBL到内部存储器(imem),并验证其真实性。我们所有提取的PBL都是32位(运行aarch32),其中SBLs是aarch32或者aarch64,其中PBL负责转换。某些设备具有XBL(可扩展引导加载程序)而不是SBL。所述SBL初始化DDR和负载数字签名的图像,例如ABOOT(它实现fastboot接口)的TrustZone,并再次验证其真实性。签名证书具有锚定在硬件中的根证书。

ABOOT然后验证的真实性boot或recovery图像,加载Linux内核和initramfs从boot或recovery图像。initramfs是一个cpio(gzipped)归档rootfs文件,/在Linux内核初始化期间被加载到(安装在RAM文件系统中)。它包含init二进制文件,第一个用户空间进程。在设备树Blob()中ABOOT准备initramfsLinux内核的内核命令行和参数DTB,然后将执行转移到Android(Linux)内核。

紧急下载模式(EDL)

基于MSM的设备包含一种特殊的操作模式 - 紧急下载模式(EDL)。在这种模式下,设备Qualcomm HS-USB 9008通过USB标识自己。EDL由PBL实施。由于PBL是ROM常驻,所以EDL不能被软件破坏。EDL模式本身实现了Qualcomm撒哈拉协议,该协议通过USB接受OEM数字签名的编程器(ELF最新设备中的二进制文件,MBN旧版设备中的二进制文件),充当SBL。现代这样的程序员实施该Firehose协议,接下来分析。

[Primary Bootloader (PBL)]
 |
 `---EDL---.
            [Programmer (Firehose)]
            `- Commands (through USB)
 

获得EDL访问权限

有几种方法将该设备强制转换为EDL。

许多设备在其电路板上公开所谓的测试点,如果在启动过程中缩短,会导致PBL将其执行转向EDL模式。(使用我们的研究框架,我们设法找出了负责评估这些测试点的PBL的确切位置,但接下来的内容更多。)

例如,以下是我们的小米Note 5A主板上的测试点:

补充:涉及到了安卓硬件类,不太了解

同样的原理,(下图是)诺基亚6:

此外,如果PBL无法验证SBL,或者未能初始化闪存,(那么)它将返回到EDL中,并且再一次,通过使用我们的研究工具,我们在PBL中找到了实现此功能的相关代码部分。如果一些SBL无法验证它们负责加载的图像,它们也可能重新启动EDL。
我们还遇到了在启动时测试USB D+/GND引脚的SBLs(例如:诺基亚6/5和旧小米SBLs),如果这些引脚被缩短,我们会重新启动到EDL。这就是众所周知的EDL或‘深闪’USB电缆。(没有查到相关设备,先直译了) 除此之外的其他设备,如OnePlus系列,在启动时测试硬件密钥组合,以此(来)实现类似的行为(结果)。



重新启动到EDL也可能发生在平台操作系统本身(如果已实施)以及允许adb访问(通过运行)adb reboot edl。我们向一些供应商报告了这种风险,包括OnePlus(CVE-2017-5947)和Google(Nexus 6 / 6P设备) -CVE-2017-13174。Google已在2017年12月的安全子弹入侵中修补CVE-2017-13174。(Nexus 6P需要root访问上下文,请参阅我们的漏洞报告以获取更多详细信息)。使用相同的机制,一些设备(主要是小米的设备)也允许/允许通过发行或通过专有命令重启EDL(即没有sysfsfastbootfastboot oem edlfastboot edloem)。有趣的是,在锁定的Android Bootloader中阻止这些命令有一个积极的趋势。

毋庸置疑,能够使用软件重新启动进入EDL的方式或仅使用此类USB电缆(描述缩短引脚的充电器)可启用危险的攻击媒介,如恶意USB端口(例如充电器)。

PBL内部

为了验证我们的基于经验的知识,我们使用了我们的调试器(第4部分)和IDA,以确定我们提取的PBL中的确切例程(第3部分),它决定了引导模式(正常或EDL)。在此之前,我们对MSM8937/MSM8917PBL做了一些初步分析,以便从高层角度了解其布局。我们相信其他PBLs没有那么不同。

0x100094PBL的重置处理程序(地址)大致如下所示(为便于阅读,省略了一些伪代码)

int init()
{
  int (__fastcall *v5)(pbl_struct *); // r1
  __mcr(15, 0, 0x100000u, 12, 0, 0);
  if ( !(MEMORY[0x1940000] & 1) )
  {
    if ( !reset_MMU_and_other_stuff() )
      infinite_loop();
    memzero_some_address();
    timer_memory_stuff();
    init_pbl_struct();
    v4 = 0;
    while ( 1 )
    {
      v5 = *(&initVector + v4);
      if ( v5 )
        v3 = v5(&pbl);
      if ( v3 )
        pbl_error_handler("./apps/pbl_mc.c", 516, 66304, v3);
      if ( ++v4 >= 0x14 )
      {
        while (1);
      }
    }
  }
}

init功能负责以下内容:

它设置VBAR为0x100000

它将堆栈重置为0x205400。

它在我们命名的函数中重置MMU和一些其他系统寄存器reset_MMU_and_other_stuff,位于0x110004。

它认为pbl_struct,这节省了PBL上下文数据,并且在PBL的整个循环周期内都存在。它的一部分接着传递给SBL。

此结构包含以下字段:(所示的符号当然是我们自己的估计。)
00000000 pbl结构;(大小=0xA4,mempdto_26)
00000000
00000000 hwinitfunction输入DCD?
00000004引导模式DCD?
0000000008字段_8 dcd?
00000000C闪存ReadDstStart DCD?
00000010闪存DstEnd DCD?
00000014 FlashReadCCallCallReadSize DCD?
00000018回调18 dcd?
0000001C字段_1Cdcd?
0000000020字段?
00000024 sbl_入DCD?
00000024 sbl_m_flash结构??
00000050回调50 dcd?
 00000054字段_54 dcd?
 00000058定时器0 dcd?
0000005C timer5c dcd?
 00000060 flash_init_timer dcd?
 00000064字段_64 dcd?
 00000068字段_68 dcd?
 0000006C字段_6C DCD?
0000000070 Timer70dcd?
[...]
 000000A4 PBL_结构结束
(其中)一些值得注意的字段包括SBL条目之后,将其设置为SBL的入口点,并且pbl2sbl数据(中)它(所)包含传递给即将跳转到SBL的参数(参见下一个)。
它迭代例程列表(init向量),位于0x10CE0C.
ROM:0010CE0C     init_vector   
ROM:0010CE0C                  
ROM:0010CE0C                  
ROM:0010CE10   DCD 0
ROM:0010CE14   DCD pbl_hw_init
ROM:0010CE18   DCD 0
ROM:0010CE1C   DCD pbl_initialize_pagetables
ROM:0010CE20   DCD 0
ROM:0010CE24   DCD pbl_stack_init
ROM:0010CE28   DCD 0
ROM:0010CE2C   DCD pbl_copy_some_data
ROM:0010CE30   DCD 0
ROM:0010CE34   DCD pbl_sense_jtag_test_points_edl
ROM:0010CE38   DCD 0
ROM:0010CE3C   DCD pbl_flash_init
ROM:0010CE40   DCD 0
ROM:0010CE44   DCD pbl_load_elf_sahara_stuff
ROM:0010CE48   DCD 0
ROM:0010CE4C   DCD pbl_mc_init5
ROM:0010CE50   DCD 0
ROM:0010CE54   DCD pbl_jmp_to_sbl
ROM:0010CE58   DCD 0
这些例程中的每一个在PBL的操作中都扮演着重要(不可替换)的角色。他们中的一些将(借此)得到我们的报道(从而)贯穿整个系列的博客文章。
基于PBL&EDL模式仲裁的SBL加载
探测是否进入EDL的例程是PBL传感器JTAG测试点EDL:
int __fastcall pbl_sense_jtag_test_points_edl(pbl_struct *pbl)
{
[...]
  pbl->bootmode_reason = bootmode_reason_0;
  if ( !(MEMORY[0xA601C] & 8) )                 // jtag fuse
  {
    if ( MEMORY[0xA606C] & 0x8000 )             // check test points
    {
      pbl->bootmode = edl;
      pbl->bootmode_reason = tp;
      return 0;
    }
    v4 = MEMORY[0x193D100];
    v5 = MEMORY[0x193D100] & 0xF;
    switch ( v5 )
    {
      case 1:
        v6 = bootmode_reason_2;
        pbl->bootmode = edl;
        break;
      case 2:
        pbl->bootmode = bootmode_80;
        pbl->bootmode_reason = bootmode_reason_3;
        goto LABEL_17;
      case 3:
        pbl->bootmode = bootmode_81;
        v6 = bootmode_reason_4;
        break;
      default:
        goto LABEL_17;
    }
    pbl->bootmode_reason = v6;
LABEL_17:
    MEMORY[0x193D100] = v4 & 0xFFF0;
    if ( pbl->bootmode_reason )
      return 0;
  }
  v2 = (MEMORY[0xA602C] >> 1) & 7;
  if ( v2 >= 8 )
    pbl_error_handler("./apps/pbl_hw_init.c", 227, 262656, ((MEMORY[0xA602C] >> 1) & 7));
  pbl->bootmode = v2;
  return 0;
}
通过对此代码的跟踪,我们得出了以下结论:0xA606C包含测试点状态(0x8000<=>‘缩短’)。此外,通过软件重新启动EDL是通过断言0x193D100登记(亦称tcsr-启动-misc-检测 例程设置自举模式字段在PBL上下文中。稍后,PBL实际上将跳过SBL图像加载,进入EDL模式。
首先,pbl将通过设置>闪存结构->初始化=0xA...。这是在里面做的撒哈拉的东西如果PBL->引导模式是EDL,或者闪存初始化失败:
int __fastcall pbl_flash_init(pbl_struct *pbl)
{
  
 [...]
  v3 = pbl->bootmode;
  if ( v3 == edl )
  {
    if ( some_sahara_stuff(v1) )
		   pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
	  if ( !flashStructLocation )
       pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
	  return 0;
  }
  [...]
  v2 = pbl_flash_sdcc(pbl);                  
  if ( v2 != 3 && some_sahara_stuff(pbl) )
    pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
  if ( !flashStructLocation )
    pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
  [...]
  return 0;
}

int __fastcall some_sahara_stuff(pbl_struct *pbl)
{
  [...]
  if ( !pbl )
    pbl_error_handler("./apps/pbl_sahara.c", 911, 819200, 0);
  if ( !a->flash )
    pbl_error_handler("./apps/pbl_sahara.c", 915, 819200, 0);
  [...]
  pbl->flash->initialized = 0xA;
  return 0;


稍后,当PBL实际尝试从闪存驱动器加载SBL时,它将考虑PBL->FLASH->初始化字段并使用撒哈拉协议:
unsigned int __fastcall pbl_jmp_to_sbl(pbl_struct *pbl)
{
  pbl_struct *v1; // r4
  signed int v2; // r0

  v1 = pbl;
  if ( !pbl->sbl )
    pbl_error_handler("./apps/pbl_mc.c", 420, 98304, 0);
  pbl->pbl2sbl_data->timer = get_timer();
  sub_10F364();
  set_vbar(infinite_loop);
  v2 = initialize_secondary_pt_for_sbl(pbl);
  if ( v2 )
    pbl_error_handler("./apps/pbl_mc.c", 100, v2 | 0x20000, 0);
  (pbl->sbl)(pbl->pbl2sbl_data);
  return -1;
}

Qualcomm Firehose程序员内部

如上所述,现代EDL程序员实施Qualcomm Firehose协议。分析几个程序员的二进制文件很快就会发现命令通过XML(通过USB)传递。例如,以下XML使程序员刷新一个新的辅助引导加载程序(SBL)映像(也通过USB传输)。

<?xml version="1.0" ?>
<data>
  <program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="sbl1.bin"
           label="sbl1" num_partition_sectors="1024" physical_partition_number="0" 
           size_in_KB="512.0" sparse="false" 
           start_byte_hex="0xc082000" start_sector="394256"/>
</data>、

<?xml版本=“1.0”?>
<数据>
  <程序 扇区尺寸IN字节="512" 文件扇形偏移量="0" 文件名“sbl1.bin”
           标号=“sbl1” num分区扇区="1024" 物理分区数="0" 
           尺寸_in_KB="512.0" 稀疏=“假” 
           起始字节十六进制=“0xc082000” 起动扇区="394256"/>
</data>

可以看到,指示程序员刷新新图像的相关标签是program。

挖掘程序员的代码(uggliteaarch32本例中的小米Note 5A程序员)表明它实际上是某种扩展的SBL。其主要程序如下:

int __cdecl SBLMain(pbl2sbl_struct *pbl2sbl_data)
{
  sbl_struct *vSbl; // r6
  
  [...]
  SBLStart(&off_805C070, vSbl);
  sub_801D0B0(off_805C078);
  sub_8040A2C(vSbl);
  initSBLStruct(&sblStruct, &unk_80678A0);
  sub_80408F4(&sblStruct);
  [...]
  return callImageLoad(&sblStruct, 0x15, imageLoad);
}

pbl2sbl_data是在pbl_jmp_to_sbl函数的最后从PBL传递给SBL的数据。sbl维护SBL上下文数据,其第一个字段指向副本pbl2sbl_data。

ImageLoad是负责加载下一个引导程序的功能,包括ABOOT:

int __fastcall ImageLoad(sbl_struct *sbl, image_load_struct *aImageLoad)
{
  [...]
  loop_callbacks(sbl, aImageLoad->callbacks);
  [...]
  if ( imageLoad->field_14 == 1 )
  {
    v5 = sub_801CCDC();
    uartB("Image Load, Start");
    v8 = imageLoad->field_C;
    if ( v8 == 1 )
    {
      if ( !boot_pbl_is_flash() )
      {
        [...]
        ERROR("sbl1_sahara.c", 816, 0x1000064);
        while ( 1 )
          ;
      }
	[...]
	  boot_elf_loader(v9, &v27);
	[...] 
  loop_callbacks(sbl, imageLoad->callbacks2);
	[...]
  return result;
}
ImageLoad首先调用(使用loop_callbacks例程)一系列初始化函数:
LOAD:0805C0C8 callbacks  DCD nullsub_35+1
LOAD:0805C0CC            DCD boot_flash_init+1
LOAD:0805C0D0            DCD sub_801ACB0+1
LOAD:0805C0D4            DCD sub_804031C+1
LOAD:0805C0D8            DCD sub_803FF08+1
LOAD:0805C0DC            DCD sub_803FCD0+1
LOAD:0805C0E0            DCD firehose_main+1
LOAD:0805C0E4            DCD sub_8040954+1
LOAD:0805C0E8            DCD clock_init_start+1
LOAD:0805C0EC            DCD sub_801B1AC+1
LOAD:0805C0F0            DCD boot_dload+1
firehose_main最终落入主流门环路中,永不返回。有趣的是,在实际的SBL中ugglite,这一系列的初始化回调如下所示:
LOAD:0805B0C8 callbacks       DCD nullsub_36+1       
LOAD:0805B0CC                 DCD boot_flash_init+1
LOAD:0805B0D0                 DCD sub_8018E8C+1
LOAD:0805B0D4                 DCD sub_8039C80+1
LOAD:0805B0D8                 DCD sub_803986C+1
LOAD:0805B0DC                 DCD sub_8039634+1  
LOAD:0805B0E0                 DCD nullsub_37+1
LOAD:0805B0E4                 DCD sub_803A2B8+1
LOAD:0805B0E8                 DCD clock_init_start+1
LOAD:0805B0EC                 DCD sub_8019388+1
LOAD:0805B0F0                 DCD boot_dload+1

因此,他们仅在firehose_main回调方面有所不同!

探索Firehose程序员的能力

深入研究兔子洞,分析firehose_main及其后代揭示了所有Firehose接受的XML标签。

void __fastcall firehose_main(sbl_struct *sbl)
{
  firehoseInit();
  while ( 1 )
  {
    handle_input();
    OUT("logbuf@0x%08X fh@0x%08X");
  }
}

int handle_input()
{
[...]
  do
  {
[...]
    result = sub_802C764(&v3, SHIDWORD(v0), v0, SHIDWORD(v0), &v4);
  }
  while ( !v4 );
  if ( !result )
  {
    v2 = sub_804A55E(&unk_806DCC0, v3);
    if ( sub_8049CEC(v2) == 1 )
    {
      OUT("XML (%d bytes) not validated");
[...]
    }
    else if ( v4 <= 0x1000 )
    {
[...]
      do
        result = parse_command();
      while ( result != 1 );
    }
    else
    {
      OUT("XML file cannot fit in buffer");
      result = sub_804A55E(&unk_806DCC0, 0);
    }
  }
  return result;
}

int firehose_parse_command()
{
[...]
  v1 = get_next(&xmlCommand);
  memzero(&v4, 0x200u);
  switch ( v1 )
  {
    case 0:
      v2 = 1;
      goto LABEL_46;
    case 4:
      sub_8026D40(&xmlCommand, &v4, 0x200uLL);
      if ( get_element_name(&xmlCommand, "data") )
      {
        v2 = 1;
        goto LABEL_46;
      }
      break;
    case 1:
      if ( get_element_name(&xmlCommand, "configure") )
      {
        v2 = sub_80157B4();
        goto LABEL_46;
      }
      if ( get_element_name(&xmlCommand, "program") )
      {
        v2 = fh_program();
        goto LABEL_46;
      }
[...]      
	if ( get_element_name(&xmlCommand, "read") )
      {
        v2 = fh_read();
        goto LABEL_46;
      }
      if ( get_element_name(&xmlCommand, "getstorageinfo") )
      {
        v2 = fh_getstorageinfo();
        goto LABEL_46;
      }
      if ( get_element_name(&xmlCommand, "erase") )
      {
        v2 = fh_erase();
        goto LABEL_46;
      }
[...]
      if ( get_element_name(&xmlCommand, "peek") )
      {
        v2 = fh_peek();
        goto LABEL_46;
      }
      if ( get_element_name(&xmlCommand, "poke") )
      {
        v2 = fh_poke();
        goto LABEL_46;
      }
[...]
}

(对于)即将到来的下一步:攻击Firehose的程序员,
(先)简单地浏览一下这些标记就足以让人们意识到,Firehoss程序员已经远远超出了分区闪烁(循环)的范围。 这些强大功能中的一些功能在接下来的部分中(将会)得到(更为)广泛的介绍。
具体而言,在下一章中,我们将使用并继续这里提出的(相关)研究来开发:

1、基于存储的攻击(利用program/update&read)。这些在第2部分中介绍。

2、基于内存的攻击(利用peek和poke)。这些被覆盖在第3部分,第4部分与第5部分。

未完待续……期待后面的内容。


[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

最后于 2018-3-13 12:46 被挽梦雪舞编辑 ,原因: 重新排版
最新回复 (20)
挽梦雪舞 2018-3-2 12:51
2

0

自己顶楼!今天怎么没人?
bxc 6 2018-3-2 15:25
3

0

这是机器翻译吗。。看起来好困难。
挽梦雪舞 2018-3-2 15:56
4

0

bxc 这是机器翻译吗。。看起来好困难。
手机打的,还没排版,纯手工啊!兄弟,你自己拿机器翻译后对照一下。。
挽梦雪舞 2018-3-2 16:00
5

0

bxc 这是机器翻译吗。。看起来好困难。
今天人们都是怎么了?原来是过十五,忙忘了
最后于 2018-3-3 00:03 被挽梦雪舞编辑 ,原因:
挽梦雪舞 2018-3-2 23:48
6

0

bxc 这是机器翻译吗。。看起来好困难。
排版完了,看看还可以不?
bxc 6 2018-3-2 23:57
7

0

挽梦雪舞 排版完了,看看还可以不?
可以,感谢分享
挽梦雪舞 2018-3-3 00:03
8

0

bxc 可以,感谢分享
嗯嗯,谢谢支持,一些专有名词是直译的,不足之处多多提出,我会不断改进的。
yy虫子yy 2018-3-3 01:31
9

0

好吧,表示看不懂
挽梦雪舞 2018-3-3 01:50
10

0

yy虫子yy 好吧,表示看不懂
什么专业的?
yy虫子yy 2018-3-3 02:38
11

0

挽梦雪舞 什么专业的?
专业搬砖的我,不说了,明天继续搬砖 
挽梦雪舞 2018-3-3 02:51
12

0

yy虫子yy 专业搬砖的我,不说了,明天继续搬砖 [em_51]
是挺晚了,唉,彻底没心情了,翻译不适合我,自己默默看好了。累了。
CCkicker 2018-3-5 15:16
13

0

挽梦雪舞 是挺晚了,唉,彻底没心情了,翻译不适合我,自己默默看好了。累了。
多翻几篇就好了  专有名词,可以不译,大家都懂的,哈哈
挽梦雪舞 2018-3-5 22:18
14

0

哆啦咪 多翻几篇就好了[em_19] 专有名词,可以不译,大家都懂的,哈哈[em_12]
嗯嗯。这话好有深意……
longbit 2018-3-12 21:26
15

0

学习中,谢谢楼主!
聖blue 2018-3-12 23:27
16

0

挽梦雪舞 2018-3-12 23:36
17

0

聖blue [em_63]
Umiade 2018-3-13 09:14
18

0

泄漏的程序员是什么意思,程序员还能被调试跟踪的吗?
没有任何针对译者的意思,不过这和机翻还是有很大的相似性

最后于 2018-3-13 09:18 被Umiade编辑 ,原因:
挽梦雪舞 2018-3-13 12:48
19

0

Umiade 泄漏的程序员是什么意思,程序员还能被调试跟踪的吗?没有任何针对译者的意思,不过这和机翻还是有很大的相似性
嗯嗯,原谅我的直译,牛津查的,你提到的一些已经改了,谢谢指出,哪里还有问题截图回复。谢谢!
fishgs 2018-3-13 14:51
20

0

你要是把Programmer翻译为编程器,可能会顺一点……
挽梦雪舞 2018-3-13 23:08
21

0

fishgs 你要是把Programmer翻译为编程器,可能会顺一点……
嗯,这个是针对编译器被调试,跟踪及其他一系列操作的,有点晕,实在没找到那块内容,方便的话给整个截图,还有就是编程器读起来有点不顺口,编译器更好一点,就好比直接说成IDE
返回