Why `strlen` isn't included in elf's symbol table? - c

I'm compile 1.c with debug info:
gcc -O0 -g ./1.c
// 1.c
#include <stdio.h>
#include <string.h>
int main() {
char c[222];
scanf("%s", c);
int a = strlen("dadw");
return 0;
}
But I can't find strlen through readelf -a a.out or nm ./a.out. In my understanding, strlen is provided by libc.so and resolved by dynamic linking. So it should appear in the symbol table. Is it caused by some special tricks of gcc?
❯ nm ./a.out
000000000000038c r __abi_tag
0000000000004010 B __bss_start
0000000000004010 b completed.0
w __cxa_finalize#GLIBC_2.2.5
0000000000004000 D __data_start
0000000000004000 W data_start
00000000000010b0 t deregister_tm_clones
0000000000001120 t __do_global_dtors_aux
0000000000003db8 d __do_global_dtors_aux_fini_array_entry
0000000000004008 D __dso_handle
0000000000003dc0 d _DYNAMIC
0000000000004010 D _edata
0000000000004018 B _end
00000000000011cc T _fini
0000000000001160 t frame_dummy
0000000000003db0 d __frame_dummy_init_array_entry
00000000000020e8 r __FRAME_END__
0000000000003fb0 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000002008 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_used
U __isoc99_scanf#GLIBC_2.7
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main#GLIBC_2.34
0000000000001169 T main
00000000000010e0 t register_tm_clones
U __stack_chk_fail#GLIBC_2.4
0000000000001080 T _start
0000000000004010 D __TMC_END__

Why strlen isn't present in the symbol table
Because GCC replaces the call to strlen with a constant 4. x86-64 GCC 12.2 with the options -O0 -g produces the following assembly code. Notice the line mov DWORD PTR [rbp-4], 4, which is the constant 4 for the length of the string.
.LC0:
.string "%s"
main:
push rbp
mov rbp, rsp
sub rsp, 240
lea rax, [rbp-240]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call __isoc99_scanf
mov DWORD PTR [rbp-4], 4
mov eax, 0
leave
ret
Try it on godbolt
How to make it actually call strlen
Without replacing the constant string "dadw" with a value read at runtime, one way to make GCC generate the call to strlen is to disable built-in functions by using the -fno-builtins option (link to documentation). According to the documentation:
GCC normally generates special code to handle certain built-in functions more efficiently; [...] the function calls no longer appear as such, [...]
With the options -O0 -g -fno-builtin, x86-64 GCC 12.2 produces the following assembly code, and strlen is present in the symbol table.
Notice the line call strlen.
.LC0:
.string "%s"
.LC1:
.string "dadw"
main:
push rbp
mov rbp, rsp
sub rsp, 240
lea rax, [rbp-240]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call __isoc99_scanf
mov edi, OFFSET FLAT:.LC1
call strlen
mov DWORD PTR [rbp-4], eax
mov eax, 0
leave
ret

Related

Return of syscall with wrong file descriptor is not negative [duplicate]

