IDA 7.0 python / idc.py

#!/usr/bin/env python
#---------------------------------------------------------------------
# IDAPython - Python plugin for Interactive Disassembler
#
# Original IDC.IDC:
# Copyright (c) 1990-2010 Ilfak Guilfanov
#
# Python conversion:
# Copyright (c) 2004-2010 Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#
# All rights reserved.
#
# For detailed copyright information see the file COPYING in
# the root of the distribution archive.
#---------------------------------------------------------------------
# idc.py - IDC compatibility module
#---------------------------------------------------------------------
"""
IDC compatibility module

This file contains IDA built-in function declarations and internal bit
definitions.  Each byte of the program has 32-bit flags (low 8 bits keep
the byte value). These 32 bits are used in get_full_flags/get_flags functions.

This file is subject to change without any notice.
Future versions of IDA may use other definitions.
"""
# FIXME: Perhaps those should be loaded on-demand
import ida_idaapi
import ida_auto
import ida_dbg
import ida_diskio
import ida_entry
import ida_enum
import ida_expr
import ida_fixup
import ida_frame
import ida_funcs
import ida_gdl
import ida_ida
import ida_idc
import ida_bytes
import ida_idd
import ida_idp
import ida_kernwin
import ida_lines
import ida_loader
import ida_moves
import ida_nalt
import ida_name
import ida_netnode
import ida_offset
import ida_pro
import ida_search
import ida_segment
import ida_segregs
import ida_struct
import ida_typeinf
import ida_ua
import ida_xref

import _ida_idaapi

import os
import re
import struct
import time
import types
import sys

__X64__  = sys.maxsize > 0xFFFFFFFF
__EA64__ = ida_idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL
WORDMASK = 0xFFFFFFFFFFFFFFFF if __EA64__ else 0xFFFFFFFF
class DeprecatedIDCError(Exception):
    """
    Exception for deprecated function calls
    """
    pass


def _IDC_GetAttr(obj, attrmap, attroffs):
    """
    Internal function to generically get object attributes
    Do not use unless you know what you are doing
    """
    if attroffs in attrmap and hasattr(obj, attrmap[attroffs][1]):
        return getattr(obj, attrmap[attroffs][1])
    else:
        errormsg = "attribute with offset %d not found, check the offset and report the problem" % attroffs
        raise KeyError, errormsg


def _IDC_SetAttr(obj, attrmap, attroffs, value):
    """
    Internal function to generically set object attributes
    Do not use unless you know what you are doing
    """
    # check for read-only atributes
    if attroffs in attrmap:
        if attrmap[attroffs][0]:
            raise KeyError, "attribute with offset %d is read-only" % attroffs
        elif hasattr(obj, attrmap[attroffs][1]):
            return setattr(obj, attrmap[attroffs][1], value)
    errormsg = "attribute with offset %d not found, check the offset and report the problem" % attroffs
    raise KeyError, errormsg


BADADDR         = ida_idaapi.BADADDR # Not allowed address value
BADSEL          = ida_idaapi.BADSEL  # Not allowed selector value/number
MAXADDR         = ida_ida.MAXADDR & WORDMASK
SIZE_MAX        = _ida_idaapi.SIZE_MAX
#
#      Flag bit definitions (for get_full_flags())
#
MS_VAL  = ida_bytes.MS_VAL             # Mask for byte value
FF_IVL  = ida_bytes.FF_IVL             # Byte has value ?

# Do flags contain byte value? (i.e. has the byte a value?)
# if not, the byte is uninitialized.

def has_value(F):     return ((F & FF_IVL) != 0)     # any defined value?

def byte_value(F):
    """
    Get byte value from flags
    Get value of byte provided that the byte is initialized.
    This macro works ok only for 8-bit byte machines.
    """
    return (F & MS_VAL)


def is_loaded(ea):
    """Is the byte initialized?"""
    return has_value(get_full_flags(ea))  # any defined value?

MS_CLS   = ida_bytes.MS_CLS   # Mask for typing
FF_CODE  = ida_bytes.FF_CODE  # Code ?
FF_DATA  = ida_bytes.FF_DATA  # Data ?
FF_TAIL  = ida_bytes.FF_TAIL  # Tail ?
FF_UNK   = ida_bytes.FF_UNK   # Unknown ?

def is_code(F):       return ((F & MS_CLS) == FF_CODE) # is code byte?
def is_data(F):       return ((F & MS_CLS) == FF_DATA) # is data byte?
def is_tail(F):       return ((F & MS_CLS) == FF_TAIL) # is tail byte?
def is_unknown(F):    return ((F & MS_CLS) == FF_UNK)  # is unexplored byte?
def is_head(F):       return ((F & FF_DATA) != 0)      # is start of code/data?

#
#      Common bits
#
MS_COMM  = ida_bytes.MS_COMM  # Mask of common bits
FF_COMM  = ida_bytes.FF_COMM  # Has comment?
FF_REF   = ida_bytes.FF_REF   # has references?
FF_LINE  = ida_bytes.FF_LINE  # Has next or prev cmt lines ?
FF_NAME  = ida_bytes.FF_NAME  # Has user-defined name ?
FF_LABL  = ida_bytes.FF_LABL  # Has dummy name?
FF_FLOW  = ida_bytes.FF_FLOW  # Exec flow from prev instruction?
FF_ANYNAME = FF_LABL | FF_NAME

def is_flow(F):       return ((F & FF_FLOW) != 0)
def isExtra(F):      return ((F & FF_LINE) != 0)
def isRef(F):        return ((F & FF_REF)  != 0)
def hasName(F):      return ((F & FF_NAME) != 0)
def hasUserName(F):  return ((F & FF_ANYNAME) == FF_NAME)

