The problem is that in the inner loop you add the indices i+j to access s1. If you imagine i to point to the "o" in "dog" in your example, j goes from 0 to 5 (length of "hello") in the inner loop. This causes your access to s1[i+j] to look at the characters o, g, \0, garbage, garbage.
The benefit of C strings is that they are null terminated. So you can iterate over strings like
for (char* i = s1; *i != 0; i++) {
...
}
I.e. you iterate from the start of s1 until you find its terminating 0 byte. In your inner loop, this allows you to write the following:
const char *j, *k;
for (j = s2, k = i; *j == *k && *j != 0; j++, k++);
if (*j == 0)
return i;
I.e. j starts at the beginning of s2, k starts where i is currently pointing at inside s1. You iterate as long as both strings are equal, and they have not reached their terminating 0 byte. If you have indeed reached the 0 byte of s2 (*j == 0), you have found the substring.
Note that you probably want to return i instead of s1, since this gives you a pointer into s1 where the requested substring starts.
'\0'on each iteration ... bad idea"hello"in that sentence?? I don't see it in there at all!