Malware ანალიზი 'NativeApp_G4QLIQRa.exe' ნაწილი #3
გაგრძელება
ახლა მეორე ფუნქციის განხილვის დროა. მოკლედ, იმ შემთხვევაში თუ ax-ის მნიშვნელობა 6-ს არ დაემთხვა, მაშინ რამდენიმე ინსტრუქციის შემდეგ ამუშავდება 0x00406694 მეხსიერების მისამართზე მყოფი ფუნქცია.
fcn.0x00406694
ასეთი პატარა ფუნქციაა მოცემული. მას სჭირდება ერთი პარამეტრი, რომელიც იქნება ეს:
1
int32_t arg_4h
ახლა ვეცდები თითოეული ინსტრუქცია აღარ ავხსნა და ზოგადი მიმოხილვა გავაკეთო. საბოლოო ჯამში, arg_4h ცვლადის მნიშვნელობა ჯდება esi რეგისტრატორში, სადაც ის განიცდის მარცხნივ 3-ჯერ გადასვლას(Shift Left):
1
shl esi, 3
ამის შემდეგ, edi რეგისტრატორში გადავა ის ცვლადი, რომლის მეხსიერების მისამართის გამოთვლაც ასე ხდება:
1
esi + 0x40a3e4
ამის შემდეგ, edi რეგისტრატორის მნიშვნელობა სტაკზე შედის და იწყება GetModuleHandleA ფუნქციის გამოძახება. ანუ, გამოდის, რომ ის რაც edi რეგისტრატორის მნიშვნელობა იყო, GetModuleHandleA ფუნქციას გადაეცა პარამეტრად. ეს არის მოდულის სახელი, რადგან ოფიციალური WinAPI-ს დოკუმენტაციის თანახმად, ასე ხდება ამ ფუნქციის გამოყენება:
1
2
3
HMODULE GetModuleHandleA(
[in, optional] LPCSTR lpModuleName
);
აქ, [in, optional] ნიშნავს იმას, რომ lpModuleName არის input, მაგრამ არასავალდებულო. ასევე, გასათვალისწინებელია ის, რომ lpModuleName არის Long Pointer Char String(LPCSTR). ანუ გამოდის, რომ რაღაცას(რაც არის esi-ს მნიშვნელობა) რომ დავუმატოთ 0x40a3e4 უნდა მივიღოთ იმ ტექსტის დასაწყისის მეხსიერების მისამართი, რომელიც GetModuleHandleA ფუნქციას გადაეცა.
entry0 კოდის სეგმენტს რომ დავაკვირდეთ ვნახავთ, რომ ეს არის ebx-ის მნიშვნელობა. აქვე საინტერესო ისაა, რომ თუ ეს ფუნქცია წარმატებით განხორციელდება, მაშინ ის შესაბამის მნიშვნელობას დააბრუნებს, ხოლო იმ შემთხვევაში თუ ეს ფუნქცია ვერ შეასრულებს თავის სამუშაოს, მაშინ ის NULL-ს დააბრუნებს. ამის გამო ხდება ეს:
1
test eax, eax
ეს იმიტომ ხდება, რომ ნაგულისხმევად(By default), როდესაც ფუნქცია რამეს აბრუნებს, ეს მნიშვნელობა ax რეგისტრატორში ინახება. რა თქმა უნდა, ეს იმ მონაცემთა ტიპზეცაა დამოკიდებული, რასაც ფუნქცია აბრუნებს. მაგრამ, წესით, ეს ფუნქცია HANDLE მონაცემთა ტიპს აბრუნებს, რომელიც eax-ში ჩავა(eax არის ax, მაგრამ x86 არქიტექტურაზე მას 16 ბიტი აქვს დამატებული, ჯამში სულ 32 ბიტია და ამის გამო ის მოიხსენიება როგორც eax).
აქაც ორი ვარიანტია. ან მოხდება გადახტომა ან არა. იმ შემთხვევაში თუ არ მოხდება გადახტომა, მაშინ edi-ს მნიშვნელობა სტაკზე შევა იმისთვის, რომ ეს მნიშვნელობა 0x00406624 მეხსიერების მისამართზე მყოფმა ფუნქციამ აიღოს. საინტერესოა ის ფაქტი, რომ ეს ფუნქცია აქაც გამოიყენება. წინა ნაწილში გავარჩიე ეს ფუნქცია და ის ასე მუშაობს: გამოითვლის სისტემის დირექტორიას და გარკვეულ .dll ფაილს ტვირთავს მეხსიერებაში. ანუ ფაქტია, რომ ეს .dll ფაილი ძალიან საჭიროა. იმ შემთხვევაში თუ ეს ფუნქცია კარგად დასრულა, მაშინ 0x00406694 მეხსიერების მისამართზე მყოფი ფუნქციაც გაჩერდება:
1
2
3
4
5
xor eax, eax
pop edi
pop esi
leave
ret 4
პირველი ინსტრუქცია eax-ის მნიშვნელობას ანულებს, ანუ გამოდის, რომ ფუნქციის ბოლოში ეს წერია:
1
return 0;
ასევე, როდესაც სტაკიდან return address მოძვრება, იმის გამო, რომ ret ინსტრუქციას 4 უწერია, სტაკს ასევე ბოლო 4 ბაიტი მოშორდება.
თუ CPU ფუნქციის ბოლოს არ გადახტება და ჩვეულებრივ სვლას განაგრძობს, მაშინ GetProcAddress ფუნქცია ამუშავდება.
1
2
3
4
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
როგორც ჩანს, აქ ორი პარამეტრია საჭირო და ორივე მათგანი სავალდებულოა. პირველი არის მოდულის ე.წ. “Handle”, ხოლო მეორე პარამეტრი არის ფუნქციის ან ცვლადის სახელი. ეს ფუნქცია .dll ფაილიდან კონკრეტული ფუნქციის ან ცვლადის წამოღებას ცდილობს.
“fcn.0x00406694”-ის შეჯამება
შეიძლება ითქვას, რომ ეს ფუნქცია კონკრეტულ მოდულს ეძებს, რის შედეგადაც ტვირთავს .dll ფაილს და ბოლოს ამ .dll ფაილიდან მოაქვს კონკრეტული ფუნქცია ან ცვლადი.
entry0 გრძელდება
მოკლედ, ამ ორ ფუნქციას თავი დავანებოთ. სრულად გასაგებია თუ რას აკეთებენ ისინი. ახლა ყველაზე საინტერესოა entry0-ში რა ხდება.
მოკლედ, როდესაც 0x00406624 მეხსიერების მისამართზე მყოფი ფუნქცია შესრულდება, esi-ს მნიშვნელობა შევა სტაკზე და ამოქმედდება lstrlenA ფუნქცია. ამ ფუნქციის პარამეტრი კი სწორედ esi-ს მნიშვნელობა გამოდის, რადგან მას აქვს ერთი სავალდებულო პარამეტრი:
1
2
3
int lstrlenA(
[in] LPCSTR lpString
);
ეს ფუნქცია გამოითვლის LPCSTR მონაცემთა ტიპის ტექსტის ზომას. შემდეგ კი ხდება ეს:
1
lea esi, [esi + eax + 1]
აქ esi რეგისტრატორში ჩაჯდება esi-ს მნიშვნელობას + eax-ის მნიშვნელობა + 1. ამის შემდეგ, მისი შედარება მოხდება 0-თან:
1
cmp byte [esi], 0
უფრო დეტალურად რომ ვთქვა, აქ esi-ს მნიშვნელობა რა მეხსიერების მისამართზეც მიუთითებს, იქ მყოფი ბაიტის შედარება მოხდება 0-თან.
1
2
3
4
5
6
7
8
9
10
┌───┐
│esi├────────────────────────────────────────┐
└───┘ │
│
│
┌─────────────────────────────────────────────┬▼──────────────────────────────────┬────────────────────────────────┐
│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│ │xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│
│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│ BYTE Stored Here │xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│
│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│ │xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│
└─────────────────────────────────────────────┴───────────────────────────────────┴────────────────────────────────┘
იმ შემთხვევაში თუ ეს ბაიტი 0-ს არ დაემთხვევა, მაშინ CPU გადახტება 6 ინსტრუქციით უკან და თავიდან გაიმეორებს ციკლს. იმ შემთხვევაში თუ ეს ბაიტი 0 გამოდგა, მაშინ უკვე ჩვეულებრივად გაგრძელდება კოდის მუშაობა.