MS_0TYPE  = ida_bytes.MS_0TYPE  # Mask for 1st arg typing
FF_0VOID  = ida_bytes.FF_0VOID  # Void (unknown)?
FF_0NUMH  = ida_bytes.FF_0NUMH  # Hexadecimal number?
FF_0NUMD  = ida_bytes.FF_0NUMD  # Decimal number?
FF_0CHAR  = ida_bytes.FF_0CHAR  # Char ('x')?
FF_0SEG   = ida_bytes.FF_0SEG   # Segment?
FF_0OFF   = ida_bytes.FF_0OFF   # Offset?
FF_0NUMB  = ida_bytes.FF_0NUMB  # Binary number?
FF_0NUMO  = ida_bytes.FF_0NUMO  # Octal number?
FF_0ENUM  = ida_bytes.FF_0ENUM  # Enumeration?
FF_0FOP   = ida_bytes.FF_0FOP   # Forced operand?
FF_0STRO  = ida_bytes.FF_0STRO  # Struct offset?
FF_0STK   = ida_bytes.FF_0STK   # Stack variable?

MS_1TYPE  = ida_bytes.MS_1TYPE  # Mask for 2nd arg typing
FF_1VOID  = ida_bytes.FF_1VOID  # Void (unknown)?
FF_1NUMH  = ida_bytes.FF_1NUMH  # Hexadecimal number?
FF_1NUMD  = ida_bytes.FF_1NUMD  # Decimal number?
FF_1CHAR  = ida_bytes.FF_1CHAR  # Char ('x')?
FF_1SEG   = ida_bytes.FF_1SEG   # Segment?
FF_1OFF   = ida_bytes.FF_1OFF   # Offset?
FF_1NUMB  = ida_bytes.FF_1NUMB  # Binary number?
FF_1NUMO  = ida_bytes.FF_1NUMO  # Octal number?
FF_1ENUM  = ida_bytes.FF_1ENUM  # Enumeration?
FF_1FOP   = ida_bytes.FF_1FOP   # Forced operand?
FF_1STRO  = ida_bytes.FF_1STRO  # Struct offset?
FF_1STK   = ida_bytes.FF_1STK   # Stack variable?

# The following macros answer questions like
#   'is the 1st (or 2nd) operand of instruction or data of the given type'?
# Please note that data items use only the 1st operand type (is...0)

def is_defarg0(F):    return ((F & MS_0TYPE) != FF_0VOID)
def is_defarg1(F):    return ((F & MS_1TYPE) != FF_1VOID)
def isDec0(F):       return ((F & MS_0TYPE) == FF_0NUMD)
def isDec1(F):       return ((F & MS_1TYPE) == FF_1NUMD)
def isHex0(F):       return ((F & MS_0TYPE) == FF_0NUMH)
def isHex1(F):       return ((F & MS_1TYPE) == FF_1NUMH)
def isOct0(F):       return ((F & MS_0TYPE) == FF_0NUMO)
def isOct1(F):       return ((F & MS_1TYPE) == FF_1NUMO)
def isBin0(F):       return ((F & MS_0TYPE) == FF_0NUMB)
def isBin1(F):       return ((F & MS_1TYPE) == FF_1NUMB)
def is_off0(F):       return ((F & MS_0TYPE) == FF_0OFF)
def is_off1(F):       return ((F & MS_1TYPE) == FF_1OFF)
def is_char0(F):      return ((F & MS_0TYPE) == FF_0CHAR)
def is_char1(F):      return ((F & MS_1TYPE) == FF_1CHAR)
def is_seg0(F):       return ((F & MS_0TYPE) == FF_0SEG)
def is_seg1(F):       return ((F & MS_1TYPE) == FF_1SEG)
def is_enum0(F):      return ((F & MS_0TYPE) == FF_0ENUM)
def is_enum1(F):      return ((F & MS_1TYPE) == FF_1ENUM)
def is_manual0(F):       return ((F & MS_0TYPE) == FF_0FOP)
def is_manual1(F):       return ((F & MS_1TYPE) == FF_1FOP)
def is_stroff0(F):    return ((F & MS_0TYPE) == FF_0STRO)
def is_stroff1(F):    return ((F & MS_1TYPE) == FF_1STRO)
def is_stkvar0(F):    return ((F & MS_0TYPE) == FF_0STK)
def is_stkvar1(F):    return ((F & MS_1TYPE) == FF_1STK)

#
#      Bits for DATA bytes
#
DT_TYPE  = ida_bytes.DT_TYPE & 0xFFFFFFFF  # Mask for DATA typing

FF_BYTE      = ida_bytes.FF_BYTE & 0xFFFFFFFF      # byte
FF_WORD      = ida_bytes.FF_WORD & 0xFFFFFFFF      # word
FF_DWORD      = ida_bytes.FF_DWORD & 0xFFFFFFFF      # dword
FF_QWORD      = ida_bytes.FF_QWORD & 0xFFFFFFFF      # qword
FF_TBYTE      = ida_bytes.FF_TBYTE & 0xFFFFFFFF      # tbyte
FF_STRLIT      = ida_bytes.FF_STRLIT & 0xFFFFFFFF      # ASCII ?
FF_STRUCT      = ida_bytes.FF_STRUCT & 0xFFFFFFFF      # Struct ?
FF_OWORD      = ida_bytes.FF_OWORD & 0xFFFFFFFF      # octaword (16 bytes)
FF_FLOAT     = ida_bytes.FF_FLOAT & 0xFFFFFFFF     # float
FF_DOUBLE    = ida_bytes.FF_DOUBLE & 0xFFFFFFFF    # double
FF_PACKREAL  = ida_bytes.FF_PACKREAL & 0xFFFFFFFF  # packed decimal real
FF_ALIGN     = ida_bytes.FF_ALIGN & 0xFFFFFFFF     # alignment directive

