sponsored links

inlinehook 看這一篇足夠了

1、什麼是hook?

hook的中文含義是鉤子,介紹hook含義之前,先放一個“現實世界裡的hook”:

2021年9月,曾報道“水門事件”的華盛頓郵報記者鮑勃·伍德沃德,披露出了一件大事,直接引發了世界震動。

馬克·米利,美國的4星上將,美參聯主席,目前美國軍方的4號人物,絕對的美國高層。 

在2021年1月8日,美國國會山騷亂事件的兩天後,米利再次給中國打了個電話: 

“美國的情況“很穩定”,一切都很好,國會山騷亂僅僅只是“偶然事件””。

“你知道的,民主有時候就是有點亂糟糟的”。 

同一天,米利在五角大樓裡秘密召開了一次會議,告知其他高階將領: 

“自今天之後,軍隊執行任何重大行動之前必須首先與他磋商。”

“不管誰給你們下達命令,你們都要按照“程式”來執行,而我就是這個“程式”!”

如果把作戰行動當作一個函式,下達作戰指令當作一次函式呼叫,川建國同志當作函式呼叫者,那麼米利的行為就相當於hook了作戰函式。這樣,米利會監控所有作戰行動,並且可以中止作戰行動。

透過上面的例子,大家應該對hook有了一個初步的認識了,hook後呼叫原函式時,直接跳到指定的函式。

2、hook技術有什麼用?

hook技術的應用很廣泛,例如筆記本上的防毒軟體、網咖系統裡的監控軟體、pc遊戲的反外掛防護系統、螢幕錄製軟體fraps都會使用hook來實現一些高階功能。

安全軟體:360等安全軟體會hook一些入口函式(例如驅動載入)獲得系統最高許可權。

監控軟體:你在網咖上網時,監控軟體hook了connect、send、recv等函式,這樣訪問的所有網站都會被記錄下來(不要做壞事)。

遊戲攻防:例如fps遊戲外掛會hook d3d的DrawIndexedPrimitive實現透視功能。

3、什麼是inlinehook?

hook的實現有很多種,inlinehook是hook的一種方式。透過修改原函式開頭的彙編指令,直接跳轉到指定函式。

開源的inlinehook庫有很多,例如subhook和微軟的Detours,本文會帶大家從0開始,一步一步實現一個基礎的inlinehook庫。

4、inlinehook庫程式碼結構

程式碼已經上傳gitee,不用github的主要原因是國內gitee網速好一些。文章先介紹一些hook庫程式碼結構,再介紹典型的使用場景。

關注微信“東北碼農”,回覆inlinehook可獲取程式碼地址。

4.1、hook庫程式碼

xx_mem.hpp:修改程式碼段屬性,改為可讀寫、可執行

xx_asm.hpp:工具函式,向目標地址寫入彙編指令,例如jmp,ret,push等。

xx_inline_hook.hpp:封裝hook的c介面。包括hook、製作跳板、偏移重定位等操作。

4.2、經典場景

test_hook_jmp32:inlinehook入門示例,基礎0xe9的jmp跳轉。

test_hook_jmp64:64bit作業系統跳轉函式,hook系統函式必備。

test_trampoline:跳板函式,hook以後如何再呼叫原函式?

test_trampoline_relocation:跳板函式中包含相對偏移,如何重定位?

5、inlinehook程式碼實現

咱們透過這幾個經典場景,來說一說。

場景一:初級、經典inlinehook

inlinehook本質是一種彙編程式碼修改技術,修改原函式,開頭第一條彙編指令改為jmp跳轉到我們的函式。先看一下demo:

//test_hook_jmp32
///////////////////////////////////////////////////////////
void hello_world()
{
  printf("[call %s]\n",__FUNCTION__);
}
void my_hello_world()
{
    printf("[call %s]\n", __FUNCTION__);
}

void test_hook_jmp32()
{
    // 修改被hook函式記憶體屬性為可寫
  xx_mem_unprotect(hello_world, 4096);
    // 在函式開頭插入jmp語句,跳轉到my_hello_world
  xx_setjmp32(&hello_world, &my_hello_world);
    // 測試一下
  hello_world();
}
int main()
{
    printf("\n\n======test_hook_jmp32=================\n");
    test_hook_jmp32();
 }

