首页
社区
课程
招聘
用angr解 Flare-on 2017第3题 - greek_to_me.exe
发表于: 2021-6-1 20:51 9377

用angr解 Flare-on 2017第3题 - greek_to_me.exe

2021-6-1 20:51
9377

笔者angr入门,一开始目的是用通过angr复现原作者用Angr解决Flareon4题目3的exp来学习angr的,但是现在angr已经更新与到python3兼容(windows下在python2下面angr我没装成功,爆了一堆错),因此笔者想根据原作者代码改出python3下angr的exp,进而学习angr的使用情景及使用方法,因此除特殊说明,本文的python代码是python3的

题目主函数如上图所示,一开始是个socket函数sub_401121(),如下图,之后是一个对loc_40107C(代码段)的混淆,再之后是被混淆的代码段

可以看出是建立本地的TCP连接并接受四个字节的数据,由主函数可以看出,如果没收到这四个字节,则直接退出程序

好在我们可以通过angr的blank_entry指定程序入口点绕过检测,前面的混淆函数容易自己模拟。那从哪里设入口呢?这需要确定混淆代码段的起始位置,根据前面反复出现的loc_40107C可以看出开始位置,又根据提示成功的字符串位置,可以找到混淆结束点为004010F9,我们还可以找到0x40105E有比较检查,必须匹配0xFB5E才能成功进入验证分支

总结一下收获:

将混淆的字节提取出来,使用IDA Python(见补充知识)

我们知道从网络接收的长度是4字节, 但是聪明的读者可能已经注意到代码中使用dl赋值给buf而非edx, 也就导致实际值的范围是从0x00xff(dl只有1字节大小). 我们脚本的开始部分类似如下:

第一阶段的混淆写一个模拟,第二阶段的混淆暴力枚举,注意把子串存起来才能跑(有点像多线程共用空间)(注意state方法在manager下面已经没有了),我们使用capstone反汇编代码解混淆结果下图:

可以看到是向数组里面传一堆字符,写一个脚本,flag就出来了

跑程序时间:秒表计时2‘11’‘(我的电脑配置不是很高)

运行结果:

WSAStartup 函数用于初始化供进程调用的Winsock相关的dll,如果调用成功,WSAStartup 函数返回0。否则,将返回五种错误代码之一 WSAVERNOTSUPPORTED、WSAVERNOTSUPPORTED、WSAEINPROGRESS、WSAEPROCLIM、WSAEFAULT

​ 这里用通俗的语言解释一下这个函数,就类似于#include一样,要添加链接库函数,要添加到附加依赖项,然后才能包含头文件进行各种函数的调用。socket编程要调用各种socket函数需要Ws2_32.lib和头文件Winsock2.h,这里的WSAStartup就是为了向操作系统说明,我们要用哪个库文件,让该库文件与当前的应用程序绑定,从而就可以调用该版本的socket的各种函数了。

The highest version of Windows Sockets specification that the caller can use. The high-order byte specifies the minor version number; the low-order byte specifies the major version number.

A pointer to the WSADATA data structure that is to receive details of the Windows Sockets implementation.

在Windows下,Socket是以DLL的形式实现的。在DLL内部维持着一个计数器,只有第一次调用WSAStartup才真正装载DLL,以后的 调用只是简单的增加计数器,而WSACleanup函数的功能则刚好相反,每调用一次使计数器减1,当计数器减到0时,DLL就从内存中被卸载!因此,你 调用了多少次WSAStartup,就应相应的调用多少次的WSACleanup.

inet_addr将一个点分十进制的IP转换成一个长整数型数(u_long)

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

| Af | Meaning |
| ------------------- | ------------------------------------------------------------ |
| AF_UNSPEC 0 | The address family is unspecified. |
| AF_INET 2 | The Internet Protocol version 4 (IPv4) address family. |
| AF_IPX 6 | The IPX/SPX address family. This address family is only supported if the NWLink IPX/SPX NetBIOS Compatible Transport protocol is installed. This address family is not supported on Windows Vista and later. |
| AF_APPLETALK 16 | The AppleTalk address family. This address family is only supported if the AppleTalk protocol is installed. This address family is not supported on Windows Vista and later. |
| AF_NETBIOS 17 | The NetBIOS address family. This address family is only supported if the Windows Sockets provider for NetBIOS is installed. The Windows Sockets provider for NetBIOS is supported on 32-bit versions of Windows. This provider is installed by default on 32-bit versions of Windows. The Windows Sockets provider for NetBIOS is not supported on 64-bit versions of windows including Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, or Windows XP. The Windows Sockets provider for NetBIOS only supports sockets where the type parameter is set to SOCK_DGRAM. The Windows Sockets provider for NetBIOS is not directly related to the NetBIOS programming interface. The NetBIOS programming interface is not supported on Windows Vista, Windows Server 2008, and later. |
| AF_INET6 23 | The Internet Protocol version 6 (IPv6) address family. |
| AF_IRDA 26 | The Infrared Data Association (IrDA) address family. This address family is only supported if the computer has an infrared port and driver installed. |
| AF_BTH 32 | The Bluetooth address family. This address family is supported on Windows XP with SP2 or later if the computer has a Bluetooth adapter and driver installed. |

