myara CG blog

CG Design Blog. Thoughts, experiments and experiences.

Maya mel Malware

1,2週間前から取引先から最近流行っているMayaのマルウェアの報告がありました。

感染すると:

・userSetup と animImportExport.pres.mel を書き換えて、マルウェアコードを追加します。
・シーンを保存する時に、マルウェアコードを追加して保存します。
2020年6月27日以降Mayaを起動すると無限ループでMayaをクラッシュさせます。

まーまーやばいやつですね。
何も知らないでこのままいったら、おそらくプリファレンスフォルダ削除で直していたんでしょうね。
喜んだ後に普通に仕事して、感染したファイルを開いて、また無限ループ。
の無限ループ。

melなので、コードの中身は簡単に確認が出来るけど、結構読み辛いです。
で、少し調べてみたら、githubにマルウェアのdeobfuscated コードがあがっていました。
興味があったら:

マルウェアコード
https://gist.github.com/OriginalAdric/acbe902c89064e8300f2bc475a953e05

・・・ deobfuscate って日本語でなんていうのかな。



--- 解決方法 ---


Autodesk Maya Security Tool
https://apps.autodesk.com/MAYA/en/Detail/Index?id=8637238041954239715&appLang=en&os=Win64
※ Autodeskアカウントが必須

インストール方法など(ボンデジ)
https://support.borndigital.co.jp/hc/ja/articles/900001472643

以上のツールを使えばマルウェア駆除が出来ますが、シーン1つ修正するとMayaが強制終了してしまうので、
複数ファイルをやるときにかなり時間が掛かってしまいます。

もっと早く出来ないかと色々検証してみて、出来たのはこれです:


checkMalware
download

仕組みはシンプルです。
・入力したフォルダの中をmaとmbファイルを探して、そのファイルの中に怪しい変数名が入っているかどうかを検索して、感染したファイルをリスト化します。
・Clean up にクリックすると Maya batch モードで MayaScanner を使ってマルウェア駆除します。
ファイルの大きさやプラグインを使っている数によって処理時間が異なりますが、1ファイル30秒以上が掛かります。
・ログも残しているので、なにかしらの原因で駆除が出来なかった場合はスクリプトエディターで報告が出てくるはずです。その場合はMayaScannerを手動でやるしかありません。

checkMalware.png

■ インストール
Mayaの scripts フォルダにコピー

■ 実行方法
import checkMalware; checkMalware.main()

入力ウィンドウが出てきて、ここに確認したいフォルダを入力して、OKにクリック。
感染したファイルのリストが出てきて、シーン名にダブルクリックで開けます。

※ incrementalsaveの中はチェックしない


注意:
・ignore userSetup,py というオプションは一時的に userSetup.py を userSetup.py.skbk にリネームして読み込まれないようにしているだけで、スクリプトが中断されたらリネームしたまま残ってしまう可能性があります。
・ツールはバージョンの確認はしないので、修正されたファイルは実行されたMayaのバージョンになります。
・ファイルアトリビュートを変更しないので、ファイルは読み取り専用になっていれば、上書きが出来なくてエラーになってしまいます。あらかじめに読み取り専用のチェックを外してください


readonly.png

Maya Batch モードだと userSetup.py で読み込んでいる弊社のパイプラインツールはうまく動かなかったから、エラーで中断していた。userSetupを無視するオプションはMayaにはないので、一時的に userSetup をリネームすることにしました。

時々エラーが出ていて、マルウェア駆除が出来ていませんでした。
その原因は、たまに起こる原因不明の現象。cmds.file(q=True, sn=True) でシーン名を取得が出来ない。
そのワークアラウンドは cmds.file(query=True, l=True)[0] でシーン名を取得すること。

なので、MayaScannerCB.py を編集して、以下のコードを追加しました