def is_byte(F):     return (is_data(F) and (F & DT_TYPE) == FF_BYTE)
def is_word(F):     return (is_data(F) and (F & DT_TYPE) == FF_WORD)
def is_dword(F):     return (is_data(F) and (F & DT_TYPE) == FF_DWORD)
def is_qword(F):     return (is_data(F) and (F & DT_TYPE) == FF_QWORD)
def is_oword(F):     return (is_data(F) and (F & DT_TYPE) == FF_OWORD)
def is_tbyte(F):     return (is_data(F) and (F & DT_TYPE) == FF_TBYTE)
def is_float(F):    return (is_data(F) and (F & DT_TYPE) == FF_FLOAT)
def is_double(F):   return (is_data(F) and (F & DT_TYPE) == FF_DOUBLE)
def is_pack_real(F): return (is_data(F) and (F & DT_TYPE) == FF_PACKREAL)
def is_strlit(F):    return (is_data(F) and (F & DT_TYPE) == FF_STRLIT)
def is_struct(F):   return (is_data(F) and (F & DT_TYPE) == FF_STRUCT)
def is_align(F):    return (is_data(F) and (F & DT_TYPE) == FF_ALIGN)

#
#      Bits for CODE bytes
#
MS_CODE  = ida_bytes.MS_CODE & 0xFFFFFFFF
FF_FUNC  = ida_bytes.FF_FUNC & 0xFFFFFFFF  # function start?
FF_IMMD  = ida_bytes.FF_IMMD & 0xFFFFFFFF  # Has Immediate value ?
FF_JUMP  = ida_bytes.FF_JUMP & 0xFFFFFFFF  # Has jump table

#
#      Loader flags
#
NEF_SEGS   = ida_loader.NEF_SEGS   # Create segments
NEF_RSCS   = ida_loader.NEF_RSCS   # Load resources
NEF_NAME   = ida_loader.NEF_NAME   # Rename entries
NEF_MAN    = ida_loader.NEF_MAN    # Manual load
NEF_FILL   = ida_loader.NEF_FILL   # Fill segment gaps
NEF_IMPS   = ida_loader.NEF_IMPS   # Create imports section
NEF_FIRST  = ida_loader.NEF_FIRST  # This is the first file loaded
NEF_CODE   = ida_loader.NEF_CODE   # for load_binary_file:
NEF_RELOAD = ida_loader.NEF_RELOAD # reload the file at the same place:
NEF_FLAT   = ida_loader.NEF_FLAT   # Autocreated FLAT group (PE)

#         List of built-in functions
#         --------------------------
#
# The following conventions are used in this list:
#   'ea' is a linear address
#   'success' is 0 if a function failed, 1 otherwise
#   'void' means that function returns no meaningful value (always 0)
#
#  All function parameter conversions are made automatically.
#
# ----------------------------------------------------------------------------
#                       M I S C E L L A N E O U S
# ----------------------------------------------------------------------------
def value_is_string(var): raise NotImplementedError, "this function is not needed in Python"
def value_is_long(var):   raise NotImplementedError, "this function is not needed in Python"
def value_is_float(var):  raise NotImplementedError, "this function is not needed in Python"
def value_is_func(var):   raise NotImplementedError, "this function is not needed in Python"
def value_is_pvoid(var):  raise NotImplementedError, "this function is not needed in Python"
def value_is_int64(var):  raise NotImplementedError, "this function is not needed in Python"

def to_ea(seg, off):
    """
    Return value of expression: ((seg<<4) + off)
    """
    return (seg << 4) + off

def form(format, *args):
    raise DeprecatedIDCError, "form() is deprecated. Use python string operations instead."

def substr(s, x1, x2):
    raise DeprecatedIDCError, "substr() is deprecated. Use python string operations instead."

def strstr(s1, s2):
    raise DeprecatedIDCError, "strstr() is deprecated. Use python string operations instead."

def strlen(s):
    raise DeprecatedIDCError, "strlen() is deprecated. Use python string operations instead."

def xtol(s):
    raise DeprecatedIDCError, "xtol() is deprecated. Use python long() instead."

def atoa(ea):
    """
    Convert address value to a string
    Return address in the form 'seg000:1234'
    (the same as in line prefixes)

    @param ea: address to format
    """
    return ida_kernwin.ea2str(ea)

def ltoa(n, radix):
    raise DeprecatedIDCError, "ltoa() is deprecated. Use python string operations instead."

def atol(s):
    raise DeprecatedIDCError, "atol() is deprecated. Use python long() instead."


def rotate_left(value, count, nbits, offset):
    """
    Rotate a value to the left (or right)

    @param value: value to rotate
    @param count: number of times to rotate. negative counter means
                  rotate to the right
    @param nbits: number of bits to rotate
    @param offset: offset of the first bit to rotate

    @return: the value with the specified field rotated
             all other bits are not modified
    """
    assert offset >= 0, "offset must be >= 0"
    assert nbits > 0, "nbits must be > 0"

    mask = 2**(offset+nbits) - 2**offset
    tmp = value & mask

    if count > 0:
        for x in xrange(count):
            if (tmp >> (offset+nbits-1)) & 1:
                tmp = (tmp << 1) | (1 << offset)
            else:
                tmp = (tmp << 1)
    else:
        for x in xrange(-count):
            if (tmp >> offset) & 1:
                tmp = (tmp >> 1) | (1 << (offset+nbits-1))
            else:
                tmp = (tmp >> 1)

    value = (value-(value&mask)) | (tmp & mask)

    return value


def rotate_dword(x, count): return rotate_left(x, count, 32, 0)
def rotate_word(x, count):  return rotate_left(x, count, 16, 0)
def rotate_byte(x, count):  return rotate_left(x, count, 8, 0)


# add_idc_hotkey return codes
IDCHK_OK        =  0   # ok
IDCHK_ARG       = -1   # bad argument(s)
IDCHK_KEY       = -2   # bad hotkey name
IDCHK_MAX       = -3   # too many IDC hotkeys

def add_idc_hotkey(hotkey, idcfunc):
    """
    Add hotkey for IDC function

    @param hotkey: hotkey name ('a', "Alt-A", etc)
    @param idcfunc: IDC function name

    @return: None
    """
    return ida_kernwin.add_idc_hotkey(hotkey, idcfunc)