I'm having trouble finding the good documentation for writing 64-bit assembly on MacOS.
The 64-bit SysV ABI says the following in section A.2.1 and this SO post quotes it:
A system-call is done via the syscall instruction. The kernel destroys
registers %rcx and %r11.
Returning from the syscall, register %rax contains the result of the
system-call. A value in the range between -4095 and -1 indicates an error,
it is -errno.
Those two sentences are ok on Linux but are wrong on macOS Sierra with the following code:
global _start
extern _exit
section .text
_start:
; Align stack to 16 bytes for libc
and rsp, 0xFFFFFFFFFFFFFFF0
; Call write
mov rdx, 12 ; size
mov rsi, hello ; buf
mov edi, 1 ; fd
mov rax, 0x2000004 ; write ; replace to mov rax, 0x1 on linux
syscall
jc .err ; Jumps on error on macOS, but why?
jnc .ok
.err:
mov rdi, -1
call _exit ; exit(-1)
.ok:
; Expect rdx to be 12, but it isn't on macOS!
mov rdi, rdx
call _exit ; exit(rdx)
; String for write
section .data
hello:
.str db `Hello world\n`
.len equ $-hello.str
Compile with NASM:
; MacOS: nasm -f macho64 syscall.asm && ld syscall.o -lc -macosx_version_min 10.12 -e _start -o syscall
; Linux: nasm -f elf64 syscall.asm -o syscall.o && ld syscall.o -lc -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o syscall
Run on macOS:
./syscall # Return value 0
./syscall >&- # Return value 255 (-1)
I found out that:
A syscall return errno an sets the carry flag on error, instead of returning -errno in rax
rdx register is clobbered by syscall
On Linux, everything works as expected
Why is rdx clobbered? Why doesn't a syscall return -errno? Where can I find the real documentation?
The only place I found where someone talks about the carry flag for syscall errors is here
I used this:
# as hello.asm -o hello.o
# ld hello.o -macosx_version_min 10.13 -e _main -o hello -lSystem
.section __DATA,__data
str:
.asciz "Hello world!\n"
.section __TEXT,__text
.globl _main
_main:
movl $0x2000004, %eax # preparing system call 4
movl $1, %edi # STDOUT file descriptor is 1
movq str#GOTPCREL(%rip), %rsi # The value to print
movq $13, %rdx # the size of the value to print
syscall
movl %eax, %edi
movl $0x2000001, %eax # exit (return value of the call to write())
syscall
and was able to catch return value into eax. Here return value is the number of bytes actually written by write system call. And yes MacOS being a BSD variant it is the carry flag that tells you if the syscall was wrong or not (errno is just an external linkage variable).
# hello_asm.s
# as hello_asm.s -o hello_asm.o
# ld hello_asm.o -e _main -o hello_asm
.section __DATA,__data
str:
.asciz "Hello world!\n"
good:
.asciz "OK\n"
.section __TEXT,__text
.globl _main
_main:
movl $0x2000004, %eax # preparing system call 4
movl $5, %edi # STDOUT file descriptor is 5
movq str#GOTPCREL(%rip), %rsi # The value to print
movq $13, %rdx # the size of the value to print
syscall
jc err
movl $0x2000004, %eax # preparing system call 4
movl $1, %edi # STDOUT file descriptor is 1
movq good#GOTPCREL(%rip), %rsi # The value to print
movq $3, %rdx # the size of the value to print
syscall
movl $0, %edi
movl $0x2000001, %eax # exit 0
syscall
err:
movl $1, %edi
movl $0x2000001, %eax # exit 1
syscall
This will exits with error code one because descriptor 5 was used, if you try descriptor 1 then it will work printing another message and exiting with 0.
I don't know why rdx gets clobbered, just to confirm that it indeed does seem to get zeroed across the "write" systemcall. I examined the status of every register:
global _start
section .text
_start:
mov rax, 0xDEADBEEF; 0xDEADBEEF = 3735928559; 3735928559 mod 256 = 239
mov rbx, 0xDEADBEEF
mov rcx, 0xDEADBEEF
mov rdx, 0xDEADBEEF
mov rsi, 0xDEADBEEF
mov rdi, 0xDEADBEEF
mov rsp, 0xDEADBEEF
mov rbp, 0xDEADBEEF
mov r8, 0xDEADBEEF
mov r9, 0xDEADBEEF
mov r10, 0xDEADBEEF
mov r11, 0xDEADBEEF
mov r12, 0xDEADBEEF
mov r13, 0xDEADBEEF
mov r14, 0xDEADBEEF
mov r15, 0xDEADBEEF
mov rdx, len2 ; size
mov rsi, msg2 ; buf
mov rdi, 1 ; fd
mov rax, 0x2000004 ; write
syscall
mov rdi, rsi ; CHANGE THIS TO EXAMINE DIFFERENT REGISTERS
mov rax, 0x2000001 ; exit
syscall
section .data
msg_pad db `aaaa\n` ; to make the buffer not to be page-aligned
msg2 db `bbbbbb\n` ; because then it's easier to notice whether
len2 equ $-msg2 ; clobbered or not
nasm -f macho64 syscall.asm && ld syscall.o -e _start -static && ./a.out; echo "status: $?"
The results I got:
clobber list of a "write" syscall
rax clobbered
rbx not clobbered
rcx clobbered
rdx clobbered <- This is the unexpected case?!
rsi not clobbered
rdi not clobbered
rsp not clobbered
rbp not clobbered
r8 not clobbered
r9 not clobbered
r10 not clobbered
r11 clobbered
r12 not clobbered
r13 not clobbered
r14 not clobbered
r15 not clobbered
It would be interesting to know other syscalls zero rdx too, I didn't have the energy to attempt a thorough investigation. But maybe, just to be safe, one should add rdx to the clobber list of all of the MacOS syscalls from now on.