# myara -------------------------------------------------------------------------------
if cmds.file(q=True, sn=True) == "":
cmds.warning("No Name Found. Renaming")
realName = cmds.file(query=True, l=True)[0]
cmds.file(rename=realName)
# ------------------------------------------------------------------------------- myara




MayaScannerFIX
download

MayaScanner をインストールした後にこのファイルを上書き

C:\ProgramData\Autodesk\ApplicationPlugins\MayaScanner\Contents\plug-ins



Python で TGAの情報(header)を読み込む方法

今回の案件でテクスチャはTGA 24bit RLE圧縮という指定がありまして、それをチェックが出来るツールを書こうとしていました。

まずは既に書いてある物をネットで探して、使えそうなモジュールはこれでした:

pyTGA
https://github.com/MircoT/pyTGA

TGAを読み込んだり、書き出したり出来るモジュールです。
そして、全てデフォルトのPythonで動くモジュールです!

全てPythonだから好きに編集が出来るけど、処理が重いのかなという心配もあります。
とりあえず試してみます。

使い方を読んで、以下のコマンドで欲しかった情報を取得ができました。
image = pyTGA.Image()
image.load( texture_filePath )
print image._header.pixel_depht


しかし、重い。
1枚数十秒だったので、4K テクスチャを数十枚を確認するには向いていませんね・・・。

どうやら load でテクスチャのデータを全て読み込んでいるようで、かなり時間が掛かってしまいます。

でも、欲しいのはヘッダーの部分だけ。
テクスチャのデータとかはいらないので、色々弄ってみて以下のコードに変わりました。
あっちこちの調整も必要があったけど、主にヘッダーの読み込みに役に立ちそうな部分だけを残して、あとは削除する作業でなんとかなりました:

from struct import unpack
def dec_byte(data, size=1, littleEndian=True):
"""Decode some data from bytes.

Args:
data (bytes): data to decode
size (int): number of bites of the data
littleEndian (bool): little endian or big endian

Returns:
int: the decoded data
"""
order = str('<' if littleEndian else '>')
format_ = str((None, 'B', 'H', None, 'I')[size])

return unpack(order + format_, data)[0]

class TGAHeader(object):
"""
#- Field(1)
# ID LENGTH (1 byte):
# Number of bites of field 6, max 255.
# Is 0 if no image id is present.
#
#- Field(2)
# COLOR MAP TYPE (1 byte):
# - 0 : no color map included with the image
# - 1 : color map included with the image
#
#- Field(3)
# IMAGE TYPE (1 byte):
# - 0 : no data included
# - 1 : uncompressed color map image
# - 2 : uncompressed true color image
# - 3 : uncompressed black and white image
# - 9 : run-length encoded color map image
# - 10 : run-length encoded true color image
# - 11 : run-length encoded black and white image
#
#- Field(4)
# COLOR MAP SPECIFICATION (5 bytes):
# - first_entry_index (2 bytes) : index of first color map entry
# - color_map_length (2 bytes)
# - color_map_entry_size (1 byte)
#
#- Field(5)
# IMAGE SPECIFICATION (10 bytes):
# - x_origin (2 bytes)
# - y_origin (2 bytes)
# - image_width (2 bytes)
# - image_height (2 bytes)
# - pixel_depth (1 byte):
# - 8 bit : grayscale
# - 16 bit : RGB (5-5-5-1) bit per color
# Last one is alpha (visible or not)
# - 24 bit : RGB (8-8-8) bit per color
# - 32 bit : RGBA (8-8-8-8) bit per color
# - image_descriptor (1 byte):
# - bit 3-0 : number of attribute bit per pixel
# - bit 5-4 : order in which pixel data is transferred
# from the file to the screen
"""

def __init__(self, file_name):
with open(file_name, "rb") as image_file:

# Read Header
image_file.seek(0)
# ID LENGTH
self.id_length = dec_byte(image_file.read(1))
# COLOR MAP TYPE
self.color_map_type = dec_byte(image_file.read(1))
# IMAGE TYPE
self.image_type = dec_byte(image_file.read(1))
# COLOR MAP SPECIFICATION
self.first_entry_index = dec_byte(image_file.read(2), 2)
self.color_map_length = dec_byte(image_file.read(2), 2)
self.color_map_entry_size = dec_byte(image_file.read(1))
# IMAGE SPECIFICATION
self.x_origin = dec_byte(image_file.read(2), 2)
self.y_origin = dec_byte(image_file.read(2), 2)
self.image_width = dec_byte(image_file.read(2), 2)
self.image_height = dec_byte(image_file.read(2), 2)
self.pixel_depth = dec_byte(image_file.read(1))
self.image_descriptor = dec_byte(image_file.read(1))


これで欲しかった情報を一瞬で読み込むことが出来ました。
テクスチャの作り方にミスがあったかどうかはすぐに分かる。

tga = TGAHeader( texture_filePath )
print tga.image_type #3以下の場合は圧縮なし
print tga.image_height #高さ
print tga.image_width #横幅
print tga.pixel_depth #24bit か 32bit


そして何もインストールする必要がない!
チェックツール1つだけで済むというのも嬉しいですね。

Maya | ヒストリを残さないアトリビュートの転送(メモ)

今日見つけたやり方です。昔のMayaユーザーにとってこれは当たり前かは分かりませんが、ちょっと面白いと思ったのでメモっておきます。

アトリビュート転送をウェイトが入っているモデルに使うと「デフォーマ以外のヒストリ(Non-Deformer History)」がこのアトリビュート転送のヒストリを消してくれない。普通のトランスフォームノードではなく、中間シェイプに転送すれば、ヒストリでアトリビュート転送を消してもウェイトが残ります。

transfer.jpg


やり方:
・Outlinerではシェイプノードが表示されるようにShapeにチェックします。
・中間シェイプ (Intermediate Shape) を探して(普通はなんとか _Orig という名前になっています)、 Intermediate Object アトリビュートのチェックを外す。これでやっと中間シェイプがOutlinerには表示されます。
・転送したい元のオブジェクトを選択して、中間シェイプを選択して、アトリビュート転送を行います。
・中間シェイプのヒストリを削除して
・中間シェイプのIntermediate Object のチェックを付けなおします。
以上

試しにポーズされたままで転送を使ってみた動画です。


Maya Transfer Attributes without history from myara on Vimeo.



スクリプト化は簡単そうなので、そのうちに書いてみようかな。

SI データを Maya に持って行ったり、持って来たり

MayaとSoftimageにデータを持って行ったりもって来たりするツールを作ろうとしています。

最初のプロトタイプ(テスト用)が出来たので、公開します。

オブジェクトモデルをインポートエクスポート
UVをインポートエクスポート

という機能です。

仕組みはわりと簡単です。
一時フォルダにOBJに書き出して、OBJを読み込む。
UVの場合は、一時フォルダからOBJを読み込んで、UVをコピーして、OBJを削除。

※ C:\TEMP フォルダを使用されていない場合は自動に作成されます。

※ ただのOBJなので、マテリアル設定などは持っていけない。


色々考えて試してみた結果、この簡単な方法が一番うまく行きました。

これでFBXとかを使い分けると共有が出来るデータの幅が広がりそう。例えばSIにデータを持って行って、SIでウェイト作業を行って、ウェイトだけMayaに持って帰ったりして・・・。

参考までにして、好きにカスタマイズしてみてください。



MayaSI
Download

・script フォルダにコピー
・Python で:

オブジェクトデータをエクスポート
import MayaSI
MayaSI.expOBJ()

オブジェクトをインポート
import MayaSI
MayaSI.impOBJ()

UVをインポート
import MayaSI
MayaSI.impUV()


SIMaya_Plugin
Download

プラグインフォルダにコピー

オブジェクトデータをエクスポート
Application.expOBJ()

オブジェクトをインポート
Application.impOBJ()