def del_idc_hotkey(hotkey):
    """
    Delete IDC function hotkey

    @param hotkey: hotkey code to delete
    """
    return ida_kernwin.del_idc_hotkey(hotkey)


def jumpto(ea):
    """
    Move cursor to the specifed linear address

    @param ea: linear address
    """
    return ida_kernwin.jumpto(ea)


def auto_wait():
    """
    Process all entries in the autoanalysis queue
    Wait for the end of autoanalysis

    @note:    This function will suspend execution of the calling script
            till the autoanalysis queue is empty.
    """
    return ida_auto.auto_wait()


def eval_idc(expr):
    """
    Evaluate an IDC expression

    @param expr: an expression

    @return: the expression value. If there are problems, the returned value will be "IDC_FAILURE: xxx"
             where xxx is the error description

    @note: Python implementation evaluates IDC only, while IDC can call other registered languages
    """
    rv = ida_expr.idc_value_t()

    err = ida_expr.eval_idc_expr(rv, BADADDR, expr)
    if err:
        return "IDC_FAILURE: "+err
    else:
        if rv.vtype == '\x02': # long
            return rv.num
        elif rv.vtype == '\x07': # VT_STR
            return rv.c_str()
        else:
            raise NotImplementedError, "eval_idc() supports only expressions returning strings or longs"


def EVAL_FAILURE(code):
    """
    Check the result of eval_idc() for evaluation failures

    @param code: result of eval_idc()

    @return: True if there was an evaluation error
    """
    return type(code) == types.StringType and code.startswith("IDC_FAILURE: ")


def save_database(idbname, flags=0):
    """
    Save current database to the specified idb file

    @param idbname: name of the idb file. if empty, the current idb
                    file will be used.
    @param flags: combination of ida_loader.DBFL_... bits or 0
    """
    if len(idbname) == 0:
        idbname = get_idb_path()
    mask = ida_loader.DBFL_KILL | ida_loader.DBFL_COMP | ida_loader.DBFL_BAK
    res = ida_loader.save_database_ex(idbname, flags & mask)
    return res

DBFL_BAK = ida_loader.DBFL_BAK # for compatiblity with older versions, eventually delete this

def validate_idb_names():
    """
    check consistency of IDB name records
    @return: number of inconsistent name records
    """
    return ida_nalt.validate_idb_names()

def qexit(code):
    """
    Stop execution of IDC program, close the database and exit to OS

    @param code: code to exit with.

    @return: -
    """
    ida_pro.qexit(code)


def call_system(command):
    """
    Execute an OS command.

    @param command: command line to execute

    @return: error code from OS

    @note:
    IDA will wait for the started program to finish.
    In order to start the command in parallel, use OS methods.
    For example, you may start another program in parallel using
    "start" command.
    """
    return os.system(command)


def qsleep(milliseconds):
    """
    qsleep the specified number of milliseconds
    This function suspends IDA for the specified amount of time

    @param milliseconds: time to sleep
    """
    time.sleep(float(milliseconds)/1000)


def load_and_run_plugin(name, arg):
    """
    Load and run a plugin

    @param name: The plugin name is a short plugin name without an extension
    @param arg: integer argument

    @return: 0 if could not load the plugin, 1 if ok
    """
    return ida_loader.load_and_run_plugin(name, arg)


def plan_to_apply_idasgn(name):
    """
    Load (plan to apply) a FLIRT signature file

    @param name:  signature name without path and extension

    @return: 0 if could not load the signature file, !=0 otherwise
    """
    return ida_funcs.plan_to_apply_idasgn(name)


#----------------------------------------------------------------------------
#      C H A N G E   P R O G R A M   R E P R E S E N T A T I O N
#----------------------------------------------------------------------------


def delete_all_segments():
    """
    Delete all segments, instructions, comments, i.e. everything
    except values of bytes.
    """
    ea = ida_ida.cvar.inf.min_ea

    # Brute-force nuke all info from all the heads
    while ea != BADADDR and ea <= ida_ida.cvar.inf.max_ea:
        ida_name.del_local_name(ea)
        ida_name.del_global_name(ea)
        func = ida_funcs.get_func(ea)
        if func:
            ida_funcs.del_func_cmt(func, False)
            ida_funcs.del_func_cmt(func, True)
            ida_funcs.del_func(ea)
        ida_bytes.del_hidden_range(ea)
        seg = ida_segment.getseg(ea)
        if seg:
            ida_segment.del_segment_cmt(seg, False)
            ida_segment.del_segment_cmt(seg, True)
            ida_segment.del_segm(ea, ida_segment.SEGMOD_KEEP | ida_segment.SEGMOD_SILENT)

        ea = ida_bytes.next_head(ea, ida_ida.cvar.inf.max_ea)


def create_insn(ea):
    """
    Create an instruction at the specified address

    @param ea: linear address

    @return: 0 - can not create an instruction (no such opcode, the instruction
    would overlap with existing items, etc) otherwise returns length of the
    instruction in bytes
    """
    return ida_ua.create_insn(ea)


def plan_and_wait(sEA, eEA, final_pass=True):
    """
    Perform full analysis of the range

    @param sEA: starting linear address
    @param eEA: ending linear address (excluded)
    @param final_pass: make the final pass over the specified range

    @return: 1-ok, 0-Ctrl-Break was pressed.
    """
    return ida_auto.plan_and_wait(sEA, eEA, final_pass)


def set_name(ea, name, flags=ida_name.SN_CHECK):
    """
    Rename an address

    @param ea: linear address
    @param name: new name of address. If name == "", then delete old name
    @param flags: combination of SN_... constants

    @return: 1-ok, 0-failure
    """
    return ida_name.set_name(ea, name, flags)

SN_CHECK      = ida_name.SN_CHECK
SN_NOCHECK    = ida_name.SN_NOCHECK  # Don't fail if the name contains invalid characters.
                                     # If this bit is clear, all invalid chars
                                     # (those !is_ident_cp()) will be replaced
                                     # by SUBSTCHAR (usually '_').
                                     # List of valid characters is defined in ida.cfg
