# IDA decompiler's Control Flow Guard check ignore function has a bug

**URL:** <https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332>\
**Category:** IDA General\
**Created:** [May 21, 2025, 10:13am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332 "2025-05-21T10:13:06Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [May 21, 2025, 10:13am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/1 "2025-05-21T10:13:06Z")

</div>

Compile the following code with vc x86 Release **/Od /guard:cf** :

```cpp

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <unknwn.h>

class CUnknown :public IUnknown
{
public:
	CUnknown()
	{
	}
public:
	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
		/* [in] */ REFIID riid,
		/* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR*__ RPC_FAR* ppvObject)
	{
		return S_OK;
	}
	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}
	ULONG STDMETHODCALLTYPE Release()
	{
		return 0;
	}
};
int main()
{
	IUnknown* p = new CUnknown();
	p->Release();
}

```

Use IDA to load the compiled exe and automatically load pdb. You will see the main function as follows:

```cpp
int __cdecl main()
{
  CUnknown *v1; // [esp+10h] [ebp-14h]
  CUnknown *v2; // [esp+14h] [ebp-10h]

  v2 = (CUnknown *)operator new(4u);
  if ( v2 )
    v1 = CUnknown::CUnknown(v2);
  else
    v1 = 0;
  ((void (__thiscall *)(unsigned int (__stdcall *)(IUnknown *), CUnknown *))v1->Release)(v1->Release, v1);
  return 0;
}

```

As you can see above, the `Release` call passes an extra parameter - `v1->Release`

Reference documents:

> **[Configuration | Hex-Rays Docs](https://docs.hex-rays.com/user-guide/decompiler/config#cfguard_check)**

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [May 21, 2025, 1:48pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/2 "2025-05-21T13:48:23Z")

</div>

Thank you for the report and the sample! Indeed, it seems the call is wrongly treated as `__thiscall`, probably because `ecx` is used as intermediate variable for the pointer value.

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [May 21, 2025, 2:22pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/3 "2025-05-21T14:22:38Z")

</div>

Yes, you are right. Maybe this problem has nothing to do with the Control Flow Guard check ignore function. It is just a problem with IDA’s analysis logic. But how can I correct this decompilation error? There are a lot of such recognition errors in the samples I want to analyze, so I need to automate batch processing.

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [May 21, 2025, 9:46pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/4 "2025-05-21T21:46:13Z")

</div>

I think “Set call type” might work, but the simplest solution would probably be to just nop out calls to `___guard_check_icall_fptr` and maybe the preceding manipulations with `ecx`.

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [May 28, 2025, 1:40pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/5 "2025-05-28T13:40:58Z")

</div>

Thanks for your suggestion, but I found

> nop out calls to `___guard_check_icall_fptr` and maybe the preceding manipulations with `ecx`

is not a reliable method. Here is an example:  
Change the `main` function to the following code,

```cpp
int main()
{
	IUnknown* p = new CUnknown();
	p->AddRef();
	p->QueryInterface(GUID_NULL, NULL);
	p->Release();
	return 0;
}

```

and then compile it after selecting `LLVM (clang-cl)` as the platform toolset. You will see that its disassembly is:

```asm
.text:00401000 _main proc near ; CODE XREF: __scrt_common_main_seh+F8↓p
.text:00401000
.text:00401000 var_3C = dword ptr -3Ch
.text:00401000 size = dword ptr -34h
.text:00401000 var_30 = dword ptr -30h
.text:00401000 var_2C = dword ptr -2Ch
.text:00401000 var_28 = dword ptr -28h
.text:00401000 var_24 = dword ptr -24h
.text:00401000 var_20 = dword ptr -20h
.text:00401000 var_1C = dword ptr -1Ch
.text:00401000 var_18 = dword ptr -18h
.text:00401000 var_14 = dword ptr -14h
.text:00401000 var_10 = dword ptr -10h
.text:00401000 var_C = dword ptr -0Ch
.text:00401000 var_8 = dword ptr -8
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push esi
.text:00401004 sub esp, 30h
.text:00401007 mov [ebp+var_8], 0
.text:0040100E mov [esp+34h+size], 4 ; size
.text:00401015 call ??2@YAPAXI@Z ; operator new(uint)
.text:0040101A mov ecx, eax
.text:0040101C mov [ebp+var_28], ecx
.text:0040101F call ??0CUnknown@@QAE@XZ ; CUnknown::CUnknown(void)
.text:00401024 mov eax, [ebp+var_28]
.text:00401027 mov [ebp+var_C], eax
.text:0040102A mov eax, [ebp+var_C]
.text:0040102D mov [ebp+var_24], eax
.text:00401030 mov eax, [eax]
.text:00401032 mov ecx, [eax+4] ; Target
.text:00401035 mov [ebp+var_20], ecx
.text:00401038 call ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x) ...
.text:0040103E mov ecx, [ebp+var_24]
.text:00401041 mov eax, [ebp+var_20]
.text:00401044 mov [esp+34h+size], ecx
.text:00401047 call eax
.text:00401049 sub esp, 4
.text:0040104C mov eax, [ebp+var_C]
.text:0040104F mov [ebp+var_1C], eax
.text:00401052 mov eax, [eax]
.text:00401054 mov ecx, [eax] ; Target
.text:00401056 mov [ebp+var_18], ecx
.text:00401059 call ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x) ...
.text:0040105F mov edx, [ebp+var_1C]
.text:00401062 mov eax, [ebp+var_18]
.text:00401065 lea ecx, _GUID_NULL
.text:0040106B xor esi, esi
.text:0040106D mov [esp+34h+size], edx
.text:00401070 mov [esp+34h+var_30], ecx
.text:00401074 mov [esp+34h+var_2C], 0
.text:0040107C call eax
.text:0040107E sub esp, 0Ch
.text:00401081 mov eax, [ebp+var_C]
.text:00401084 mov [ebp+var_14], eax
.text:00401087 mov eax, [eax]
.text:00401089 mov ecx, [eax+8] ; Target
.text:0040108C mov [ebp+var_10], ecx
.text:0040108F call ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x) ...
.text:00401095 mov ecx, [ebp+var_14]
.text:00401098 mov eax, [ebp+var_10]
.text:0040109B mov [esp+3Ch+var_3C], ecx
.text:0040109E call eax
.text:004010A0 sub esp, 4
.text:004010A3 xor eax, eax
.text:004010A5 add esp, 30h
.text:004010A8 pop esi
.text:004010A9 pop ebp
.text:004010AA retn
.text:004010AA _main endp

```

As you can see,

```asm
.text:00401032 mov ecx, [eax+4] ; Target
.text:00401035 mov [ebp+var_20], ecx
.text:00401038 call ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x) ...

```

There are other instructions between `mov ecx` and `call ds:__guard_check_icall_fptr`, and the middle instruction uses the value of `ecx` to be used by the following instructions.

Here’s another problem: Change the platform toolset to `Visual Studio 2010 (v100)`, you will see the following pseudo code:

```cpp
int __cdecl main(int argc, const char **argv, const char** envp)
{
  CUnknown *v4; // [esp+0h] [ebp-10h]
  CUnknown *v5; // [esp+4h] [ebp-Ch]
  IUnknown *p; // [esp+Ch] [ebp-4h]

  v5 = (CUnknown *)operator new(4u);
  if ( v5 )
    v4 = CUnknown::CUnknown(v5);
  else
    v4 = 0;
  p = v4;
  ((void (__thiscall *)(CUnknown *, CUnknown *))v4->AddRef)(v4, v4);
  p->QueryInterface(p, &GUID_NULL, 0);
  p->Release(p);
  return 0;
}

```

`Visual Studio 2010` does not support the `Control Flow Guard` feature, so there is no `___guard_check_icall_fptr` call, but IDA still misidentifies the first `AddRef` call. This seems to be a problem caused by the changes in IDA7.1, because I can get the correct output when decompiling with IDA7.0, as follows:

```cpp
int __cdecl main(int argc, const char **argv, const char** envp)
{
  IUnknown *v3; // eax
  IUnknown *p; // ST18_4
  IUnknown *v6; // [esp+0h] [ebp-10h]
  CUnknown *v7; // [esp+4h] [ebp-Ch]

  v7 = (CUnknown *)operator new(4u);
  if ( v7 )
  {
    CUnknown::CUnknown(v7);
    v6 = v3;
  }
  else
  {
    v6 = 0;
  }
  p = v6;
  v6->vfptr->AddRef(v6);
  p->vfptr->QueryInterface(p, &GUID_NULL, 0);
  p->vfptr->Release(p);
  return 0;
}

```

Therefore, I think there is a problem with the latest IDA’s judgment logic for the ` __thiscall` calling convention, especially for the scenario of virtual table function calling. In this scenario, should we strictly abide by the calling convention in the virtual table function declaration, If this information is provided through debugging symbols such as PDB/DWARF? I hope that the next version of IDA can improve the judgment logic for `__ thiscall`.  
However, here, [Force call type](https://hex-rays.com/blog/igors-tip-of-the-week-119-force-call-type) should be a mitigation measure.

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [June 3, 2025, 9:39am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/6 "2025-06-03T09:39:12Z")

</div>

Interestingly, the Clang compiled file seems to produce a correct output for me, although I get a slightly different assembly.

```auto
int __cdecl main()
{
  CUnknown *v1; // [esp+4h] [ebp-10h]

  v1 = (CUnknown *)operator new(4u);
  CUnknown::CUnknown(v1);
  v1->Release(v1);
  return 0;
}
.text:00414B98 mov eax, [eax]
.text:00414B9A mov ecx, [eax+8] ; Target
.text:00414B9D mov [ebp+var_8], ecx
.text:00414BA0 call ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x) ...
.text:00414BA6 mov ecx, [ebp+var_C]
.text:00414BA9 mov eax, [ebp+var_8]
.text:00414BAC mov [esp+14h+size], ecx
.text:00414BAF call eax
.text:00414BB1 sub esp, 4

```

In general, it’s best to always provide the actual files/idb you’re working with, as even seemingly minor differences can matter.

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [June 4, 2025, 4:34pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/7 "2025-06-04T16:34:22Z")

</div>

Because you didn’t change the code of the main function first as I said, and I compiled it with clang 20.1.5 Release /Od /guard:cf, you can try it again. In addition, you can also pay attention to the situation of using vs2010 to compile in my last reply. 7.0 is normal, and 7.1 has an additional layer of forced conversion.  
Thanks for reply.

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [June 5, 2025, 11:53am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/8 "2025-06-05T11:53:57Z")

</div>

We would really appreciate if you uploaded compiled binaries, as hunting down the exact compiler versions and switches is quite time consuming. So far, nopping out the call to `___guard_check_icall_fptr` seems to be enough in most cases (with occasional “Force call type”).

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [June 5, 2025, 12:29pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/9 "2025-06-05T12:29:42Z")

</div>

If you need binary, you’ll have to wait a few days because I’m currently on vacation and don’t have my work computer with me. Thank you.

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [June 9, 2025, 1:35pm UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/10 "2025-06-09T13:35:24Z")

</div>

Hello, I have uploaded the relevant demo exe, pdb, idb.  
source code：

```cpp
int main()
{
	IUnknown* p = new CUnknown();
	p->AddRef();
	p->QueryInterface(GUID_NULL, NULL);
	p->Release();
	return 0;
}

```

clang:  
[IdaCfgBugTest-clang.7z](https://workupload.com/file/YgaYU4j4Cua)

```cpp
int __cdecl main()
{
  int v1; // [esp+Ch] [ebp-38h]
  unsigned int size; // [esp+10h] [ebp-34h]
  IUnknown *v3; // [esp+1Ch] [ebp-28h]

  v3 = (IUnknown *)operator new(4u);
  CUnknown::CUnknown((CUnknown *)v3);
  v3->AddRef(v3);
  ((void (__stdcall *)(IUnknown *))v3->QueryInterface)(v3);
  ((void (__stdcall *)(IUnknown *, int, unsigned int))v3->Release)(v3, v1, size);
  return 0;
}

```

vs2010:  
[IdaCfgBugTest-vs2010.7z](https://www.upload.ee/files/18191702/IdaCfgBugTest-vs2010.7z.html)

```auto
int __cdecl main(int argc, const char **argv, const char** envp)
{
  CUnknown *v4; // [esp+0h] [ebp-10h]
  CUnknown *v5; // [esp+4h] [ebp-Ch]
  IUnknown *p; // [esp+Ch] [ebp-4h]

  v5 = (CUnknown *)operator new(4u);
  if ( v5 )
    v4 = CUnknown::CUnknown(v5);
  else
    v4 = 0;
  p = v4;
  ((void (__thiscall *)(CUnknown *, CUnknown *))v4->AddRef)(v4, v4);
  p->QueryInterface(p, &GUID_NULL, 0);
  p->Release(p);
  return 0;
}

```

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [June 10, 2025, 9:22am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/11 "2025-06-10T09:22:30Z")

</div>

Thank you very much for the binaries! It seems “Force call type” helps in both situations. We’ll try to see if it can be improved, but you can read here why it can be difficult to detect proper arguments for indirect calls: [Force call type | Hex-Rays Docs](https://docs.hex-rays.com/user-guide/decompiler/interactive/cmd_force_call_type)

---

<div class="post-metadata">

**Author:** ![sonyps5201314](https://avatars.discourse-cdn.com/v4/letter/s/fbc32d/32.png) [@sonyps5201314](https://community.hex-rays.com/u/sonyps5201314)\
**Post date:** [June 10, 2025, 11:31am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/12 "2025-06-10T11:31:33Z")

</div>

I understand that perfectly, but the weird thing is that as mentioned before, the vs2010 example [IdaCfgBugTest-vs2010.7z](https://www.upload.ee/files/18191702/IdaCfgBugTest-vs2010.7z.html) were fine in IDA 7.0, and the cast was present in later versions, so I think it’s a regression bug.

> ```cpp
> int __cdecl main(int argc, const char **argv, const char** envp)
> {
> IUnknown *v3; // eax
> IUnknown *p; // ST18_4
> IUnknown *v6; // [esp+0h] [ebp-10h]
> CUnknown *v7; // [esp+4h] [ebp-Ch]
> 
> v7 = (CUnknown *)operator new(4u);
> if ( v7 )
> {
> CUnknown::CUnknown(v7);
> v6 = v3;
> }
> else
> {
> v6 = 0;
> }
> p = v6;
> v6->vfptr->AddRef(v6);
> p->vfptr->QueryInterface(p, &GUID_NULL, 0);
> p->vfptr->Release(p);
> return 0;
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![igor](https://sea2.discourse-cdn.com/flex002/user_avatar/community.hex-rays.com/igor/32/35_2.png) [@igor](https://community.hex-rays.com/u/igor)\
**Post date:** [June 10, 2025, 11:58am UTC](https://community.hex-rays.com/t/ida-decompilers-control-flow-guard-check-ignore-function-has-a-bug/332/13 "2025-06-10T11:58:45Z")

</div>

Thank you. Indeed, it’s possible there was a regression in this specific case, but likely it came with improvements elsewhere.