執行結果螢幕輸出

======test_hook_jmp32=================
[call my_hello_world]
可見,呼叫hello_world時,實際執行的是my_hello_world。

下面說說實現,主要有兩步:修改程式碼段記憶體屬性;插入jmp彙編指令。

修改程式碼段記憶體屬性
// 修改被hook函式記憶體屬性為可寫
xx_mem_unprotect(hello_world, 4096);
一段記憶體有是否可讀、是否可寫、是否可執行等屬性。程式碼段預設是不可寫的(不可修改),所以需要先設定為可寫才能修改。xx_mem_unprotect的windows實現,呼叫VirtualProtect系統api來實現(linux下的api是mmap)

static bool xx_mem_unprotect(void* address, size_t size) {
DWORD old_flags;
BOOL result = VirtualProtect(address,
size,
PAGE_EXECUTE_READWRITE,
&old_flags);
return result == TRUE;
}

插入jmp彙編指令
// 在函式開頭插入jmp語句,跳轉到my_hello_world
xx_setjmp32(&hello_world, &my_hello_world);
hook以後,hello_world的彙編指令如下:

void hello_world()
{
00007FF6642410B0 E9 8B 00 00 00       jmp         my_hello_world (07FF664241140h)
00007FF6642410B5

jmp指令共佔用5個位元組,指令opcode 0xe9佔用1位元組,偏移量佔用4位元組。偏移量是目標地址相對本地址的偏移。

上面的07FF664241140(下一條指令地址)-00007FF6642410B5(目標地址)=8b,剛好是e9後面的值。

程式碼實現部分

首先製作一個輔助類,用於向目標地址寫入jmp彙編指令

彙編指令

//JMP rel32
class jmp_rel32
{
public:
  struct asm_cmd {
    uint8_t opcode_;
    int32_t rel32_;
  };
  static void write(void* cmd_addr, int32_t rel32) {
    auto* cmd = (asm_cmd*)cmd_addr;
    cmd->opcode_ = 0xe9;
    cmd->rel32_ = rel32;
  }
  static uint8_t size() { return sizeof(asm_cmd); }
};

其中write是寫入指令,size返回本指令長度,後續會不斷擴充彙編指令類,都有這兩個介面。

下面再實現xx_setjmp32 就簡單多了,計算一下偏移,然後寫入指令。

// ret:兩個地址的偏移
static int64_t xx_get_offset(void* src, void* dst) {
  return (char*)dst - (char*)src;
}

// ret:返回從src jmp 到dst的偏移
static int64_t xx_get_jmp32_offset(void* src, void* dst) {
  return xx_get_offset((char*)src + jmp_rel32::size(), dst);
}
// 寫入彙編,32bit位移跳轉,jmp到dst
// ret 5(修改5 byte)
static uint32_t xx_setjmp32(void* src, void* dst) {
  int32_t offset = (int32_t)xx_get_jmp32_offset(src, dst);
  jmp_rel32::write(src, offset);
  return jmp_rel32::size();
}

現在,我們的hook庫支援功能如下:

  • 32位跳轉

場景二:64位系統的hook

上一個場景使用的jmp指令跳轉,jmp指令有一個限制是,跳轉地址與原地址的偏移不能超過int32的範圍,在64bit作業系統下可能無法跳過去。我們的hook庫需要進化一下,支援在64位地址空間下任意跳轉。

偏移足夠小時,儘量使用32位jmp跳轉。因為32位跳轉只修改5位元組,而64位大跳需要修改14位元組,hook時儘量減少對原函式修改。

先看一下demo,為了展示“大跳”,hook一個系統函式

//test_hook_jmp64
///////////////////////////////////////////////////////////
int __cdecl my_fclose(
    _Inout_ FILE* _Stream
) {
    printf("[call %s]\n", __FUNCTION__);
    return 0;
}
void test_hook_jmp64()
{
    // 判斷偏移是否滿足32位
    int64_t offset = xx_get_offset(&fclose, &my_fclose);
    bool need_far_jmp = xx_int32_overflow(offset);
    printf("need_far_jmp=%u \n", need_far_jmp);

    // 修改被hook函式記憶體屬性為可寫
    xx_mem_unprotect(&fclose, 1024);
    // 藉助ret彙編指令實現64位跳轉
    xx_setjmp64(&fclose, &my_fclose);
    // 測試一下
    fclose(nullptr);
}
int main()
{
    printf("\n\n======test_hook_jmp64=================\n");
    test_hook_jmp64();
}

執行結果螢幕輸出

======test_hook_jmp64=================
need_far_jmp=1
[call my_fclose]
need_far_jmp=1代表需要“大跳”。

如何判斷是否需要大跳

判斷時,計算原函式和跳轉函式的偏移,是否在int32的範圍即可。

// ret:是否超過int32範圍
static bool xx_int32_overflow(int64_t val) {
  return val < INT32_MIN || val > INT32_MAX;
}

64位大跳實現
實現時藉助ret彙編指令。先說說ret彙編指令,在正常函式呼叫時,會先將返回地址入棧,等函式執行完後再呼叫ret指令完成返回地址出棧+跳轉。

實現64位跳轉時,先把要跳轉的地址入棧,然後再呼叫ret指令實現跳轉。

先看一下修改後,原函式的彙編程式碼:

00007FF97A2596A0 68 D0 10 A0 BE       push        0FFFFFFFFBEA010D0h
00007FF97A2596A5 C7 44 24 04 F7 7F 00 00 mov         dword ptr [rsp+4],7FF7h
00007FF97A2596AD C3                   ret
push+mov指令負責把地址壓棧,ret指令負責跳轉。

程式碼實現部分

首先也是彙編指令輔助類,這裡需要3個push、mov、ret。每個輔助類還是有write和size兩個介面。

// PUSH imm32
class push_imm32
{
public:
  struct asm_cmd {
    uint8_t  opcode_;
    uint32_t imm32_;
  };

  static void write(void* cmd_addr, uint32_t imm32) {
    auto* cmd = (asm_cmd*)cmd_addr;
    cmd->opcode_ = 0x68;//jmp
    cmd->imm32_ = imm32;
  }

  static uint8_t size() { return sizeof(asm_cmd); }
};
// mov dword ptr[rsp + offset],imm32
class mov_rsp_ptr_imm32
{
public:
  struct asm_cmd {
    uint8_t  opcode_;
    uint8_t  para1_;
    uint8_t  reg_type_;
    int8_t  offset_;
    uint32_t imm32_;
  };
  static void write(void* cmd_addr, int8_t off, uint32_t imm32) {
    auto* cmd = (asm_cmd*)cmd_addr;
    cmd->opcode_ = 0xc7;//mov
    cmd->para1_ = 0x44;// to reg ptr
    cmd->reg_type_ = 0x24;//rsp
    cmd->offset_ = off;
    cmd->imm32_ = imm32;
  }
  static uint8_t size() { return sizeof(asm_cmd); }
};

// ret
class ret
{
public:
  struct asm_cmd {
    uint8_t  opcode_;
  };

  static void write(void* cmd_addr) {
    auto* cmd = (asm_cmd*)cmd_addr;
    cmd->opcode_ = 0xc3;
  }

  static uint8_t size() { return sizeof(asm_cmd); }
};

寫入這3條彙編,程式碼如下:

// 寫入彙編,64bit位移跳轉利用ret來跳轉
// ret 14(修改14 byte)
static uint32_t xx_setjmp64(void* src, void* dst) {
  char* cmd_addr = (char*)src;

  push_imm32::write(cmd_addr, (uint32_t)(uintptr_t)dst);
  cmd_addr += push_imm32::size();

  mov_rsp_ptr_imm32::write(cmd_addr, 4, (uint32_t)(((uintptr_t)dst) >> 32));
  cmd_addr += mov_rsp_ptr_imm32::size();

  ret::write(cmd_addr);
  return push_imm32::size() + mov_rsp_ptr_imm32::size() + ret::size();
}

現在,我們的hook庫支援功能如下:

  • 32位跳轉
  • 64位跳轉

為了方便使用,封裝了一個xx_setjmp函式,自動判斷偏移量,選擇合適的跳轉方式,程式碼如下:

// 寫入彙編,自動判斷偏移選擇適合彙編指令
// ret 修改位元組數
static uint32_t xx_setjmp(void* src, void* dst) {
  // 64bit system,check jmp32 ok.
  int64_t dis = xx_get_jmp32_offset(src, dst);
  if (xx_int32_overflow(dis))
    return xx_setjmp64(src, dst);
  else
    return xx_setjmp32(src, dst);
}

場景三:使用跳板,跳回原函式

前兩個場景都沒有呼叫原函式,如果想呼叫原函式怎麼做呢?例如實現監控程式,跳轉函式中記錄函式引數,然後再呼叫原函式。直接呼叫原函式是不行的,會再次跳轉到跳轉函式。

我們先看一下demo,模擬監控功能,記錄函式呼叫引數後,藉助“跳板”執行原邏輯。

//test_trampoline
///////////////////////////////////////////////////////////
char xx_trampoline[1024];
int sum(int a,int b)
{
    return a + b;
}
int mysum(int a, int b)
{
    printf("[call %s]!a=%d,b=%d\n", __FUNCTION__,a,b);
    //typedef int(sum_func_t)(int, int);
    //sum_func_t *f = (sum_func_t*)((char*)xx_trampoline);
    // 執行原邏輯
    auto ori_func = xx_trampoline_to_func(&sum, xx_trampoline);
    return (*ori_func)(a, b);;
}

void test_trampoline()
{
    // 製作跳板
    xx_mem_unprotect(xx_trampoline,1024);
    xx_make_trampoline(&sum, xx_trampoline, 4+4);
    // hook
    xx_mem_unprotect(&sum, 1024);
    xx_setjmp(&sum, &mysum);
    // test
    int a = sum(1, 2);
    printf("a=%u\n",a);
}
int main()
{
    printf("\n\n======test_trampoline=================\n");
    test_trampoline();
}

執行結果螢幕輸出

======test_trampoline=================
[call mysum]!a=1,b=2
a=3

如何製作跳板?例如hook時破壞了原函式的前3條彙編指令,那麼hook前需要申請一塊記憶體把前3條指令複製過去,然後再跳轉到原函式第四條指令,這塊記憶體就是所謂的跳板。

跳板需要複製多少程式碼
複製彙編程式碼時,是以彙編指令為單位來複制的,不能修改了5位元組,就複製5位元組,那樣有可能複製到半條指令。究竟複製多少位元組呢?好多hook庫都內嵌了一個反彙編引擎,自動判斷需要複製條數,本文為了讓大家更深入理解,決定人工計算,透過引數指定複製位元組數。

首先先判斷原函式需要修改多少位元組(使用32位跳轉還是64位跳轉),xx_setjmp 的返回值是修改位元組數,就是為了這裡使用。我們的demo是修改5位元組。

// 寫入彙編,自動判斷偏移選擇適合彙編指令
// ret 修改位元組數
static uint32_t xx_setjmp(void* src, void* dst)

然後觀察原函式彙編程式碼

int sum(int a,int b)
{
00007FF728C411F0 89 54 24 10          mov         dword ptr [b],edx
00007FF728C411F4 89 4C 24 08          mov         dword ptr [a],ecx
00007FF728C411F8 8B 44 24 10          mov         eax,dword ptr [b]
00007FF728C411FC 8B 4C 24 08          mov         ecx,dword ptr [a]

最少修改5位元組 ,完整指令,所以需要複製2條指令,4+4=8位元組。

複製程式碼+跳回去
製作跳板比較簡單,複製程式碼後面跟著跳轉語句跳回原函式。

// 製作跳板,hook以後再跳回去
// ret:跳板長度
static uint32_t xx_make_trampoline(void* src, void* trampoline, uint32_t copy_len) {
  memcpy(trampoline, src, copy_len);
  return copy_len + xx_setjmp((char*)trampoline + copy_len,(char*)src + copy_len);
}

呼叫前,別忘了給跳板記憶體增加可執行屬性!

    // 製作跳板
    xx_mem_unprotect(xx_trampoline,1024);
    xx_make_trampoline(&sum, xx_trampoline, 4+4);

現在,我們的hook庫支援功能如下:

  • 32位跳轉
  • 64位跳轉
  • 跳板功能

場景四:跳板偏移修復

上個場景中,跳板直接複製原函式程式碼就行了,不過有時複製來的程式碼確是錯的,例如下面這個demo,我們看一下

bool g_ready = true;
char xx_trampoline2[1024];

void flag_print() {
    if (g_ready) {
        printf("[call %s]!ready\n",__FUNCTION__);
    }
    else {
        printf("[call %s]!not ready\n", __FUNCTION__);
    }
}

void my_flag_print() {
    printf("[call %s]!\n", __FUNCTION__);
    auto ori = xx_trampoline_to_func(&flag_print, xx_trampoline2);
    (*ori)();
}
void test_trampoline_relocation() {
    // 製作跳板
    xx_mem_unprotect(xx_trampoline2, 1024);
    xx_make_trampoline(&flag_print, xx_trampoline2, 4 + 7);
    // 跳板偏移重定位
    xx_reloc_offset(&flag_print, xx_trampoline2, 4 + 3 );

    xx_setjmp(&flag_print, &my_flag_print);
    flag_print();
}

什麼時候跳板需要重定位?

我們在第25行執行了偏移重定位,我們看一下重定位之前的原函式和跳板函式的彙編指令。

原函式

void flag_print() {
00007FF635091070 48 83 EC 28          sub         rsp,28h
00007FF635091074 0F B6 05 85 3F 00 00 movzx       eax,byte ptr [g_ready (07FF635095000h)]

跳板

00007FF635095450 48 83 EC 28          sub         rsp,28h
00007FF635095454 0F B6 05 85 3F 00 00 movzx       eax,byte ptr [7FF6350993E0h]
00007FF63509545B E9 1B BC FF FF       jmp         flag_print+0Bh (07FF63509107Bh)

大家發現沒有,第二條指令,位元組是一樣的,但是指令不同!

原函式

00007FF635091074 0F B6 05 85 3F 00 00 movzx       eax,byte ptr [g_ready (07FF635095000h)]
跳板
00007FF635095454 0F B6 05 85 3F 00 00 movzx       eax,byte ptr [7FF6350993E0h]

因為這條mov彙編指令使用的是相對偏移,指令所在位置不同,絕對地址就不同,大家可以回想一下前面的jmp指令。這種引數是偏移量的指令,就需要重定位。

以後我們會內嵌一個反彙編引擎,查表可以判斷是否需要重定位,不用肉眼看了。

重定位概念很有用,以後手動載入dll或記憶體完整性校驗時也會提到。

重定位過程
首先透過原函式計算絕對地址,然後根據跳板地址計算偏移。

// 偏移重定位,從src+val_offset獲取絕對addr,寫入trampoline+val_offset
// val_offset:偏移的位移
static bool xx_reloc_offset(void* src, void* trampoline, uint32_t val_offset)
{
  // 獲取絕對地址
  int32_t* src_offset = (int32_t*)((char*)src + val_offset);
  char* src_next_cmd = (char*)src + val_offset + sizeof(int32_t);
  char* real_addr = src_next_cmd + *src_offset;

  // 判斷trampoline相對於絕對地址的偏移是否超過int32
  char* tra_next_cmd = (char*)trampoline + val_offset + sizeof(int32_t);
  int64_t tra_offset_val = xx_get_offset(tra_next_cmd,real_addr);
  if (xx_int32_overflow(tra_offset_val)) {
    return false;
  }

  // trampoline重定位
  int32_t* tra_offset = (int32_t*)((char*)trampoline + val_offset);
  *tra_offset = (int32_t)tra_offset_val;
  return true;
}

至此,我們的hook庫功能基本齊全,可以用了。支援功能如下:

  • 32位跳轉
  • 64位跳轉
  • 跳板功能
  • 跳板偏移重定位

接下來會介紹遠端執行緒注入、然後配合inlinehook去觀察其它軟體如何工作。

最後,求關注、點贊、轉發,謝謝~

分類: 科技
時間: 2022-01-08

相關文章

另闢蹊徑!微軟Win11取消對安卓支援,華為自己開發鴻蒙PC系統

另闢蹊徑!微軟Win11取消對安卓支援,華為自己開發鴻蒙PC系統
微軟釋出新一代Windows 11系統最大的特點就是支援原生安卓APP應用.目前,微軟也為測試人員提供了Beta渠道.然而,根據最新訊息,微軟最近別無選擇,只能宣佈:暫時取消安卓App和亞馬遜App ...

微軟Win11相容性檢查工具正式版釋出 官網開放下載

微軟Win11相容性檢查工具正式版釋出 官網開放下載
9月21日,據媒體報道,微軟正式釋出了Windows 11相容性檢查工具正式版. 據悉,Windows 11相容性檢查工具正式版的版本號為3.0.210914001-s2,可以透過該軟體來確定裝置是否 ...

華為宣佈將釋出全新作業系統;Win11 新預覽版改善體驗修復 Bug

華為宣佈將釋出全新作業系統;Win11 新預覽版改善體驗修復 Bug
0.Win11 新預覽版 22463 推送:改善體驗.修復BUG 今晨,微軟面向Dev通道的Insider會員推送了新預覽版,作業系統版本號Build 22463. 此次更新的主要內容在於UI小幅改進 ...

iOS 15正式版釋出,6大更新

iOS 15正式版釋出,6大更新
果粉之家,專業蘋果手機技術研究十年!您身邊的蘋果專家~ 今日凌晨,蘋果推送了iOS 15正式版的更新.想要升級iOS 15正式版的果粉只要開啟設定--通用--關於本機,即可線上OTA升級至iOS 15 ...

最強 Surface 來了,微軟釋出 Surface Laptop Studio 等多款新品

最強 Surface 來了,微軟釋出 Surface Laptop Studio 等多款新品
沒有 Surface Book 4,迎來的是 Surface Laptop Studio. 在昨晚的微軟 Surface 新品釋出會上,微軟釋出了包括全新 Surface Laptop Studio ...

微信大更新,10 年最清爽版本

微信大更新,10 年最清爽版本
這兩天微信又更新了,不像之前的小修小補,這次絕對是一次大更新,直接衝上了微博熱搜第 1 ! 這次版本更新到了 8.0.14 ,特地強調可開啟關懷模式,畢竟之前被點名沒做好適老化工作,這次終於做出來了. ...

單盤打造Win11+黑蘋果+Deepin 三系統,aigo 1T NVMe 固態硬碟體驗

單盤打造Win11+黑蘋果+Deepin 三系統,aigo 1T NVMe 固態硬碟體驗
之前一直玩WIN10+黑蘋果雙系統,用512GB的固態就夠用了,最近想給電腦升級Win11,然後在再個Deepin V20,感覺512GB容量有些緊張,乾脆直接來個1T固態,看看能不能將三個系統裝到一 ...

更新 iPhone、iPad 或 iPod touch
瞭解如何將 iPhone.iPad 或 iPod touch 更新至最新版本的 iOS 或 iPadOS. 您可以透過無線方式將 iPhone.iPad 或 iPod touch 更新至最新版本的 i ...

2021年Windows UWP推薦

2021年Windows UWP推薦
買了新電腦,大家都會看見一個Microsoft Store,很多人和我吐槽,有多麼的難用,還有的朋友新電腦在這裡面找軟體,找到崩潰,載入速度慢,什麼也搜不到,好多下載了也用不了. UWP誕生的時候微軟 ...

C盤不夠用?圖片功能太簡單?Win 11這些改進可以瞭解一下

C盤不夠用?圖片功能太簡單?Win 11這些改進可以瞭解一下
C盤空間不夠用?這似乎是Windows XP時代就留下的病根,而現在,事情似乎有了轉機. 01 Win11改進了硬碟空間佔用和效能表現 微軟已宣佈將於 10 月 5 日正式推出下一代 Windows ...

微信回應頻繁讀取使用者相簿行為 | iPhone SE 3 又又又曝光

微信回應頻繁讀取使用者相簿行為 | iPhone SE 3 又又又曝光
iOS15 發現微信後臺頻繁讀取使用者相簿 昨日,有網友 @Hackl0us 發現當開啟了 iOS 15 的隱私新特性 「記錄 App 活動」時,微信會在使用者未主動啟用 App 的情況下,後臺數次讀 ...

極客簡報|iPhone SE 3 成庫克傳家寶;國民 APP 正在窺探手機相簿

極客簡報|iPhone SE 3 成庫克傳家寶;國民 APP 正在窺探手機相簿
微軟 Win11 趕鴨子上架.Apple Watch 現在怎麼選.R 星將復刻 GTA 罪惡都市. Apple Watch S7 開售,它是智慧手錶的究極形態嗎? 國慶長假後第一天,Apple Wat ...

用了之後欲罷不能!10款Windows11神級軟體清單

用了之後欲罷不能!10款Windows11神級軟體清單
哈嘍大家好,我是Fanfan. 最近Win11更新成為了大家茶餘飯後的話題,升級還是很大的,而微軟的官方應用商店(Microsoft Store)也隨著一起做了次大更新. 首先是更多的剛需軟體加入,比 ...

試駕起亞智跑Ace:換標後的“頭一炮”,它香嗎?

試駕起亞智跑Ace:換標後的“頭一炮”,它香嗎?
今年起亞在中國市場會有兩款新車,一款是倍受大家關注的國產全新嘉華,另一款就是這次我們試駕的這臺.它並未使用最新的i-GMP平臺,似乎平平無奇,但在廣袤的北疆城市內蒙古赤峰,它--智跑Ace,可謂出盡了 ...

零百3.8秒,續航712km,28.1萬起,詳解自主高階電動車極氪001

零百3.8秒,續航712km,28.1萬起,詳解自主高階電動車極氪001
說到極氪這個汽車品牌,相信許多朋友都沒有聽說過,但是它的老東家大家肯定都有所耳聞,那就是吉利汽車.極氪作為吉利旗下的一個獨立純電品牌,旗下產品也是偏向於高階的,就如今天我們要介紹的這款極氪001. 極 ...

不到19萬買第五代途勝L,標配10.4英寸大屏,空間、動力強過CR-V

不到19萬買第五代途勝L,標配10.4英寸大屏,空間、動力強過CR-V
相信大家也都發現了,近兩年國內SUV市場可謂是"芝麻開花",兼具著空間實用性與國人對於汽車審美的喜好,越來越多的SUV產品在國內市場出現.而競爭最為激烈的莫過於是20萬上下的緊湊級 ...

搭載刀片電池技術,續航401km,9.98萬起售,解析比亞迪e2

搭載刀片電池技術,續航401km,9.98萬起售,解析比亞迪e2
比亞迪e2是一臺定位緊湊級兩廂轎車,目前在售車型共有5款,補貼後售價9.98萬至14.48萬.這個價格區間在同級中也稍顯優勢,所以比亞迪e2的銷量表現也還不錯,6月份賣出3972輛,在新能源車型銷量排 ...

這些就是快樂星球 續航輕鬆500km+的新能源車怎麼選?

這些就是快樂星球 續航輕鬆500km+的新能源車怎麼選?
如今新能源成了汽車市場的大趨勢,大家都有目共睹,前不久,我們在微博上發起了Model Y和極氪001的投票,更多網友選擇了極氪001,但是據統計2021年7月,特斯拉Model Y月銷將近1.3萬輛, ...

超屏佔直屏LCD,超級快充,榮耀X20使用月餘全方面真實表現

超屏佔直屏LCD,超級快充,榮耀X20使用月餘全方面真實表現
榮耀X20系列是當下不可多得的一款直屏機,近95%的超高屏佔比,邊框超窄設計帶來不一樣的視覺感受,192g的整機質量,上手也不顯重. 6.67英寸的120Hz護眼LCD屏,P3色域,原彩顯示功能,加上 ...

AMD確認Windows 11可能導致Ryzen CPU的效能下降高達15%

AMD確認Windows 11可能導致Ryzen CPU的效能下降高達15%
媒體這幾天連續報道了使用者安裝微軟最新的Windows 11作業系統後出現的一些已知問題,無論是Oracle VirtualBox軟體還是瀏覽器相容性問題,以及英特爾通訊方案的網路聯通問題.今天,AM ...