slimm609/checksec.sh

RELRO Checking is incorrect for crafted ELF file

ZhangZhuoSJTU opened this issue · 4 comments

Issue

RELRO checking in checksec is incorrect for some crafted ELF file.

Debug Report

checksec may mis-identify an ELF without RELRO as FULL RELRO.

I have attached a crafted ELF file to trigger this bug, who has a crafted dynamic section entry with following value.

d_tag = DT_FLAGS,
d_un.d_val = DF_BIND_NOW,

It seems that checksec only checks some features of RELRO binary compiled by standard compilers, instead of checking whether the GOT table will be actually set as NO-WRITE.

Output of checksec:

$ ./checksec --file=easiest_patch
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Full RELRO      No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols	  No	0		1		easiest_patch

Directly check the run-time permission:

gdb-peda$ p puts
$1 = {<text variable, no debug info>} 0x400480 <puts@plt>

gdb-peda$ nearpc 0x400480
   0x400471:	xor    eax,0x200b92
   0x400476:	jmp    QWORD PTR [rip+0x200b94]        # 0x601010
   0x40047c:	nop    DWORD PTR [rax+0x0]
   0x400480 <puts@plt>:	jmp    QWORD PTR [rip+0x200b92]        # 0x601018
   0x400486 <puts@plt+6>:	push   0x0
   0x40048b <puts@plt+11>:	jmp    0x400470
   0x400490 <quick_exit@plt>:	jmp    QWORD PTR [rip+0x200b8a]        # 0x601020
   0x400496 <quick_exit@plt+6>:	push   0x1

gdb-peda$ telescope 0x601018
0000| 0x601018 --> 0x7ffff7a64a30 (push   r13)
0008| 0x601020 --> 0x7ffff7a27810 (lea    rsi,[rip+0x3a7f09]        # 0x7ffff7dcf720)
0016| 0x601028 --> 0x7ffff7af4180 (lea    rax,[rip+0x2e0771]        # 0x7ffff7dd48f8)
0024| 0x601030 --> 0x0
0032| 0x601038 --> 0x0
0040| 0x601040 --> 0x0
0048| 0x601048 --> 0x0
0056| 0x601050 --> 0x0

gdb-peda$ vmmap 0x601018
Start              End                Perm	Name
0x00601000         0x00602000         rw-p	/u/antor/u28/zhan3299/trojai/ZeroPatch/workshop/easiest_patch

It is easy to check that the GOT of puts function is writable, but checksec reports is as FULL RELRO

easiest_patch.zip

sha256 of attached ELF: 7e5a5806d899fb00226487237b2a3a066cad8653be31d5210747e0beaf2cc1cc

pwntool and gdb-peda have similar issue.

It seems that checksec only checks some features of RELRO binary compiled by standard compilers, instead of checking whether the GOT table will be actually set as NO-WRITE.

As a side note, the overall issue with checksec.sh project in particular is that it just greps through various readelf results and so it is very easy to fool it. As an example, if you compile this code without the stack protector, checksec.sh will still say the binary has stack canary:

void lol__stack_chk_fail() {}
int main() {
    lol__stack_chk_fail();
}

PS: Since you tested some other tools, the checksec.rs also does misreport this case.

As a side note, the overall issue with checksec.sh project in particular is that it just greps through various readelf results and so it is very easy to fool it.

Is it in scope of checksec to take in account some troll code? I think people use it to inspect legitimate binaries. Checking spoofed binaries for hardening doesn't have much purpose as nobody will use them anyway. Also I don't see why someone would put troll code into legitimate binary only to confuse checksec. If they have bad intentions and put something malicious in code you run then it doesn't matter if it has rerlro or not.

Is it in scope of checksec to take in account some troll code?

Rather not, but checksec is often used for random binaries and to make statements of platform security.

Also I don't see why someone would put troll code into legitimate binary only to confuse checksec. If they have bad intentions and put something malicious in code you run then it doesn't matter if it has rerlro or not.

In theory, one could slip in a backdoor this way 🤷.

Both of these should be solved in the main branch now.

For the crafted elf, the virtual address does not resolve to an actual address in the binary so I added a search to get the address and then validate that it's a real address.

For the stack_chk_fail, we can do similar and look for the lack of an address, from looking at several binaries, it will always be all 0s and UND for the ndx where if you declare a function, it will have an address that won't be all 0's