PurrShell How do I cheat?
Reputation: 0
Joined: 11 Feb 2025 Posts: 5
|
Posted: Fri Aug 14, 2026 2:27 pm Post subject: CEAA: Two Bugs |
|
|
CEAA: Two Bugs
======== Bug 1 ========
User writes:
| Code: | | push 7766554433221100 |
Actually assembles to:
No warning is shown — the upper 32 bits are silently discarded. Extremely hard to track down for newcomers who don't debug their own code.
--------
User writes:
| Code: | | mov qword ptr [rax],7766554433221100 |
Actually assembles to:
| Code: | | mov dword ptr [rax],33221100 |
No warning is shown — the upper 32 bits are silently discarded. Extremely hard to track down for newcomers who don't debug their own code.
======== Bug 2 ========
| Code: |
[ENABLE]
registersymbol(get_p_val)
alloc(get_p_val,$1000)
get_p_val:
call get_eip
get_eip:
pop eax
lea eax,[eax-get_eip+val] // < the bug actually happens here
ret
val: // <- but the error points at this line
dd 33221100
[DISABLE]
unregistersymbol(*)
dealloc(*)
|
error: <<12:Negative registers can not be encoded>>
The two labels are close together and the semantics are perfectly correct — it's the classic shellcode idiom, yet it fails.
======== Summary ========
Since my own ability is limited, the following explanation was produced by DeepSeek after reading through the source code:
Bug 1: missing checks in the 32-bit immediate encoding paths
- CE's only line of defense is the "Invalid64BitValueFor32BitField" dialog (Assemblerunit.pas:8463), but it only asks once per session (dialog text: "This is the only time asked and will be remembered until you restart CE") — once the user confirms, every truncation in that session is silent;
- The check only fires when the operand was classified as a 64-bit value AND the instruction carries REX.W (8 call sites). The PUSH immediate path has no call site at all — the opcode table only lists imm8/imm32 encodings for PUSH (6a/6 , so a large immediate is written out as its low 32 bits directly;
- More subtle: the opcode table has no "par_rm64" entry at all (x86-64's "mov r/m64, imm32" is not modeled), so "mov qword ptr [mem],imm" falls straight into the rm32 branch — the store width is silently downgraded from 8 bytes to 4 bytes without triggering any check.
Bug 2: the label patch re-assembly only replaces one name at a time
- CEAA resolves script labels by pure text replacement: in the main loop, defined labels are replaced with their real address, while forward-referenced labels are replaced with a placeholder address and recorded in that label's reference list (autoassembler.pas:3597-3774);
- When a label is finally defined, every referencing line gets a patch re-assembly — but this step takes the ORIGINAL line and only replaces that one label's name (autoassembler.pas:3803-3814):
| Code: | | lea eax,[eax-get_eip+val] -> lea eax,[eax-get_eip+<val address>] |
...leaving "get_eip" as a raw name;
- In the low-level assembler, "setmodrm" treats any segment that isn't hexadecimal as a register (Assemblerunit.pas:4206); since that segment carries a '-' prefix, it throws the unrelated "Negative registers can not be encoded" (:4209);
- Side issue: the error is reported at the "val:" line where the patch happens, not at the "lea" line that is actually wrong — which misleads debugging even further.
Root cause
CE's label mechanism is "text replacement + one label at a time". It cannot express arithmetic between multiple labels on the same line (e.g. "val-get_eip"), and the low-level assembler does not know script labels. The two layers are disconnected, which makes multi-label expressions a dead end. Suggested fix: perform a full-line label replacement before the patch re-assembly, or register script labels into the low-level symbol table.
|
|