1주차 부터 이어지는 내실 다지기 IDA 버전
IDA 학습 보고서

일단 기본적인 내실을 다지기 위한 IDA로 리버싱 문제 풀기
분석 시작 ( 시행 착오 )
이번주에 pdb블럭을 직접 생성해보고 싶어서 노트북으로 직접 epic 허브를 깔고 언리얼엔진을 다운로드 받은 언리얼엔진에서 컴파일 시도 → 너무 오래걸림
피씨방에서 가서 다시 다운로드 받은뒤 시도 → pc방 용량이 부족하다면서

한번 팅기고 다시 용량 확보후 다시 시도

crash 발생 pdb 만들기 보류
Fname 을 찾아서
pdb가 존재 하지 않는 데모게임을 분석하려고 했으나 아무리 찾아도 Fname을 찾을 수 없었습니다.
미숙해서 그런지 몰라도
시도한 방법
1 . crtl+ 1 Strings 에서 찾기 → 없음
2 . function에서 찾기 → 없음
따라서 방향석을 잃고 Dumper와 sdk generator를 이용해보기로 결심
SDK Generator
UE4SDKGenerator
s4m33r89
UnrealEngineSDKGenerator
polivilas
SDK Generator 를 통해 한번 분석을 해보려고 했습니다.
sdk를 계속 알아보니 unreal engine4 sdk generator 가 유명하다는 것을 알게 되었고 이를 사용해 보려 다운로드 해보았으나 원인 모를 오류발생 빌드도 불가
Dumper
UnrealDumper-4.25
guttir14
사용하려고 하니 언리얼엔진4 까지 지원한다고