UVをインポート
Application.impUV()

SI | Normal + Inmed モードでもウェイトが消えない方法

Softimage でInmedモード(ヒストリーが残らないモード)のままでウェイトが付いているオブジェクトにNormal(法線)ツールを使うと、ウェイトが消えてしまう。

Inmedモードで法線いじらなければ済む問題ですが、ついやってしまいます。

Inmedモードを切って、
法線をいじって、
Freeze M を掛けて
Inmedモードに戻す

という手順になりますが、これは自動に出来ないのかと今更思いついて書いてみました。

答えは、コマンドイベントを作って、以上の手順を自動にすればいいだけです。
なんで早くこれをやらなかったのかな!

以下のコードをPythonファイルに保存して

例:NormalsEvent.py



プラグインフォルダに入れる:

例: C:\Users\myara\Autodesk\Softimage_2015_SP1\Application\Plugins


ワークグループの場合は:

C:\Workgroup\Application\Plugins


それだけです。あとはSoftimageに任せてください。


from win32com.client import constants as c
xsi = Application

def XSILoadPlugin( in_reg ):
in_reg.Author = "myara"
in_reg.Name = "NormalsEvent Plug-in"
in_reg.Major = 1
in_reg.Minor = 0

in_reg.RegisterEvent("Normal_BeginCommand", c.siOnBeginCommand)
in_reg.RegisterEvent("Normal_EndCommand", c.siOnEndCommand)
#RegistrationInsertionPoint - do not remove this line

return True

def XSIUnloadPlugin( in_reg ):
strPluginName = in_reg.Name
Application.LogMessage(str(strPluginName) + str(" has been unloaded."), c.siVerbose)
return True

def Normal_BeginCommand_OnEvent( in_ctxt ):
Application.LogMessage("BeginCommand_OnEvent called", c.siVerbose)
Application.LogMessage("Command: " + str(in_ctxt.GetAttribute("Command")), c.siVerbose)

coms = ['Tweak Normal Tool', 'Average User Normals', 'Set User Normal Values', 'Set Vertex User Normals', 'Set Polygon User Normals', 'Invert User Normals', 'Normalize User Normals', 'Paste User Normals', 'Create User Normals']

if str(in_ctxt.GetAttribute("Command")) in coms:
UserImmed = xsi.GetUserPref("OperationMode")
if UserImmed == True :
xsi.SetGlobal('Normal_Immed', 1)
xsi.SetUserPref("OperationMode", False )

return False

def Normal_EndCommand_OnEvent( in_ctxt ):
Application.LogMessage("EndCommand_OnEvent called", c.siVerbose)
Application.LogMessage("Command: " + str(in_ctxt.GetAttribute("Command")), c.siVerbose)

coms = ['Tweak Normal Tool', 'Average User Normals', 'Set User Normal Values', 'Set Vertex User Normals', 'Set Polygon User Normals', 'Invert User Normals', 'Normalize User Normals', 'Paste User Normals', 'Create User Normals']

if str(in_ctxt.GetAttribute("Command")) in coms:
UserImmed = xsi.GetUserPref("OperationMode")
if xsi.GetGlobal('Normal_Immed') == 1 :
sel = xsi.Selection
for obj in sel:
if obj.Type.find('Component')!=-1:
obj = obj.SubComponent.Parent3DObject
xsi.FreezeModeling(obj)
xsi.SetUserPref("OperationMode", True )

return False


元から書いてある部分
Application.LogMessage("Command: " + str(in_ctxt.GetAttribute("Command")), c.siVerbose)

をあえて残しています。
このコマンドイベントは全てのコマンドを感知して、この行で使われたコマンドのログを残してくれる。
スクリプトエディターのプリファレンスにVerboseもログするようにしない限りログには出てこないけど、似たようなコマンドイベントを作りたい場合は便利だと思ったので、念のため残しておきます。

では、 Read more
Next page