Again - can't change global variable in C

I'm newbie to C, and this question could be asked for 100 times, but I can't find the same case.
Here's the code:
int r;
void set_idt() {
r = 0x88;
if(r == 0){
kprint_hex_byte(0xff);
}
}
It's strange, but I can't modify the global variable and kprint_hex_byte gets executed. When I try to pick &r inside set_idt() I get the valid address, but assignment of the variable doesn't work!
UPDATE
I decided to provide a minimal example
Here's the minified code:
int r = 0x34def;
void main(){
r = 0x33;
char d = 0;
if(r == 0){
d = 1;
}
else{
d = 2;
}
}
I try to compile it and disassemble it using the following commands:
gcc -ffreestanding -fno-asynchronous-unwind-tables -fno-pie -m32 -c kernel.c -o kernel.o
ld -o kernel.bin -m elf_i386 --oformat binary kernel.o
ndisasm -b 32 kernel.bin > kernel.dis
So as a result of this operations I don't have any hint that any variable r or address space is initialized as 0x34def.
Here's the .dis code:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 83EC10 sub esp,byte +0x10
00000006 C7052C9004083300 mov dword [dword 0x804902c],0x33
-0000
00000010 C645FF00 mov byte [ebp-0x1],0x0
00000014 A12C900408 mov eax,[0x804902c]
00000019 85C0 test eax,eax
0000001B 7506 jnz 0x23
0000001D C645FF01 mov byte [ebp-0x1],0x1
00000021 EB04 jmp short 0x27
00000023 C645FF02 mov byte [ebp-0x1],0x2
00000027 90 nop
00000028 C9 leave
00000029 C3 ret
PS
In the original example when I tried to disassemble it, I also had some assembler instructions dealing with expected memory address and commands test and jnz were presented the exact same way, but it didn't work as expected

X86 Assembly Problems

I'm attempting to get through a book on X86 that was written using examples from Visual C++ and Visual Studio. I'm trying to convert the examples for use with gcc. After a number of problems, I finally wound up with code that would at least compile, but now I'm getting segfaults. Here's the code:
assembly.s:
.intel_syntax noprefix
.section .text
.globl CalcSum
.type CalcSum, #function
// extern "C" int CalcSum_(int a, int b, int c)
CalcSum:
// Initialize a stack frame pointer
pushq rbp
mov ebp,esp
// Load the argument values
mov eax,[ebp+8]
mov ecx,[ebp+12]
mov edx,[ebp+16]
// Calculate the sum
add eax, ecx
add eax, edx
// Restore the caller's stack frame pointer
popq rbp
ret
test.c:
#include <stdio.h>
extern int CalcSum(int a, int b, int c);
int main() {
int sum = CalcSum(5,6,7);
printf(" result: %d\n",sum);
return 0;
}
I'm using gcc -o execute test.c assembly.s to compile. If I change all the 32 bit instructions to 64 bit (i.e. ebp to rbp) it will run but give completely random output. Could anyone point out what I'm doing wrong here? Thanks!
As hinted in the comments, it's a matter of calling convention. 32-bit C functions follow the CDECL calling convention in Windows and in Linux. In 64-bit Linux you have to use the System V AMD64 ABI. The 64-bit calling convention of Windows is different. There might be specifics to use functions of the operating system.
32-bit C (GCC):
.intel_syntax noprefix
.section .text
.globl CalcSum
.type CalcSum, #function
// extern "C" int CalcSum_(int a, int b, int c)
CalcSum: // with underscore in Windows: _CalcSum
// Initialize a stack frame pointer
push ebp
mov ebp,esp
// Load the argument values
mov eax,[ebp+8]
mov ecx,[ebp+12]
mov edx,[ebp+16]
// Calculate the sum
add eax, ecx
add eax, edx
// Restore the caller's stack frame pointer
pop ebp
ret
64-bit Linux (GCC):
.intel_syntax noprefix
.section .text
.globl CalcSum
.type CalcSum, #function
// extern "C" int CalcSum_(int a, int b, int c)
CalcSum:
// Load the argument values
mov rax, rdi
add rax, rsi
add rax, rdx
ret
64-bit Windows (MingW-GCC):
.intel_syntax noprefix
.section .text
.globl CalcSum
// .type CalcSum, #function
// extern "C" int CalcSum_(int a, int b, int c)
CalcSum:
// Load the argument values
mov rax, rcx
add rax, rdx
add rax, r8
ret