→ 뭔가 이상함의 기운을 감지했습니다. 잘못하고 있다.
기본으로 돌아가기
언리얼 엔진 작동 방식( 다소 부정확할 수 있음 추후 수정 )
- 언리얼엔진으로 프로젝트를 만든다.
- 프로젝트를 패키징한 이를 통해 오디오 , 모델링 등 리소스 파일들은 Cook 과정을 거친다.
- 이제 해당 엔진코드 들이 동적라이브러리로 변환(?) 된다.
그렇다면 FNAME와 같은 구조체는 어떻게 처리될까?
언리얼엔진 오픈소스 허가받고 git clone으로 내려받기 (한 1시간 걸린듯)
이후 setup.bat 파일과 GenerateProjectFiles.bat 파일을 실행 ( 한 30분 소요)
이제 그러면 .sln 파일을 얻을 수 있었고 비주얼 스튜디오로 열 수 있다.
이제 분석을 해보면
class FName { public: #if UE_FNAME_OUTLINE_NUMBER CORE_API FNameEntryId GetComparisonIndex() const; CORE_API FNameEntryId GetDisplayIndex() const; #else // UE_FNAME_OUTLINE_NUMBER FORCEINLINE FNameEntryId GetComparisonIndex() const { checkName(IsWithinBounds(ComparisonIndex)); return ComparisonIndex; } ...
드디어 Fname 이 뭔지 찾을 수 있었다.
Fname은 클래스 였고 아래를 더 살펴 보니 드디어 GetPlainNameString 이 뭔지 찾을 수 있었다.
→ 클래스 멤버 함수 였다.
/** Get name without number part as a dynamically allocated string */ CORE_API FString GetPlainNameString() const; /** Convert name without number part into TCHAR buffer and returns string length. Doesn't allocate. */ CORE_API uint32 GetPlainNameString(TCHAR(&OutName)[NAME_SIZE]) const;
CORE_API는 Unreal Engine에서 특정 함수나 클래스가 다른 모듈에서도 접근 가능하도록 하는 매크로라고 합니다.실질적인 함수는 ? → unrealNames.cpp
GetPlainNameString 가 어떻게 구성되는지 보고 싶어서 찾아보았습니다.
unrealNames.cpp 에서 찾을 수 있었으며
uint32 FName::GetPlainNameString(TCHAR(&OutName)[NAME_SIZE]) const { const FNameEntry& Entry = *GetDisplayNameEntry(); Entry.GetName(OutName); return Entry.GetNameLength(); } FString FName::GetPlainNameString() const { return GetDisplayNameEntry()->GetPlainNameString(); }
GetDisplayNameEntry 가 뭘까?
const FNameEntry* FName::GetDisplayNameEntry() const { return ResolveEntryRecursive(GetDisplayIndexInternal()); }
FNameEntry 포인터를 반환한다.
GetName은 문자열을 반환해준다.
void FNameEntry::GetName(TCHAR(&OutName)[NAME_SIZE]) const { CopyAndConvertUnterminatedName(OutName); OutName[GetNameLength()] = TEXT('\0'); }
CopyAndConvertUnterminatedName
void FNameEntry::CopyAndConvertUnterminatedName(TCHAR* OutName) const { if (sizeof(TCHAR) < sizeof(WIDECHAR) && IsWide()) // Normally compiled out { FNameBuffer Temp; CopyUnterminatedName(Temp.WideName); ConvertInPlace<WIDECHAR, TCHAR>(Temp.WideName, Header.Len); FPlatformMemory::Memcpy(OutName, Temp.AnsiName, Header.Len * sizeof(TCHAR)); } else if (IsWide()) { CopyUnterminatedName((WIDECHAR*)OutName); ConvertInPlace<WIDECHAR, TCHAR>((WIDECHAR*)OutName, Header.Len); } else { CopyUnterminatedName((ANSICHAR*)OutName); ConvertInPlace<ANSICHAR, TCHAR>((ANSICHAR*)OutName, Header.Len); } }
FNameEntry
/** * A global deduplicated name stored in the global name table. */ struct FNameEntry { private: #if WITH_CASE_PRESERVING_NAME FNameEntryId ComparisonId; #endif FNameEntryHeader Header; // Unaligned to reduce alignment waste for non-numbered entries struct FNumberedData { #if UE_FNAME_OUTLINE_NUMBER #if WITH_CASE_PRESERVING_NAME // ComparisonId is 4B-aligned, 4B-align Id/Number by 2B pad after 2B Header uint8 Pad[sizeof(Header) % alignof(decltype(ComparisonId))]; #endif uint8 Id[sizeof(FNameEntryId)]; uint8 Number[sizeof(uint32)]; #endif // UE_FNAME_OUTLINE_NUMBER }; ... /** Copy null-terminated name to TCHAR buffer without allocating. */ CORE_API void GetName(TCHAR(&OutName)[NAME_SIZE]) const;
Nametypes.h 파일에서 GetName이 FNameEntry에 존재하는 것을 확인
해당 함수는 다음과 같이 구성되어있습니다.
FString FNameEntry::GetPlainNameString() const { checkName(Header.Len != 0); FNameBuffer Temp; if (Header.bIsWide) { return FString::ConstructFromPtrSize(GetUnterminatedName(Temp.WideName), Header.Len); } else { return FString::ConstructFromPtrSize(GetUnterminatedName(Temp.AnsiName), Header.Len); } }
본론 : FName 을 사용하면 어떻게 되나?
FName 사용방법
FName DefaultName; FName PlayerName(TEXT("Player")); FName PlayerNameWithNumber(TEXT("Player"), 1); 등등 수많은 방법 존재
그렇다면 FName 생성자는?
FName 생성자
FName::FName(const WIDECHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType)) {} FName::FName(const ANSICHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType)) {} FName::FName(const UTF8CHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType)) {} FName::FName(int32 Len, const WIDECHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) {} FName::FName(int32 Len, const ANSICHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) {} FName::FName(int32 Len, const UTF8CHAR* Name, EFindName FindType) : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) {} FName::FName(const WIDECHAR* Name, int32 InNumber) : FName(FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FNAME_Add, InNumber)) {} FName::FName(const ANSICHAR* Name, int32 InNumber) : FName(FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FNAME_Add, InNumber)) {} FName::FName(const UTF8CHAR* Name, int32 InNumber) : FName(FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FNAME_Add, InNumber)) {} FName::FName(int32 Len, const WIDECHAR* Name, int32 InNumber) : FName(InNumber != NAME_NO_NUMBER_INTERNAL ? FNameHelper::MakeWithNumber(MakeUnconvertedView(Name, Len), FNAME_Add, InNumber) : FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FNAME_Add)) {} FName::FName(int32 Len, const ANSICHAR* Name, int32 InNumber) : FName(InNumber != NAME_NO_NUMBER_INTERNAL ? FNameHelper::MakeWithNumber(MakeUnconvertedView(Name, Len), FNAME_Add, InNumber) : FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FNAME_Add)) {} FName::FName(int32 Len, const UTF8CHAR* Name, int32 InNumber) : FName(InNumber != NAME_NO_NUMBER_INTERNAL ? FNameHelper::MakeWithNumber(MakeUnconvertedView(Name, Len), FNAME_Add, InNumber) : FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FNAME_Add)) {} FName::FName(const TCHAR* Name, int32 InNumber, bool bSplitName) : FName(InNumber == NAME_NO_NUMBER_INTERNAL && bSplitName ? FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FNAME_Add) : FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FNAME_Add, InNumber)) {} FName::FName(const FNameEntrySerialized& LoadedEntry) : FName(FNameHelper::MakeFromLoaded(LoadedEntry)) {}
FNameHelper 구조체임
/** Templated implementations of non-templated member functions, helps keep header clean */ struct FNameHelper { template<typename ViewType> static FName MakeDetectNumber(ViewType View, EFindName FindType) { if (View.Len == 0) { return FName(); } uint32 InternalNumber = ParseNumber(View.Str, /* may be shortened */ View.Len); return MakeWithNumber(View, FindType, InternalNumber); } static FName MakeWithNumber(FNameAnsiStringView View, EFindName FindType, int32 InternalNumber) { // Ignore the supplied number if the name string is empty // to keep the semantics of the old FName implementation if (View.Len == 0) { return FName(); } --- 여러함수 존재
FLOW
MakeDetectNumber
static FName MakeDetectNumber(ViewType View, EFindName FindType) { if (View.Len == 0) { return FName(); } uint32 InternalNumber = ParseNumber(View.Str, /* may be shortened */ View.Len); return MakeWithNumber(View, FindType, InternalNumber); }
숫자를 Parse 한뒤
MakeDetectNumber → MakeWithNumber
MakeWithNumber
static FName MakeWithNumber(FNameAnsiStringView View, EFindName FindType, int32 InternalNumber) { // Ignore the supplied number if the name string is empty // to keep the semantics of the old FName implementation if (View.Len == 0) { return FName(); } return MakeInternal(FNameStringView(View.Str, View.Len), FindType, InternalNumber); }
문자열 길이 검사한뒤
MakeWithNumber → MakeInternal
MakeInternal
static FName MakeInternal(FNameStringView View, EFindName FindType, int32 InternalNumber) { FNameEntryIds Ids; UE_AUTORTFM_OPEN({ Ids = FindOrStoreString(View, FindType); }); #if UE_FNAME_OUTLINE_NUMBER if (FindType == FNAME_Find && !Ids.DisplayId) { // We need to disambiguate here between "we found 'None'" and "we didn't find what we were looking for" if (View.IsNoneString()) { return MakeWithNumber(Ids, FindType, InternalNumber); // 'None' with suffix } else { return FName(); // Not found } } #endif return MakeWithNumber(Ids, FindType, InternalNumber); }
Ids = FindOrStoreString(View, FindType);
이부분에서 FindType에 따라 결정
if ( FindType이 FNAME_Add 라면 )
해당 문자열을 저장하고 저장된 ids 를 가져온다.
if ( FindType이 FNAME_Find 라면 )
해당 문자열을 찾아서 저장되어있는 ids를 가져온다.
FindOrStoreString 코드
static FNameEntryIds FindOrStoreString(FNameStringView View, EFindName FindType) { FNameEntryIds Result{}; UE_AUTORTFM_OPEN( { if (View.Len >= NAME_SIZE) { // If we're doing a find, and the string is too long, then clearly we didn't find it if (FindType == FNAME_Find) { Result = FNameEntryIds{}; } else { checkf(false, TEXT("FName's %d max length exceeded. Got %d characters excluding null-terminator:\n%.*s"), NAME_SIZE - 1, View.Len, NAME_SIZE, View.IsAnsi() ? ANSI_TO_TCHAR(View.Ansi) : View.Wide) const ANSICHAR* ErrorString = "ERROR_NAME_SIZE_EXCEEDED"; Result = FindOrStoreString(FNameStringView(ErrorString, FCStringAnsi::Strlen(ErrorString), false), FNAME_Add); } } else { FNamePool& Pool = GetNamePool(); if (FindType == FNAME_Add) { FNameEntryId DisplayId = Pool.Store(View); Result = FNameEntryIds{ ResolveComparisonId(DisplayId), DisplayId }; } else { check(FindType == FNAME_Find); FNameEntryId DisplayId = Pool.Find(View); Result = FNameEntryIds{ ResolveComparisonId(DisplayId), DisplayId }; } } }); return Result; }
if (FindType == FNAME_Find && !Ids.DisplayId)
FindType == FNAME_Find 이라는 것은 사용자가 문자열을 저장한다는게 아니라 찾을 때 TypeMakeInternal → MakeWithNumber(IDS , ~ , ~ )
static FName MakeWithNumber(FNameEntryIds BaseIds, EFindName FindType, int32 InternalNumber) { #if UE_FNAME_OUTLINE_NUMBER #if WITH_CASE_PRESERVING_NAME // Advanced users must pass in matching display & comparison ids checkName(FName::ResolveEntry(BaseIds.DisplayId)->ComparisonId == BaseIds.ComparisonId); #endif // If BaseIds are NAME_None, we want to produce a numbered suffix of None. // If anything searches for BaseIds they need to validate whether they want to pass NAME_None on or not before calling this function. if (InternalNumber == NAME_NO_NUMBER_INTERNAL) { return FinalConstruct(BaseIds); } FNamePool& Pool = GetNamePool(); if (FindType == FNAME_Add) { FNameEntryId DisplayId; UE_AUTORTFM_OPEN({ DisplayId = Pool.StoreWithNumber(BaseIds, InternalNumber); }); return FinalConstruct(FNameEntryIds{ ResolveComparisonId(DisplayId), DisplayId }); } else { check(FindType == FNAME_Find); FNameEntryId DisplayId = Pool.FindWithNumber(BaseIds.DisplayId, InternalNumber); if (DisplayId) { return FinalConstruct(FNameEntryIds{ ResolveComparisonId(DisplayId), DisplayId }); } else { // Not found return FName(); } } #else // Number is just stored in the FName pass it straight on return FinalConstruct(BaseIds, InternalNumber); #endif } template<typename CharType> static bool EqualsString(FName Name, const CharType* Str, int32 Len) { check(Len >= 0); // Make NAME_None == TEXTVIEW("") or TEXTVIEW(nullptr) consistent with NAME_None == FName(TEXT("")) or FName(nullptr) if (Len == 0) { return Name.IsNone(); } // "_[num]" is considered "None" to keep semantics of old FName implementation if (Name.IsNone() & (Str[0] == '_')) { ParseNumber(Str, /* in-out */ Len); return Len == 0; } const FNameEntry* Entry = FName::ResolveEntry(Name.GetComparisonIndexInternal()); #if UE_FNAME_OUTLINE_NUMBER int32 Number = Entry->IsNumbered() ? Entry->GetNumber() : NAME_NO_NUMBER_INTERNAL; Entry = Entry->IsNumbered() ? FName::ResolveEntry(Entry->GetNumberedName().Id) : Entry; check(!Entry->IsNumbered()); #else int32 Number = Name.GetNumber(); #endif // Check _[number] suffix first and shorten Len if it matches if (Number != NAME_NO_NUMBER_INTERNAL && Number != ParseNumber(Str, /* in-out */ Len)) { return false; } if (Len != Entry->GetNameLength()) { return false; } FNameBuffer Temp; return Entry->IsWide() ? !FPlatformString::Strnicmp(Str, Entry->GetUnterminatedName(Temp.WideName), Len) : !FPlatformString::Strnicmp(Str, Entry->GetUnterminatedName(Temp.AnsiName), Len); } template<typename CharType> static uint32 ParseNumber(const CharType* Name, int32& InOutLen) { const int32 Len = InOutLen; int32 Digits = 0; for (const CharType* It = Name + Len - 1; It >= Name && *It >= '0' && *It <= '9'; --It) { ++Digits; } const CharType* FirstDigit = Name + Len - Digits; static constexpr int32 MaxDigitsInt32 = 10; if (Digits && Digits < Len && *(FirstDigit - 1) == '_' && Digits <= MaxDigitsInt32) { // check for the case where there are multiple digits after the _ and the first one // is a 0 ("Rocket_04"). Can't split this case. (So, we check if the first char // is not 0 or the length of the number is 1 (since ROcket_0 is valid) if (Digits == 1 || *FirstDigit != '0') { int64 Number = Atoi64(Name + Len - Digits, Digits); if (Number < MAX_int32) { InOutLen -= 1 + Digits; return static_cast<uint32>(NAME_EXTERNAL_TO_INTERNAL(Number)); } } } return NAME_NO_NUMBER_INTERNAL; }
요약 Flag와 인자에 따라 여러가지 함수제공
MakeWithNumber(IDS , ~ , ~ ) → FinalConstruct
static FName FinalConstruct(FNameEntryIds Ids, int32 InternalNumber) #endif // UE_FNAME_OUTLINE_NUMBER { FName Out; Out.ComparisonIndex = Ids.ComparisonId; #if WITH_CASE_PRESERVING_NAME Out.DisplayIndex = Ids.DisplayId; #endif #if !UE_FNAME_OUTLINE_NUMBER Out.Number = InternalNumber; #endif return Out; }
단순히 만들기
일단 여기까지 오픈소스 분석이었습니다.
+추가 치트 엔진 튜토리얼 밀기

치트엔진 튜토리얼은 8레벨 까지 밀었습니다.