详解2014hitb_bin100.elf,LD_PRELOAD注入so

本来是想了解下 LD_PRELOAD 注入 so 的。断断续续磕了几天,静下心来总算把程序给摸清了,有一种豁然释怀的感觉,满足。

1.前期侦测

hitb_bin100.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=99a4be07e428a159472b8e4b3508476feab70e8b, not stripped

1
2
3
4
5
Canary                        : Yes
NX : Yes
PIE : No
Fortify : Yes
RelRO : Partial

运行发现,程序一直在输出一些逆序的字符串,查了下发现是 YTCrack 的 LulzSec Anthem Song, 当时去云音乐查,还没有,果断 @云村曲库君啊,一个小时后就补上了,还是蛮惊喜的,生活小乐趣吧,哈哈。

2.IDA 分析及探索尝试

从 main 函数入手,汇编代码大致流程如下:

  1. store_time_ret:0x4007d8
  2. rand_call_funny:0x400719
  3. print_funny_sleep:0x400783

接下来我直接 patch 了程序,直接 call print_flag

1
2
KEY: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
OK YOU WIN. HERE'S YOUR FLAG: iYS g.'J%+i,7"rmn~uJ -

发现是需要 key 的,进一步分析在 print_funny_sleep 中 printf 和 sleep 影响了key的输出结果,patch 了这个函数,改为 nop

1
2
3
KEY: 1b 1b 24 f0 d9 d9 d9 d9 d9 25 25 25 04 b6 33 33 33 33 c5 fd 89 89 78 78 5a 88 88 ee ee eb 40 40 40 40 f6 0e
OK YOU WIN. HERE'S YOUR FLAG: r6OEB ZiѾ
4f1㌍#

显然结果是不对的。patch printf 应该是没问题的,应该 sleep 出现了问题

3.具体分析

结合国内的 writeup , patch 了 printf funny,然后 IDA F5,分析 C 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
qmemcpy(v22, &unk_400A7E, sizeof(v22));	// read uinit flag
v24 = __readfsqword(0x28u);
v3 = v21;
for ( i = 9LL; i; --i ){
*(_DWORD *)v3 = 0;
v3 += 4;
} // init v21,v21 key_buf
v19 = 201527; // 0x31337
v20 = time(0LL);
do{
v11 = 0LL;
do{
v5 = 0LL;
v6 = time(0LL);
srand(233811181 - v20 + v6); // init randmon seed
v7 = v21[v11];
v21[v11] = rand() ^ v7; // key_buf element xor
v8 = (&funny)[v11];
while ( v5 < strlen(v8) ){
v9 = v8[v5];
if ( (_BYTE)v9 == 105 ){
v23[(signed int)v5] = 105;
}else{
if ( (_DWORD)v5 && v8[v5 - 1] != 32 )
v10 = __ctype_toupper_loc();
else
v10 = __ctype_tolower_loc();
v23[(signed int)v5] = (*v10)[v9];
}
++v5;
}
v23[(signed int)v5] = 0;
++v11; //
// here is print funny,emm...but i patch it
sleep(1u);
}while ( v11 != 36 );
--v19;
}

3.1 程序流程详解

仔细分析程序流程,大致就是计算 key( 随机值与 0 xor 201527次),然后 key 与 一段固定的值( uinit_flag ) xor 得到flag,输出flag,重点在计算 key 的三个嵌套循环中

  1. 复制未初始化的 flag
  2. 栈保护 (Canary)
  3. key_buf 清0
  4. 初始化大循环条件,v19 == 201527
  5. 大循环,循环条件,v19 >= 0,也就是要循环201527次
    1. 初始化中循环条件变量,v11 = 0
    2. 中循环,循环条件,v11 !=36,36 即 flag 的长度,也是 key 的长度
      1. 通过时间差获取随机数种子
      2. 随机数 与 key_buf 数组中下标为 v11 的元素异或
      3. 小循环:格式化一行 funny
    3. 输出一行 funny
    4. sleep(1)
  6. 输出 key
  7. key xor uint_flag,输出flag

算下来,整个大循环需要 201527 次,sleep(1), 201527/60/60,也就是说至少要 55 个小时才能跑完这三个嵌套循环。

