C code, rewritten to use a Person struct without pointers
This is an interesting problem. Removal of pointers can improve the safety of code. It's much more difficult in C than something like C++, though, as C doesn't have many other options like references or smart pointers. Regardless, here are some thoughts on your code.
Use Your typedefs
You've made a type definition for struct person named Who. But you only appear to be using it two of the five places where you need them. Many C programmers dislike using typedef with a struct. I am not one of those programmers. I think they make the code much nicer and easier to understand. You've made the typedef, so you should use it everywhere. Person_create() should return a Who not a struct person. Also in main() you should be creating 2 Whos, not 2 struct persons.
Removing pointers
You've said the goal was to remove pointers from your struct, but you're still using one for the name field. Why? You could make it a fixed-length array if you truly want to remove all use of pointers. You'll need to ensure that you don't copy anything too long into it, in that case, though. You could define a constant for the maximum length of a name in that case. Something like this:
const size_t kMaxNameBytes = 32;    // Maximum number of bytes including NUL terminator

struct person {
    char name [ kMaxNameBytes ];
    int age;
    int height;
    int weight;
};

typedef struct person Who;

Now you have a struct that has no pointers. It is bigger, however. The disadvantage with having no pointers is that every struct must carry all of its data within the struct leading to much larger pieces of data on the stack. If you want an array of thousands of persons, it may be too large to fit on the stack.
As mentioned above, you'll also need to be more careful with your copies of the name field, now, too. Instead of just calling w.name = strdup(name);, you'll have to do something like this:
size_t name_len = MIN(strlen(name), kMaxNameBytes - 1);
memcpy(w.name, name, name_len);
w.name [ name_len + 1 ] = '\0';

Note all the extra work you now have to do to ensure that the string doesn't go over the maximum length and that it is also properly NUL terminated. On the other hand, copying a whole person is easier because you don't have to manually copy the name string like you do in your version. You can simply memcpy() the bytes from one struct to the other because it's self-contained.
Handling Failure
Note that your code doesn't handle the failure of strdup(). If strdup() can't allocate the memory, it will return NULL. Your code doesn't check the return value and will happily set the name to NULL and keep going. This is likely to cause a memory error somewhere else in your program.