SN_PUBLIC     = ida_name.SN_PUBLIC   # if set, make name public
SN_NON_PUBLIC = ida_name.SN_NON_PUBLIC # if set, make name non-public
SN_WEAK       = ida_name.SN_WEAK     # if set, make name weak
SN_NON_WEAK   = ida_name.SN_NON_WEAK # if set, make name non-weak
SN_AUTO       = ida_name.SN_AUTO     # if set, make name autogenerated
SN_NON_AUTO   = ida_name.SN_NON_AUTO # if set, make name non-autogenerated
SN_NOLIST     = ida_name.SN_NOLIST   # if set, exclude name from the list
                                     # if not set, then include the name into
                                     # the list (however, if other bits are set,
                                     # the name might be immediately excluded
                                     # from the list)
SN_NOWARN     = ida_name.SN_NOWARN   # don't display a warning if failed
SN_LOCAL      = ida_name.SN_LOCAL    # create local name. a function should exist.
                                     # local names can't be public or weak.
                                     # also they are not included into the list
                                     # of names they can't have dummy prefixes

def set_cmt(ea, comment, rptble):
    """
    Set an indented regular comment of an item

    @param ea: linear address
    @param comment: comment string
    @param rptble: is repeatable?

    @return: None
    """
    return ida_bytes.set_cmt(ea, comment, rptble)


def make_array(ea, nitems):
    """
    Create an array.

    @param ea: linear address
    @param nitems: size of array in items

    @note: This function will create an array of the items with the same type as
    the type of the item at 'ea'. If the byte at 'ea' is undefined, then
    this function will create an array of bytes.
    """
    flags = ida_bytes.get_flags(ea)

    if ida_bytes.is_code(flags) or ida_bytes.is_tail(flags) or ida_bytes.is_align(flags):
        return False

    if ida_bytes.is_unknown(flags):
        flags = ida_bytes.FF_BYTE

    if ida_bytes.is_struct(flags):
        ti = ida_nalt.opinfo_t()
        assert ida_bytes.get_opinfo(ti, ea, 0, flags), "get_opinfo() failed"
        itemsize = ida_bytes.get_data_elsize(ea, flags, ti)
        tid = ti.tid
    else:
        itemsize = ida_bytes.get_item_size(ea)
        tid = BADADDR

    return ida_bytes.create_data(ea, flags, itemsize*nitems, tid)


def create_strlit(ea, endea):
    """
    Create a string.

    This function creates a string (the string type is determined by the
    value of get_inf_attr(INF_STRTYPE))

    @param ea: linear address
    @param endea: ending address of the string (excluded)
        if endea == BADADDR, then length of string will be calculated
        by the kernel

    @return: 1-ok, 0-failure

    @note: The type of an existing string is returned by get_str_type()
    """
    return ida_bytes.create_strlit(ea, 0 if endea == BADADDR else endea - ea, get_inf_attr(INF_STRTYPE))


def create_data(ea, flags, size, tid):
    """
    Create a data item at the specified address

    @param ea: linear address
    @param flags: FF_BYTE..FF_PACKREAL
    @param size: size of item in bytes
    @param tid: for FF_STRUCT the structure id

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_data(ea, flags, size, tid)


def create_byte(ea):
    """
    Convert the current item to a byte

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_byte(ea, 1)