3.2 srand()/rand() 机制

这里需要理解 srand()/rand() ,其要点就是随机数种子一致,生成的随机数是一致的。可通过以下代码验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
* file: rand_srand.cpp
* author: asanzjx
* rand srand test file
* compiler: g++ rand_srand.cpp -o rand_srand
*/

#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <unistd.h>
using namespace std;

static int t = 0x31337;

int main(){
int i = 0;
for(i;i<10;i++){
srand(time(0));
sleep(1);
cout << i << " random value:" << rand() << endl;
}

// if the srand ret some value,the random value is some
for(i;i>0;i--){
srand(t);
sleep(1);
cout << i << " random value:" << rand() << endl;
}

}

$./rand_srand
0 random value:1498851891
1 random value:125926385
2 random value:1958051108
3 random value:1663308015
4 random value:1351789803
5 random value:2121833688
6 random value:753716439
7 random value:1513028956
8 random value:136593720
9 random value:907047410
10 random value:1427067673
9 random value:1427067673
8 random value:1427067673
7 random value:1427067673
6 random value:1427067673
5 random value:1427067673
4 random value:1427067673
3 random value:1427067673
2 random value:1427067673
1 random value:1427067673

所以通过连续的时间差来确保程序每次运行时种子值序列是一致的,从而使程序每次运行的随机值序列一致,这样程序每次运行的 key 值是一致的,自然程序每次输出的 flag 值一致。

4.通过 LD_PRELOAD 注入 so 来加速时间

如果按照正常执行这个程序,需要55个小时,这显然是不能忍的,所以要加快速度。方法就是改写 sleep()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* file: anti_time.c
* compiler: gcc --share anti_time.c -o anti_time.so
* LD_PRELOAD=./anti_time.so ./hitb_bin100.elf
*/

static int t = 0x31337;

void sleep(int sec) {
t += sec;
}

int time() {
return t;
}

sleep() 不执行 sleep() 操作这个好理解,但为什么要返回 t+sec 以及 time() 函数返回 t 呢?这当然是和 key 的 xor 操作有关,保证时间差值序列一致。

1
2
3
$LD_PRELOAD=./anti_time.so ./hitb_bin100.elf
KEY: 19 8f 67 74 c9 68 e6 0c 6f 54 1a 43 af 7b 5f b3 5c 01 98 58 68 56 1a 5e 31 0c 46 29 b8 a8 93 fc bf f9 70 5e
OK YOU WIN. HERE'S YOUR FLAG: p4ul_1z_d34d_1z_wh4t_th3_r3c0rd_s4ys

5.LD_PRELOAD 示例

LD_PRELOAD 是一种动态注入,在程序运行时加载。通过代码实际感受下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* file: preload.c
* author: asanzjx
* compiler: gcc preload.c -o preload
* LD_PRELOAD=./anti_time.so ./preload
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>


int main(){
printf("[+]pid:%d",getpid());
while(1){
sleep(2);
printf("[+]time:0x%x",time(0));
}
}

/*
* file: anti_time.c
* author: asanzjx
* compiler: gcc --share anti_time.c -o anti_time.so
* LD_PRELOAD=./anti_time.so ./preload
*/
int time() {
return t;
}

查看maps结果如下(部分省略)

1
2
3
4
5
6
7
8
9
10
11
$LD_PRELOAD=./anti_time.so ./preload
[+]pid:124
...

$cat /proc/124/maps
7fa715450000-7fa7155e5000 r-xp 00000000 00:00 689225 /lib/x86_64-linux-gnu/libc-2.x.so
7fa7155e5000-7fa7155ed000 ---p 00195000 00:00 689225 /lib/x86_64-linux-gnu/libc-2.x.so

7fa7157f0000-7fa7157f1000 r-xp 00000000 00:00 714489 .../anti_time.so
7fa7157f1000-7fa7157f2000 ---p 00001000 00:00 714489 .../anti_time.so
...

ldd 加载库如下

1
2
3
4
$ldd ./preload
linux-vdso.so.1 (0x00007fffdf1d3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4442830000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4442e00000)