I've been plotting BER curves and am wondering why FEC seems to make a BER curve worse at low Eb/N0 than the un-coded theoretical BER curve?
This is a behaviour that is commonly outlined in some textbooks and tutorials on FEC, but usually in the form of an observation. For instance, Turbo Codes are sometimes characterised by their Threshold $E_b/N_o$, which can be considered as the minimum SNR per bit from where the BER starts to decrease. I have not seen a formal explanation for this property, but it can be deduced at least for some types of codes I assume. In this Wikipedia article, for instance, it is explained as a consequence of the risk-polling effect that comes from the noise averaging capability of FEC. Let me complement this, also by an intuitive approach.
Consider for instance the use of a $(n,k)$ binary block code with hard decision decoding. The maximum number of correctable bit errors for this code is commonly denoted by $t$. If the number of bit errors within each transmitted block is higher than $t$, then the receiver will not be able to recover the original codeword. Moreover, the received block will be at roughly the same distance of multiple codewords within the set of size $2^k$. The decoder then picks one of those codewords with high uncertainty, which results in a BER close to 0.5.
Simply put, a few bit errors - though above the error correction capability $t$ of the code - may result in a complete block error, which explains why FEC underperforms the uncoded system for a certain SNR range.
This is why in systems that operate at low SNR or when errors appear in bursts, it is a good idea to use interleaving in order to spread the bit errors along several blocks and keep the number of bit errors per block below $t$.
Convolutional codes spread protection across a relatively large number of bits - when the decoder makes a mistake it impacts bits over that larger range. Convolutional decoding is not infallible, especially at low Eb/No (i.e. low SNR), their performance is thus worse than uncoded data for low ranges (where mistakes are limited only to the immediate bits being considered). In addition, when coding, energy is being used for the parity bits as well as the data bits (so for 1/2 rate coding on average less power is being used for the data). The coding gain has to compensate for this - at relatively low SNR's the coding gain is not sufficient to compensate for the data power reduction, hence more errors - at high SNR's it is more than able to, hence less errors.