Short answer: the same way any other compiler works.
Long answer: A program that takes programming code input in one language and transforms it to output in a different language is called a compiler. (An assembler is a special type of compiler whose input language is assembly language and whose output language is machine code.)
A compiler's work can be broadly described as consisting of three phases: parsing (reading the code and building an internal data structure describing what it says), semantic analysis (making sure the parsed data is actually valid and determining what it means), and code generation (turning the analyzed data into the output language.)
For an assembler, the semantic analysis phase would probably involve looking up each instruction name in a table, then calling a method with that instruction's arguments to figure out if it matches any of the sets of arguments that are valid for that instruction. This determines what hex code it will use. (For example, the hex opcode for MOV (register, register)
is different from the hex opcode for MOV (register, memory address)
even though the assembly mnemonic is the same.)
Once your parse data has been annotated with semantic data, your code generator can go over it and read the hex opcodes and encode it into machine code output according to the rules of the processor's machine language, in the form of a stream of numbers.
There is no software that "turns it into 1s and 0s." Software operates at a higher abstraction level; breaking down the larger numbers it works with into bits happens at the hardware level, where it's actually implemented as "higher voltage" and "lower voltage". The numbers 1 and 0 are just for our convenience, but don't have much actual meaning (at any level) these days.