making C wrapper from asm of nasm

This was a C program in the beginning then converted to nasm
but no luck trying to put into shellcode format. (segment fault)
global main
extern printf
SECTION .text align=4
main:
push rbp
mov rbp, rsp
mov eax, L_001
mov rdi, rax
mov eax, 0
call printf
mov eax, 0
leave
ret
SECTION .data align=4
SECTION .bss align=4
SECTION .rodata
L_001:
db 48H, 65H, 6CH, 6CH, 6FH, 20H, 74H, 68H
db 65H, 72H, 65H, 00H
This was written on a x86_64 platform of CentOS
C wrapper program(shellcode format)
char code[] = "\x48\x65\x6c\x6c\x6f\x20\x74\x68\x65\x72\x65\x00";
int main(int argc, char **argv)
{
(*(void(*)())code)();
return 0;
}

Understanding GCC inline assembly with a Hello World program

A friend helped me come up with the following code to use inline assembly in GCC on a 64-bit Windows machine:
int main() {
char* str = "Hello World";
int ret;
asm volatile(
"call puts"
: "=a" (ret), "+c" (str)
:
: "rdx", "rdi", "rsi", "r8", "r9", "r10", "r11");
return 0;
}
After compiling with -S -masm=intel (I prefer Intel syntax), I get this assembly code:
.file "hello.c"
.intel_syntax noprefix
.def __main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.LC0:
.ascii "Hello World\0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
push rbp
.seh_pushreg rbp
push rdi
.seh_pushreg rdi
push rsi
.seh_pushreg rsi
mov rbp, rsp
.seh_setframe rbp, 0
sub rsp, 48
.seh_stackalloc 48
.seh_endprologue
call __main
lea rax, .LC0[rip]
mov QWORD PTR -8[rbp], rax
mov rax, QWORD PTR -8[rbp]
mov rcx, rax
/APP
# 7 "hello.c" 1
call puts
# 0 "" 2
/NO_APP
mov DWORD PTR -12[rbp], eax
mov QWORD PTR -8[rbp], rcx
mov eax, 0
add rsp, 48
pop rsi
pop rdi
pop rbp
ret
.seh_endproc
.ident "GCC: (x86_64-posix-seh-rev1, Built by MinGW-W64 project) 4.9.2"
It works, but it sure looks messy with what appears to be superfluous code. Then again, my last experience with assembly was with the 65816 back in the 80s, and it wasn't inline. Anyway, I cleaned up the code, and the following accomplishes the exact same thing, as far as I can tell:
.intel_syntax noprefix
.data:
.ascii "Hello World\0"
.text
.globl main
main:
sub rsp, 48
lea rax, .data[rip]
mov rcx, rax
call puts
mov eax, 0
add rsp, 48
ret
Much simpler. What's all that extra stuff GCC added?
Edit: Not a duplicate because in addition to the structured exception handling, I'm also asking about the callee-saved registers, the call to __main, the explicit size directives, and the APP/NO_APP section.

Resources