Repository: angr/angr

Issue:
X86 instruction loop does not end a block with size specified
<!--
*Disclaimer:
The angr suite is maintained by a small team of volunteers.
While we cannot guarantee any timeliness for fixes and enhancements, we will do our best.
For more real-time help with angr, from us and the community, join our [Slack.](http://angr.io/invite/)*
-->
**Describe the bug.**
<!--
Please include a clear and concise description of what the bug is.
-->

When `size` is explicitly specified to `Block()`, the X86 instruction `loop` does not put an end to the current block, which messes the CFG. See 0x4010d3 in the following picture.

![image](https://user-images.githubusercontent.com/5071575/71873602-d30cef00-30dc-11ea-9b39-0cfa11c870a9.png)


**Environment Information.**
<!--
Many common issues are caused by problems with the local Python environment.
Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date.
Please include the output of `python -m angr.misc.bug_report` here.
-->

Latest angr and angr management.

**To Reproduce.**
<!--
Please include *both a script to reproduce the crash, and attach the binary used, if possible*
-->

Decode the basic block 0x4010a2 in binary angr-doc/examples/flareon2015_2/very_success.

**Additional context.**
<!--
Add any other context about the problem here.
-->

The flag `strict_block_end` is lost if `size` is explicitly specified.


Discussion:
we did this already. you need `strict_block_end=True`
This is a different issue. PR incoming.

Resolving patch:
diff --git a/angr/analyses/cfg/cfg_fast.py b/angr/analyses/cfg/cfg_fast.py
--- a/angr/analyses/cfg/cfg_fast.py
+++ b/angr/analyses/cfg/cfg_fast.py
@@ -3397,7 +3397,8 @@ def _generate_cfgnode(self, cfg_job, current_function_addr):
             irsb = None
             irsb_string = None
             try:
-                lifted_block = self._lift(addr, size=distance, opt_level=self._iropt_level, collect_data_refs=True)
+                lifted_block = self._lift(addr, size=distance, opt_level=self._iropt_level, collect_data_refs=True,
+                                          strict_block_end=True)
                 irsb = lifted_block.vex_nostmt
                 irsb_string = lifted_block.bytes[:irsb.size]
             except SimTranslationError:
@@ -3421,7 +3422,7 @@ def _generate_cfgnode(self, cfg_job, current_function_addr):
 
                 try:
                     lifted_block = self._lift(addr_0, size=distance, opt_level=self._iropt_level,
-                                              collect_data_refs=True)
+                                              collect_data_refs=True, strict_block_end=True)
                     irsb = lifted_block.vex_nostmt
                     irsb_string = lifted_block.bytes[:irsb.size]
                 except SimTranslationError:
diff --git a/angr/block.py b/angr/block.py
--- a/angr/block.py
+++ b/angr/block.py
@@ -15,7 +15,7 @@ class Block(Serializable):
     BLOCK_MAX_SIZE = 4096
 
     __slots__ = ['_project', '_bytes', '_vex', 'thumb', '_capstone', 'addr', 'size', 'arch', '_instructions',
-                 '_instruction_addrs', '_opt_level', '_vex_nostmt', '_collect_data_refs'
+                 '_instruction_addrs', '_opt_level', '_vex_nostmt', '_collect_data_refs', '_strict_block_end',
                  ]
 
     def __init__(self, addr, project=None, arch=None, size=None, byte_string=None, vex=None, thumb=False, backup_state=None,
@@ -72,6 +72,7 @@ def __init__(self, addr, project=None, arch=None, size=None, byte_string=None, v
         self._capstone = None
         self.size = size
         self._collect_data_refs = collect_data_refs
+        self._strict_block_end = strict_block_end
 
         self._instructions = num_inst
         self._instruction_addrs = []
@@ -153,6 +154,7 @@ def vex(self):
                     opt_level=self._opt_level,
                     arch=self.arch,
                     collect_data_refs=self._collect_data_refs,
+                    strict_block_end=self._strict_block_end,
             )
             self._parse_vex_info()
 
@@ -177,6 +179,7 @@ def vex_nostmt(self):
             arch=self.arch,
             skip_stmts=True,
             collect_data_refs=self._collect_data_refs,
+            strict_block_end=self._strict_block_end,
         )
         return self._vex_nostmt
 


Test patch:
diff --git a/tests/test_lifter.py b/tests/test_lifter.py
--- a/tests/test_lifter.py
+++ b/tests/test_lifter.py
@@ -17,5 +17,56 @@ def test_strict_block_ends_cbz():
     p.factory.default_engine.default_strict_block_end = True
     assert p.factory.block(1).instructions == 2
 
+
+def test_strict_block_ends_with_size_x86():
+    # cmovnz  cx, dx
+    # pop     eax
+    # jecxz   short loc_4010D7  ; the first block should end here
+    # sub     edi, 2
+    # loop    loc_4010c9
+    # nop
+    # nop
+
+
+    b = b"\x66\x0f\x45\xca\x58\xe3\x07\x83\xef\x02\xe2\xf4\x90\x90"
+    p = angr.load_shellcode(b, 'x86', load_address=0x4010c9)
+
+    # jecxz
+    assert p.factory.block(0x4010c9, size=len(b), strict_block_end=False).instructions == 7
+    assert p.factory.block(0x4010c9, strict_block_end=True).instructions == 3
+    assert p.factory.block(0x4010c9, size=len(b), strict_block_end=True).instructions == 3
+
+    # loop
+    assert p.factory.block(0x4010d0, strict_block_end=False).instructions == 4
+    assert p.factory.block(0x4010d0, strict_block_end=True).instructions == 2
+    assert p.factory.block(0x4010d0, size=7, strict_block_end=True).instructions == 2
+
+
+def test_strict_block_ends_with_size_amd64():
+    # cmovnz  cx, dx
+    # pop     rax
+    # jrcxz   short loc_4010D7  ; the first block should end here
+    # sub     edi, 2
+    # loop    loc_4010c9
+    # nop
+    # nop
+
+
+    b = b"\x66\x0f\x45\xca\x58\xe3\x07\x83\xef\x02\xe2\xf4\x90\x90"
+    p = angr.load_shellcode(b, 'amd64', load_address=0x4010c9)
+
+    # jrcxz
+    assert p.factory.block(0x4010c9, size=len(b), strict_block_end=False).instructions == 7
+    assert p.factory.block(0x4010c9, strict_block_end=True).instructions == 3
+    assert p.factory.block(0x4010c9, size=len(b), strict_block_end=True).instructions == 3
+
+    # loop
+    assert p.factory.block(0x4010d0, strict_block_end=False).instructions == 4
+    assert p.factory.block(0x4010d0, strict_block_end=True).instructions == 2
+    assert p.factory.block(0x4010d0, size=7, strict_block_end=True).instructions == 2
+
+
 if __name__ == '__main__':
     test_strict_block_ends_cbz()
+    test_strict_block_ends_with_size_x86()
+    test_strict_block_ends_with_size_amd64()