Breaking it down line by line:
int **p = (int **)malloc(2 * sizeof(int *)); 
After this line executes, you have the following:
   +---+                +---+
p: |   | --------> [0]: |   | ---?
   +---+                +---+
                   [1]: |   | ---?
                        +---+
? indicates that p[0] and p[1] contain indeterminate pointer values (malloc does not initialize the memory it allocates); these pointer value are most likely not valid (i.e., do not correspond to the address of an object in your program).  
p[0] = (int *)malloc(2 * sizeof(int));
After this line executes, you have the following:
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ------> [0]: | ? |
   +---+                +---+              +---+
                   [1]: |   | ---?    [1]: | ? |
                        +---+              +---+
Again, ? represents an indeterminate value.  
p[1] = p[0]; 
After this line executes, you have the following:
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ---+--> [0]: | ? |
   +---+                +---+    |         +---+
                   [1]: |   | ---+    [1]: | ? |
                        +---+              +---+
Now, p[0] and p[1] are pointing to the same block of memory.  Thus, p[0][0] and p[1][0] resolve to the same object.  If we unroll your loop, we see the following sequence of assignments:
p[0][0] = 0 + 0;
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ---+--> [0]: | 0 |
   +---+                +---+    |         +---+
                   [1]: |   | ---+    [1]: | ? |
                        +---+              +---+
p[0][1] = 0 + 1;
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ---+--> [0]: | 0 |
   +---+                +---+    |         +---+
                   [1]: |   | ---+    [1]: | 1 |
                        +---+              +---+
p[1][0] = 1 + 0;
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ---+--> [0]: | 1 |
   +---+                +---+    |         +---+
                   [1]: |   | ---+    [1]: | 1 |
                        +---+              +---+
p[1][1] = 1 + 1;
   +---+                +---+              +---+
p: |   | --------> [0]: |   | ---+--> [0]: | 1 |
   +---+                +---+    |         +---+
                   [1]: |   | ---+    [1]: | 2 |
                        +---+              +---+
This is why you're seeing the ouput you're seeing.  The fact that p[0] and p[1] show the same value in your output should have been a big hint.
If p[1] is meant to point to a different array from p[0], then you'll need to allocate another block of memory for p[1] to point to:
p[1] = malloc( 2 * sizeof *p[1] );
As a matter of practice, you'll want to explicitly free everything you allocate, even if your program is about to exit - it's just a good habit to get into:
free( p[0] ); // free each p[i] *before* freeing p
free( p[1] ); // assuming you added the malloc for p[1]
free( p );
If this is meant to be C code, lose the cast on the malloc calls - it's unnecessary, it just adds visual clutter, and under C89 could mask a bug if you forgot to include stdlib.h.  A much better way to write those calls is
int **p = malloc( 2 * sizeof *p );
and
p[0] = malloc( 2 * sizeof *p[0] );
If this is meant to be C++ code, use new and delete instead of malloc and free.