type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

| Type | Meaning |
| -------------------- | ------------------------------------------------------------ |
| SOCK_STREAM 1 | A socket type that provides sequenced, reliable, two-way, connection-based byte streams with an OOB data transmission mechanism. This socket type uses the Transmission Control Protocol (TCP) for the Internet address family (AF_INET or AF_INET6). |
| SOCK_DGRAM 2 | A socket type that supports datagrams, which are connectionless, unreliable buffers of a fixed (typically small) maximum length. This socket type uses the User Datagram Protocol (UDP) for the Internet address family (AF_INET or AF_INET6). |
| SOCK_RAW 3 | A socket type that provides a raw socket that allows an application to manipulate the next upper-layer protocol header. To manipulate the IPv4 header, the IP_HDRINCL socket option must be set on the socket. To manipulate the IPv6 header, the IPV6_HDRINCL socket option must be set on the socket. |
| SOCK_RDM 4 | A socket type that provides a reliable message datagram. An example of this type is the Pragmatic General Multicast (PGM) multicast protocol implementation in Windows, often referred to as reliable multicast programming. This type value is only supported if the Reliable Multicast Protocol is installed. |
| SOCK_SEQPACKET 5 | A socket type that provides a pseudo-stream packet based on datagrams. |

protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

| protocol | Meaning |
| --------------------- | ------------------------------------------------------------ |
| IPPROTO_ICMP 1 | The Internet Control Message Protocol (ICMP). This is a possible value when the af parameter is AF_UNSPEC, AF_INET, or AF_INET6 and the type parameter is SOCK_RAW or unspecified. This protocol value is supported on Windows XP and later. |
| IPPROTO_IGMP 2 | The Internet Group Management Protocol (IGMP). This is a possible value when the af parameter is AF_UNSPEC, AF_INET, or AF_INET6 and the type parameter is SOCK_RAW or unspecified. This protocol value is supported on Windows XP and later. |
| BTHPROTO_RFCOMM 3 | The Bluetooth Radio Frequency Communications (Bluetooth RFCOMM) protocol. This is a possible value when the af parameter is AF_BTH and the type parameter is SOCK_STREAM. This protocol value is supported on Windows XP with SP2 or later. |
| IPPROTO_TCP 6 | The Transmission Control Protocol (TCP). This is a possible value when the af parameter is AF_INET or AF_INET6 and the type parameter is SOCK_STREAM. |
| IPPROTO_UDP 17 | The User Datagram Protocol (UDP). This is a possible value when the af parameter is AF_INET or AF_INET6 and the type parameter is SOCK_DGRAM. |
| IPPROTO_ICMPV6 58 | The Internet Control Message Protocol Version 6 (ICMPv6). This is a possible value when the af parameter is AF_UNSPEC, AF_INET, or AF_INET6 and the type parameter is SOCK_RAW or unspecified. This protocol value is supported on Windows XP and later. |
| IPPROTO_RM 113 | The PGM protocol for reliable multicast. This is a possible value when the af parameter is AF_INET and the type parameter is SOCK_RDM. On the Windows SDK released for Windows Vista and later, this protocol is also called IPPROTO_PGM. This protocol value is only supported if the Reliable Multicast Protocol is installed. |

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

参考Windows Socket编程示例-TCP示例程序

方法一:只有几行的二进制代码提取可以通过Options -> General --> Number of opcode bytes 置为16左右,然后在Graph View中就出现了opcode,逐行复制即可

方法二:如果是多行复制,简单

