The key step you're going to have to make is to think inductively. Your current solution:
and :: [Bool] -> Bool
and [] = True
and [True, True] = True
and [False, False] = True
and [True, False] = False
enumerates some of the possibilities, but it obviously doesn't work for all lists. So how to write one that will work for lists of any length?
In Haskell, you can usually write your functions by taking apart a data type. In this case, lists. Lists are defined as:
data [a] = []
| a : [a]
So, lists have two cases: either the empty list, or a one element with a tail. Let's start writing your and function then, so that it matches those two cases for lists:
and [] = True
and (a:as) = ...
So, the "base case", the empty list, is True. But what should we do for the case of a list with one element, and some tail?
Well, we already have the && function in Haskell:
> True && True
True
> True && False
False
> False && True
False
> False && False
False
Interesting! So, && takes two arguments, and correctly determines if both arguments are True. And we are currently have a list with one element, and a list for a tail. And at the same time, we're defining the and function, that results in a single boolean value when applied to a list.
So we can make the inductive step and use the function we're defining, and, together with the && binary operator, to finish our definition:
and [] = True
and (a:as) = a && (and as)
So we evaluate the tail of the list to some value (recursively), and use the && operator to combined that value with the current value, and that's our function written.
Recursion and induction, driven by the recursive structure of the lists, are the key technique to learn in programming. If you can write this, you're making a big step forward.