Legacy applications can be difficult and time-consuming to update in response to changing business requirements. These applications can be hard to understand due to the lack of modern abstraction mechanisms in legacy languages as well as the gradual deterioration of code due to repeated maintenance activities. This paper presents an algorithm for reverse engineering object-oriented data models from programs written in weakly-typed languages like Cobol. These models, similar to UML class diagrams, can facilitate a variety of program maintenance activities, as well as assist in migrating applications to modern object-oriented languages. Our algorithm is based on a semantic analysis of the program's code. Our second contribution is a bisimulation-based formalization of what it means for a model to be correct for a program. The models recovered by our algorithm are guaranteed to be correct according to this characterization. Keywords: object-oriented model, reverse engineering, logical data model, program understanding