的方法是图形界面右键开始地址的opcode,然后同步到 Hex View![](https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN/img/20210601204853.png" alt="图片.png" style="zoom: 80%;" />

图片.png

然后选择自己先要复制的部分复制

方法三:IDA Python

一些常用的API和脚本请到参考链接处获取,由于题目是需要达到任意地址读取任意长字节的opcode,而且其他获取特定函数等功能可以通过这种方法结合在IDA里人工查地址方式实现,这里就只看这把大刀

为什么一定要强调这个呢?因为在我使用的是IDA7.5,其内嵌的是python3.8,而idaapi模块下面已经没有get_many_bytes方法了(实际上是改到了ida_bytes下的get_bytes方法,然后get_many_bytes变成了一个类的内部调用接口())

![](https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN/img/20210601204830.png" alt="Snipaste_2021-01-21_08-45-48.png" style="zoom:80%;" />

图片.png

最关键的问题是,这么好用的东西竟然网上讲常见IDA Python API的博客竟然一个都没有提及……()

如,将0x40107C地址处的0x79个字节的opcode写到greek_to_me_buffer.asm(好后续让程序读取利用)

直接File --> Script Command,或者直接Shift + F2 在输入框内输入脚本,Run即可

这里的作用是将局部(非整个可执行文件)的二进制opcode段反汇编为汇编指令段,就像有输入输出框一样,把一小部分二进制交给它,它也可以帮我们翻译好(作者无能,也去搜索过有没有在线的,没找到,有没有IDA插件,好用的也没找到……),而且可以泡在程序里自动执行,正符合angr的特性,就可以写出直接跑答案的程序

1)安装:用pip安装即可

2)使用:python3 使用样例

3)注意事项()

python2 中disasm函数的输入是表示十六进制的字符串经过encode('hex')生成的bytes,而python3下取而代之的encode()输出的bytes会报错

而沿着报错的点我找到了库函数,看到了下面这一段:

图片.png

他把不是python2的判断注释掉了(……),也就是python3照这个意思应该输入的是表示十六进制的字符串的str,它内部自己encode()

程序正常执行了,但是输出了一堆奇怪的指令

一开始我还怀疑是输入的二进制错了,做了各种调试,甚至自己手写了一些进制和类型转换函数,发现结果怎么都是这个奇怪的指令。最后看到原作者在用unicorn解这道题的题解中用的函数binascii.unhexlify()这种写法的时候,我重新尝试了传这个函数输出的bytes并把原来的注释加回去,结果这回正确了()

我也不知道为什么encode()和unhexlify()的结果都是bytes,两者结果有区别,我继而做了实验,发现确实是这个原因,实验代码:

![](https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN/img/20210601204812.png" alt="图片.png" style="zoom: 80%;" />

​ encode()的输出结果

​ unhexlify()的输出结果

下面就抛砖引玉了,各位大神看看是什么问题(),求大神指点

Windows Socket编程示例-TCP示例程序 https://blog.csdn.net/guo8113/article/details/26448011

Microsoft winsock2 API 官方手册 https://docs.microsoft.com/en-us/windows/win32/api/winsock2

IDApython学习笔记(一) https://mzgao.blog.csdn.net/article/details/105174680?utm_medium=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.not_use_machine_learn_pai

IDA Python 官方手册 https://www.hex-rays.com/products/ida/support/idapython_docs/ida_bytes-module.html#get_bytes

Python-Capstone Shellcode反汇编 https://gamous.cn/index.php/archives/29/

 
 
 
 
 
 
 
 
with open('greek_to_me_buffer.asm', 'wb') as f:
  f.write(ida_bytes.get_bytes(0x40107C, 0x79))
with open('greek_to_me_buffer.asm', 'wb') as f:
  f.write(ida_bytes.get_bytes(0x40107C, 0x79))
for buf in range(0x100):
print("Using {0}".format(buf))
for buf in range(0x100):
print("Using {0}".format(buf))
0x1000    mov    bl, 0x65    None
0x1002    mov    byte ptr [ebp - 0x2b], bl
0x1005    mov    byte ptr [ebp - 0x2a], 0x74
0x1009    mov    dl, 0x5f    None
0x100b    mov    byte ptr [ebp - 0x29], dl
0x100e    mov    byte ptr [ebp - 0x28], 0x74
0x1012    mov    byte ptr [ebp - 0x27], 0x75
0x1016    mov    byte ptr [ebp - 0x26], dl
0x1019    mov    byte ptr [ebp - 0x25], 0x62
0x101d    mov    byte ptr [ebp - 0x24], 0x72
0x1021    mov    byte ptr [ebp - 0x23], 0x75
0x1025    mov    byte ptr [ebp - 0x22], 0x74
0x1029    mov    byte ptr [ebp - 0x21], bl
0x102c    mov    byte ptr [ebp - 0x20], dl
0x102f    mov    byte ptr [ebp - 0x1f], 0x66
0x1033    mov    byte ptr [ebp - 0x1e], 0x6f
0x1037    mov    byte ptr [ebp - 0x1d], 0x72
0x103b    mov    byte ptr [ebp - 0x1c], 0x63
0x103f    mov    byte ptr [ebp - 0x1b], bl
0x1042    mov    byte ptr [ebp - 0x1a], 0x40
0x1046    mov    byte ptr [ebp - 0x19], 0x66
0x104a    mov    byte ptr [ebp - 0x18], 0x6c
0x104e    mov    byte ptr [ebp - 0x17], 0x61
0x1052    mov    byte ptr [ebp - 0x16], 0x72
0x1056    mov    byte ptr [ebp - 0x15], bl
0x1059    mov    byte ptr [ebp - 0x14], 0x2d
0x105d    mov    byte ptr [ebp - 0x13], 0x6f
0x1061    mov    byte ptr [ebp - 0x12], 0x6e
0x1065    mov    byte ptr [ebp - 0x11], 0x2e
0x1069    mov    byte ptr [ebp - 0x10], 0x63
0x106d    mov    byte ptr [ebp - 0xf], 0x6f
0x1071    mov    byte ptr [ebp - 0xe], 0x6d
0x1075    mov    byte ptr [ebp - 0xd], 0
0x1000    mov    bl, 0x65    None
0x1002    mov    byte ptr [ebp - 0x2b], bl
0x1005    mov    byte ptr [ebp - 0x2a], 0x74
0x1009    mov    dl, 0x5f    None
0x100b    mov    byte ptr [ebp - 0x29], dl
0x100e    mov    byte ptr [ebp - 0x28], 0x74
0x1012    mov    byte ptr [ebp - 0x27], 0x75
0x1016    mov    byte ptr [ebp - 0x26], dl
0x1019    mov    byte ptr [ebp - 0x25], 0x62
0x101d    mov    byte ptr [ebp - 0x24], 0x72
0x1021    mov    byte ptr [ebp - 0x23], 0x75
0x1025    mov    byte ptr [ebp - 0x22], 0x74
0x1029    mov    byte ptr [ebp - 0x21], bl
0x102c    mov    byte ptr [ebp - 0x20], dl
0x102f    mov    byte ptr [ebp - 0x1f], 0x66
0x1033    mov    byte ptr [ebp - 0x1e], 0x6f
0x1037    mov    byte ptr [ebp - 0x1d], 0x72
0x103b    mov    byte ptr [ebp - 0x1c], 0x63
0x103f    mov    byte ptr [ebp - 0x1b], bl
0x1042    mov    byte ptr [ebp - 0x1a], 0x40
0x1046    mov    byte ptr [ebp - 0x19], 0x66
0x104a    mov    byte ptr [ebp - 0x18], 0x6c
0x104e    mov    byte ptr [ebp - 0x17], 0x61
0x1052    mov    byte ptr [ebp - 0x16], 0x72
0x1056    mov    byte ptr [ebp - 0x15], bl
0x1059    mov    byte ptr [ebp - 0x14], 0x2d
0x105d    mov    byte ptr [ebp - 0x13], 0x6f
0x1061    mov    byte ptr [ebp - 0x12], 0x6e
0x1065    mov    byte ptr [ebp - 0x11], 0x2e
0x1069    mov    byte ptr [ebp - 0x10], 0x63
0x106d    mov    byte ptr [ebp - 0xf], 0x6f
0x1071    mov    byte ptr [ebp - 0xe], 0x6d
0x1075    mov    byte ptr [ebp - 0xd], 0
#!/usr/bin/env python3
 
import angr
import sys
import binascii
 
p = angr.Project('greek_to_me.exe', load_options={'auto_load_libs': False})
f2 = None
 
# Interate through all of the possible byte values to find the correct "user" input to de-mask the flag
for buf in range(0x100):
 
    print(("Trying buf = {0}".format(buf)))
 
    # Variable to store the bits written to disk using IDA
    asm = None
    # Store the output from the first de-obfuscation routine
    b2 = []
    # Read in bytes written to file from IDA
    with open('greek_to_me_buffer.asm', 'rb') as f:
        asm = f.read()
 
    # Re-implement loc_401039
    dl = buf
    for byte in asm:
        #bl = ord(byte)
        bl = byte
        bl = bl ^ dl
        bl = bl & 0xff
        bl = bl + 0x22
        bl = bl & 0xff
        b2.append(bl)
 
 
    # Set up angr to "run" sub_4011E6
    s = p.factory.blank_state(addr=0x4011E6)
    s.mem[s.regs.esp+4:].dword = 1    # Angr memory location to hold the xor'ed and add'ed bytes
    s.mem[s.regs.esp+8:].dword = 0x79 # Length of ASM
 
    # Copy bytes output from loc_401039 into address 0x1 so Angr can run it
 
    #asm = ''.join(map(lambda x: chr(x), b2))
 
    asm = bytes(b2)
 
    s.memory.store(1, s.se.BVV(int.from_bytes(asm,byteorder='big',signed=False), 0x79 * 8 ))
 
    # Create a simulation manager...
    #import pdb; pdb.set_trace()
    simgr = p.factory.simulation_manager(s)
 
    # Tell Angr where to go, though there is only one way through this function,
    # we just need to stop after ax is set
    simgr.explore(find=0x401268)
 
    # Once ax is set, check to see if the value in ax matches the comparison value
    for found in simgr.found:
        #import pdb; pdb.set_trace()
        print(('        ax = %s' % hex(found.solver.eval(found.regs.ax))))
        # Comparison check
        if hex(found.solver.eval(found.regs.ax)) == '0xfb5e':
            # Upon success, dump the asm
            code = ("%x" % found.solver.eval_upto(found.memory.load(1, 0x79), 1)[0])
            print(('\n        Winner is: {0}\n\n'.format(buf)))
            print(('          %s' % code))
            bl = None
            dl = None
            flag = []
            # Using capstone, interpret the ASM
            from capstone import *
            md = Cs(CS_ARCH_X86, CS_MODE_32)
            code = binascii.unhexlify(code)
            for i in md.disasm(code, 0x1000):
                flag_char = None
                #print(i.op_str)
                # The if statements do the work of interpreting the ASCII codes to their value counterpart
                try:
                    if i.op_str.split(',')[0].startswith("byte ptr"):
                        flag_char = chr(int(i.op_str.split(',')[1], 16))
                    if i.op_str.split(',')[0].startswith('bl'):
                        bl = chr(int(i.op_str.split(',')[1], 16)) 
                    if i.op_str.split(',')[0].startswith('dl'):
                        dl = chr(int(i.op_str.split(',')[1], 16)) 
                except:
                    if i.op_str.split(',')[1].strip() == 'dl':
                        flag_char = dl
                    if i.op_str.split(',')[1].strip() == 'bl':
                        flag_char = bl
 
                if (flag_char):
                    flag.append(flag_char.strip())
 
                print(("          0x%x\t%s\t%s\t%s" %(i.address, i.mnemonic, i.op_str, flag_char)))
 
            print('\n\n')
            print((''.join(flag)))
 
            sys.exit(0)
#!/usr/bin/env python3
 
import angr
import sys
import binascii
 
p = angr.Project('greek_to_me.exe', load_options={'auto_load_libs': False})
f2 = None
 
# Interate through all of the possible byte values to find the correct "user" input to de-mask the flag
for buf in range(0x100):
 
    print(("Trying buf = {0}".format(buf)))
 
    # Variable to store the bits written to disk using IDA
    asm = None
    # Store the output from the first de-obfuscation routine
    b2 = []
    # Read in bytes written to file from IDA
    with open('greek_to_me_buffer.asm', 'rb') as f:
        asm = f.read()
 
    # Re-implement loc_401039
    dl = buf
    for byte in asm:
        #bl = ord(byte)
        bl = byte
        bl = bl ^ dl
        bl = bl & 0xff
        bl = bl + 0x22
        bl = bl & 0xff
        b2.append(bl)
 
 
    # Set up angr to "run" sub_4011E6
    s = p.factory.blank_state(addr=0x4011E6)
    s.mem[s.regs.esp+4:].dword = 1    # Angr memory location to hold the xor'ed and add'ed bytes
    s.mem[s.regs.esp+8:].dword = 0x79 # Length of ASM
 
    # Copy bytes output from loc_401039 into address 0x1 so Angr can run it
 
    #asm = ''.join(map(lambda x: chr(x), b2))

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-6-1 21:08 被向往阳光的月编辑 ,原因: 调整格式和图片
收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//