def create_word(ea):
    """
    Convert the current item to a word (2 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_word(ea, 2)


def create_dword(ea):
    """
    Convert the current item to a double word (4 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_dword(ea, 4)


def create_qword(ea):
    """
    Convert the current item to a quadro word (8 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_qword(ea, 8)


def create_oword(ea):
    """
    Convert the current item to an octa word (16 bytes/128 bits)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_oword(ea, 16)


def create_yword(ea):
    """
    Convert the current item to a ymm word (32 bytes/256 bits)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_yword(ea, 32)


def create_float(ea):
    """
    Convert the current item to a floating point (4 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_float(ea, 4)


def create_double(ea):
    """
    Convert the current item to a double floating point (8 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_double(ea, 8)


def create_pack_real(ea):
    """
    Convert the current item to a packed real (10 or 12 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_packed_real(ea, ida_idp.ph_get_tbyte_size())


def create_tbyte(ea):
    """
    Convert the current item to a tbyte (10 or 12 bytes)

    @param ea: linear address

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_tbyte(ea, ida_idp.ph_get_tbyte_size())


def create_struct(ea, size, strname):
    """
    Convert the current item to a structure instance

    @param ea: linear address
    @param size: structure size in bytes. -1 means that the size
        will be calculated automatically
    @param strname: name of a structure type

    @return: 1-ok, 0-failure
    """
    strid = ida_struct.get_struc_id(strname)

    if size == -1:
        size = ida_struct.get_struc_size(strid)

    return ida_bytes.create_struct(ea, size, strid)


def create_custom_data(ea, size, dtid, fid):
    """
    Convert the item at address to custom data.

    @param ea: linear address.
    @param size: custom data size in bytes.
    @param dtid: data type ID.
    @param fid: data format ID.

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_custdata(ea, size, dtid, fid)



def create_align(ea, count, align):
    """
    Convert the current item to an alignment directive

    @param ea: linear address
    @param count: number of bytes to convert
    @param align: 0 or 1..32
              if it is 0, the correct alignment will be calculated
              by the kernel

    @return: 1-ok, 0-failure
    """
    return ida_bytes.create_align(ea, count, align)


def define_local_var(start, end, location, name):
    """
    Create a local variable

    @param start: start of address range for the local variable
    @param end: end of address range for the local variable
    @param location: the variable location in the "[bp+xx]" form where xx is
                     a number. The location can also be specified as a
                     register name.
    @param name: name of the local variable

    @return: 1-ok, 0-failure

    @note: For the stack variables the end address is ignored.
           If there is no function at 'start' then this function.
           will fail.
    """
    func = ida_funcs.get_func(start)

    if not func:
        return 0

    # Find out if location is in the [bp+xx] form
    r = re.compile("\[([a-z]+)([-+][0-9a-fx]+)", re.IGNORECASE)
    m = r.match(location)

    if m:
        # Location in the form of [bp+xx]
        register = ida_idp.str2reg(m.group(1))
        offset = int(m.group(2), 0)
        frame = ida_frame.get_frame(func)

        if register == -1 or not frame:
            return 0

        offset += func.frsize
        member = ida_struct.get_member(frame, offset)

        if member:
            # Member already exists, rename it
            if ida_struct.set_member_name(frame, offset, name):
                return 1
            else:
                return 0
        else:
            # No member at the offset, create a new one
            if ida_struct.add_struc_member(frame,
                                           name,
                                           offset,
                                           ida_bytes.byteflag(),
                                           None, 1) == 0:
                return 1
            else:
                return 0
    else:
        # Location as simple register name
        return ida_frame.add_regvar(func, start, end, location, name, None)


def del_items(ea, flags=0, size=1):
    """
    Convert the current item to an explored item

    @param ea: linear address
    @param flags: combination of DELIT_* constants
    @param size: size of the range to undefine

    @return: None
    """
    return ida_bytes.del_items(ea, flags, size)


DELIT_SIMPLE   = ida_bytes.DELIT_SIMPLE   # simply undefine the specified item
DELIT_EXPAND   = ida_bytes.DELIT_EXPAND   # propogate undefined items, for example
                                          # if removing an instruction removes all
                                          # references to the next instruction, then
                                          # plan to convert to unexplored the next
                                          # instruction too.
DELIT_DELNAMES = ida_bytes.DELIT_DELNAMES # delete any names at the specified address(es)


def set_array_params(ea, flags, litems, align):
    """
    Set array representation format

    @param ea: linear address
    @param flags: combination of AP_... constants or 0
    @param litems: number of items per line. 0 means auto
    @param align: element alignment
                  - -1: do not align
                  - 0:  automatic alignment
                  - other values: element width

    @return: 1-ok, 0-failure
    """
    return eval_idc("set_array_params(0x%X, 0x%X, %d, %d)"%(ea, flags, litems, align))

AP_ALLOWDUPS    = 0x00000001L     # use 'dup' construct
AP_SIGNED       = 0x00000002L     # treats numbers as signed
AP_INDEX        = 0x00000004L     # display array element indexes as comments
AP_ARRAY        = 0x00000008L     # reserved (this flag is not stored in database)
AP_IDXBASEMASK  = 0x000000F0L     # mask for number base of the indexes
AP_IDXDEC       = 0x00000000L     # display indexes in decimal
AP_IDXHEX       = 0x00000010L     # display indexes in hex
AP_IDXOCT       = 0x00000020L     # display indexes in octal
AP_IDXBIN       = 0x00000030L     # display indexes in binary

def op_bin(ea, n):
    """
    Convert an operand of the item (instruction or data) to a binary number

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands

    @return: 1-ok, 0-failure

    @note: the data items use only the type of the first operand
    """
    return ida_bytes.op_bin(ea, n)


def op_oct(ea, n):
    """
    Convert an operand of the item (instruction or data) to an octal number

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_oct(ea, n)


def op_dec(ea, n):
    """
    Convert an operand of the item (instruction or data) to a decimal number

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_dec(ea, n)


def op_hex(ea, n):
    """
    Convert an operand of the item (instruction or data) to a hexadecimal number

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_hex(ea, n)


def op_chr(ea, n):
    """
    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_chr(ea, n)


def op_plain_offset(ea, n, base):
    """
    Convert operand to an offset
    (for the explanations of 'ea' and 'n' please see op_bin())

    Example:
    ========

        seg000:2000 dw      1234h

        and there is a segment at paragraph 0x1000 and there is a data item
        within the segment at 0x1234:

        seg000:1234 MyString        db 'Hello, world!',0

        Then you need to specify a linear address of the segment base to
        create a proper offset:

        op_plain_offset(["seg000",0x2000],0,0x10000);

        and you will have:

        seg000:2000 dw      offset MyString

    Motorola 680x0 processor have a concept of "outer offsets".
    If you want to create an outer offset, you need to combine number
    of the operand with the following bit:

    Please note that the outer offsets are meaningful only for
    Motorola 680x0.

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    @param base: base of the offset as a linear address
        If base == BADADDR then the current operand becomes non-offset
    """
    if base == BADADDR:
        return ida_bytes.clr_op_type(ea, n)
    else:
        return ida_offset.op_plain_offset(ea, n, base)


OPND_OUTER = ida_bytes.OPND_OUTER # outer offset base


def op_offset(ea, n, reftype, target, base, tdelta):
    """
    Convert operand to a complex offset expression
    This is a more powerful version of op_plain_offset() function.
    It allows to explicitly specify the reference type (off8,off16, etc)
    and the expression target with a possible target delta.
    The complex expressions are represented by IDA in the following form:

    target + tdelta - base

    If the target is not present, then it will be calculated using

    target = operand_value - tdelta + base

    The target must be present for LOW.. and HIGH.. reference types

    @param ea: linear address of the instruction/data
    @param n: number of operand to convert (the same as in op_plain_offset)
    @param reftype: one of REF_... constants
    @param target: an explicitly specified expression target. if you don't
              want to specify it, use -1. Please note that LOW... and
              HIGH... reference type requre the target.
    @param base: the offset base (a linear address)
    @param tdelta: a displacement from the target which will be displayed
              in the expression.

    @return: success (boolean)
    """
    return ida_offset.op_offset(ea, n, reftype, target, base, tdelta)


REF_OFF8    = ida_nalt.REF_OFF8    # 8bit full offset
REF_OFF16   = ida_nalt.REF_OFF16   # 16bit full offset
REF_OFF32   = ida_nalt.REF_OFF32   # 32bit full offset
REF_LOW8    = ida_nalt.REF_LOW8    # low 8bits of 16bit offset
REF_LOW16   = ida_nalt.REF_LOW16   # low 16bits of 32bit offset
REF_HIGH8   = ida_nalt.REF_HIGH8   # high 8bits of 16bit offset
REF_HIGH16  = ida_nalt.REF_HIGH16  # high 16bits of 32bit offset
REF_OFF64   = ida_nalt.REF_OFF64   # 64bit full offset
REFINFO_RVA     = 0x10 # based reference (rva)
REFINFO_PASTEND = 0x20 # reference past an item it may point to an nonexistitng
                       # do not destroy alignment dirs
REFINFO_NOBASE  = 0x80 # offset base is a number
                       # that base have be any value
                       # nb: base xrefs are created only if base
                       # points to the middle of a segment
REFINFO_SUBTRACT = 0x0100 # the reference value is subtracted from
                          # the base value instead of (as usual)
                          # being added to it
REFINFO_SIGNEDOP = 0x0200 # the operand value is sign-extended (only
                          # supported for REF_OFF8/16/32/64)

def op_seg(ea, n):
    """
    Convert operand to a segment expression

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_seg(ea, n)


def op_num(ea, n):
    """
    Convert operand to a number (with default number base, radix)

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_num(ea, n)


def op_flt(ea, n):
    """
    Convert operand to a floating-point number

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands

    @return: 1-ok, 0-failure
    """
    return ida_bytes.op_flt(ea, n)


def op_man(ea, n, opstr):
    """
    Specify operand represenation manually.

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    @param opstr: a string represenation of the operand

    @note: IDA will not check the specified operand, it will simply display
    it instead of the orginal representation of the operand.
    """
    return ida_bytes.set_forced_operand(ea, n, opstr)


def toggle_sign(ea, n):
    """
    Change sign of the operand

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.toggle_sign(ea, n)


def toggle_bnot(ea, n):
    """
    Toggle the bitwise not operator for the operand

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    ida_bytes.toggle_bnot(ea, n)
    return True


def op_enum(ea, n, enumid, serial):
    """
    Convert operand to a symbolic constant

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    @param enumid: id of enumeration type
    @param serial: serial number of the constant in the enumeration
             The serial numbers are used if there are more than
             one symbolic constant with the same value in the
             enumeration. In this case the first defined constant
             get the serial number 0, then second 1, etc.
             There could be 256 symbolic constants with the same
             value in the enumeration.
    """
    return ida_bytes.op_enum(ea, n, enumid, serial)


def op_stroff(ea, n, strid, delta):
    """
    Convert operand to an offset in a structure

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    @param strid: id of a structure type
    @param delta: struct offset delta. usually 0. denotes the difference
                    between the structure base and the pointer into the structure.

    """
    path = ida_pro.tid_array(1)
    path[0] = strid
    return ida_bytes.op_stroff(ea, n, path.cast(), 1, delta)


def op_stkvar(ea, n):
    """
    Convert operand to a stack variable

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    """
    return ida_bytes.op_stkvar(ea, n)


def op_offset_high16(ea, n, target):
    """
    Convert operand to a high offset
    High offset is the upper 16bits of an offset.
    This type is used by TMS320C6 processors (and probably by other
    RISC processors too)

    @param ea: linear address
    @param n: number of operand
        - 0 - the first operand
        - 1 - the second, third and all other operands
        - -1 - all operands
    @param target: the full value (all 32bits) of the offset
    """
    return ida_offset.op_offset(ea, n, ida_nalt.REF_HIGH16, target)


def MakeVar(ea):
    """
    Mark the location as "variable"

    @param ea: address to mark

    @return: None

    @note: All that IDA does is to mark the location as "variable".
    Nothing else, no additional analysis is performed.
    This function may disappear in the future.
    """
    ida_bytes.doVar(ea, 1)

# Every anterior/posterior line has its number.
# Anterior  lines have numbers from E_PREV
# Posterior lines have numbers from E_NEXT
E_PREV = ida_lines.E_PREV
E_NEXT = ida_lines.E_NEXT

def get_extra_cmt(ea, n):
    """
    Get extra comment line

    @param ea: linear address
    @param n: number of line (0..MAX_ITEM_LINES)
          MAX_ITEM_LINES is defined in IDA.CFG

    To get anterior  line #n use (E_PREV + n)
    To get posterior line #n use (E_NEXT + n)

    @return: extra comment line string
    """
    return ida_lines.get_extra_cmt(ea, n)


def update_extra_cmt(ea, n, line):
    """
    Set or update extra comment line

    @param ea: linear address
    @param n: number of additional line (0..MAX_ITEM_LINES)
    @param line: the line to display

    @return: None

    @note: IDA displays additional lines from number 0 up to the first unexisting
    additional line. So, if you specify additional line #150 and there is no
    additional line #149, your line will not be displayed.  MAX_ITEM_LINES is
    defined in IDA.CFG

    To set anterior  line #n use (E_PREV + n)
    To set posterior line #n use (E_NEXT + n)
    """
    ida_lines.update_extra_cmt(ea, n, line)


def del_extra_cmt(ea, n):
    """
    Delete an extra comment line

    @param ea: linear address
    @param n: number of anterior additional line (0..MAX_ITEM_LINES)

    @return: None

    To delete anterior  line #n use (E_PREV + n)
    To delete posterior line #n use (E_NEXT + n)
    """
    ida_lines.del_extra_cmt(ea, n)


def set_manual_insn(ea, insn):
    """
    Specify instruction represenation manually.

    @param ea: linear address
    @param insn: a string represenation of the operand

    @note: IDA will not check the specified instruction, it will simply
    display it instead of the orginal representation.
    """
    return ida_bytes.set_manual_insn(ea, insn)


def get_manual_insn(ea):
    """
    Get manual representation of instruction

    @param ea: linear address

    @note: This function returns value set by set_manual_insn earlier.
    """
    return ida_bytes.get_manual_insn(ea)


def patch_dbg_byte(ea,value):
    """
    Change a byte in the debugged process memory only

    @param ea: address
    @param value: new value of the byte

    @return: 1 if successful, 0 if not
    """
    return ida_dbg.put_dbg_byte(ea, value)


def patch_byte(ea, value):
    """
    Change value of a program byte
    If debugger was active then the debugged process memory will be patched too

    @param ea: linear address
    @param value: new value of the byte

    @return: 1 if the database has been modified,
             0 if either the debugger is running and the process' memory
               has value 'value' at address 'ea',
               or the debugger is not running, and the IDB
               has value 'value' at address 'ea already.
    """
    return ida_bytes.patch_byte(ea, value)


def patch_word(ea, value):
    """
    Change value of a program word (2 bytes)

    @param ea: linear address
    @param value: new value of the word

    @return: 1 if the database has been modified,
             0 if either the debugger is running and the process' memory
               has value 'value' at address 'ea',
               or the debugger is not running, and the IDB
               has value 'value' at address 'ea already.
    """
    return ida_bytes.patch_word(ea, value)


def patch_dword(ea, value):
    """
    Change value of a double word

    @param ea: linear address
    @param value: new value of the double word

    @return: 1 if the database has been modified,
             0 if either the debugger is running and the process' memory
               has value 'value' at address 'ea',
               or the debugger is not running, and the IDB
               has value 'value' at address 'ea already.
    """
    return ida_bytes.patch_dword(ea, value)


def patch_qword(ea, value):
    """
    Change value of a quad word

    @param ea: linear address
    @param value: new value of the quad word

    @return: 1 if the database has been modified,
             0 if either the debugger is running and the process' memory
               has value 'value' at address 'ea',
               or the debugger is not running, and the IDB
               has value 'value' at address 'ea already.
    """
    return ida_bytes.patch_qword(ea, value)


SR_inherit      = 1 # value is inherited from the previous range
SR_user         = 2 # value is specified by the user
SR_auto         = 3 # value is determined by IDA
SR_autostart    = 4 # as SR_auto for segment starting address

def split_sreg_range(ea, reg, value, tag=SR_user):
    """
    Set value of a segment register.

    @param ea: linear address
    @param reg: name of a register, like "cs", "ds", "es", etc.
    @param value: new value of the segment register.
    @param tag: of SR_... constants

    @note: IDA keeps tracks of all the points where segment register change their
           values. This function allows you to specify the correct value of a segment
           register if IDA is not able to find the corrent value.
    """
    reg = ida_idp.str2reg(reg);
    if reg >= 0:
        return ida_segregs.split_sreg_range(ea, reg, value, tag)
    else:
        return False


def auto_mark_range(start, end, queuetype):
    """
    Plan to perform an action in the future.
    This function will put your request to a special autoanalysis queue.
    Later IDA will retrieve the request from the queue and process
    it. There are several autoanalysis queue types. IDA will process all
    queries from the first queue and then switch to the second queue, etc.
    """
    return ida_auto.auto_mark_range(start, end, queuetype)


def auto_unmark(start, end, queuetype):
    """
    Remove range of addresses from a queue.
    """
    return ida_auto.auto_unmark(start, end, queuetype)


def AutoMark(ea,qtype):
    """
    Plan to analyze an address
    """
    return auto_mark_range(ea,ea+1,qtype)

AU_UNK   = ida_auto.AU_UNK   # make unknown
AU_CODE  = ida_auto.AU_CODE  # convert to instruction
AU_PROC  = ida_auto.AU_PROC  # make function
AU_USED  = ida_auto.AU_USED  # reanalyze
AU_LIBF  = ida_auto.AU_LIBF  # apply a flirt signature (the current signature!)
AU_FINAL = ida_auto.AU_FINAL # coagulate unexplored items


#----------------------------------------------------------------------------
#               P R O D U C E   O U T P U T   F I L E S
#----------------------------------------------------------------------------

def gen_file(filetype, path, ea1, ea2, flags):
    """
    Generate an output file

    @param filetype:  type of output file. One of OFILE_... symbols. See below.
    @param path:  the output file path (will be overwritten!)
    @param ea1:   start address. For some file types this argument is ignored
    @param ea2:   end address. For some file types this argument is ignored
    @param flags: bit combination of GENFLG_...

    @returns: number of the generated lines.
                -1 if an error occured
                OFILE_EXE: 0-can't generate exe file, 1-ok
    """
    f = ida_diskio.fopenWT(path)

    if f:
        retval = ida_loader.gen_file(filetype, f, ea1, ea2, flags)
        ida_diskio.eclose(f)
        return retval
    else:
        return -1


# output file types:
OFILE_MAP  = ida_loader.OFILE_MAP
OFILE_EXE  = ida_loader.OFILE_EXE
OFILE_IDC  = ida_loader.OFILE_IDC
OFILE_LST  = ida_loader.OFILE_LST
OFILE_ASM  = ida_loader.OFILE_ASM
OFILE_DIF  = ida_loader.OFILE_DIF

# output control flags:
GENFLG_MAPSEG  = ida_loader.GENFLG_MAPSEG  # map: generate map of segments
GENFLG_MAPNAME = ida_loader.GENFLG_MAPNAME # map: include dummy names
GENFLG_MAPDMNG = ida_loader.GENFLG_MAPDMNG # map: demangle names
GENFLG_MAPLOC  = ida_loader.GENFLG_MAPLOC  # map: include local names
GENFLG_IDCTYPE = ida_loader.GENFLG_IDCTYPE # idc: gen only information about types
GENFLG_ASMTYPE = ida_loader.GENFLG_ASMTYPE # asm&lst: gen information about types too
GENFLG_GENHTML = ida_loader.GENFLG_GENHTML # asm&lst: generate html (gui version only)
GEN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值