6

I'm disassembling a packed 16 bit DOS MZ EXE.

To deobfuscate it, I've set a breakpoint in DOSbox at the end of the unpacking routine, let it run, and made a memory dump. This way I essentially got the deobfuscated EXE image.

Problems started when I loaded the image in IDA. You see, I don't understand the IDA's concept of segments. They are similar to x86 segments, but there are numerous differences which I can't grasp. When IDA asked me to create at least one segment, I just made a single huge segment 1 MB length, because the code and data in program's address space are mixed and it doesn't make sense to introduce separate segments such as CODE, DATA etc.

After showing IDA the entry point, everything worked fine: IDA successfully determined functions, local variables, arguments etc. The only problem is that some calls are marked as NONAME, even though they point at correct subroutines. The strangest thing is that those subroutines have correct XREFs to the 'illegal' calls. Here's an example:

seg000:188FF 004                 call    1AD9h:1         ; Call Procedure

This line is red and has an associated NONAME problem in Problems List. Why?

The 1AD9h:1 seg:offset address corresponds to linear address 0x1ad91, which has this:

seg000:1AD91     ; =============== S U B R O U T I N E =======================================
seg000:1AD91
seg000:1AD91     ; Attributes: bp-based frame
seg000:1AD91
seg000:1AD91     sub_1AD91       proc far                ; CODE XREF: sub_188F2+DP

Note the XREF. So IDA actually processes the call correctly! Why is the call considered invalid? IDA help file says this:

Problem: Can not find name

Description

Two reasons can cause this problem:

  1. Reference to an illegal address is made in the program being disassembled;
  2. IDA couldn't find a name for the address but it must exist.

What to do

  1. If this problem is caused by a reference to an illegal address

    • Try to enter the operand manually
    • Or make the illegal address legal by creating a new segment.
  2. Otherwise, the database is corrupt.

So, I guess the problem is that I have one gargantuan segment instead of several small ones. But, how do I properly divide the address space into appropriate segments?

I know the register values (including DS, CS, SS, IP, etc) at the entry point. Let's assume I create a CODE segment starting from the segment corresponding to the CS register value at the entry point. But what length should this segment have ?

What's the point of segments in IDA at all? If DATA segments can contain instructions, and CODE segments can be read and written as data?

Please excuse me for such a newbie question, but official IDA manual is notoriously scarce and HexRays forums are closed for me because I use freeware version.

3 Answers 3

5
  1. Your program is using a segment with base 1AD9h (the segment part of the far call). You need to create a new segment which matches it.

    Start = 0x1AD90   (0x1AD9<<4)
    End = 0x2AD90  [for example] (start + 64KB - maximum size)
    Base = 0x1AD9
    (o) 16-bit
    
  2. Now, go through the new segment and make sure everything makes sense. Trim the segment (reduce end address) if necessary.

  3. Find another far jump/call with a different segment value. Repeat step 1 for the new base.

  4. Do the same with data segments (look for values loaded into ds/es/ss).

2
  • So you are implying that for each far call, IDA should have a segment whose base equals the segment part of the call? But x86 segments can overlap, and IDA segments can not. What if after I create a segment starting at 0x1AD90 I'll stumble upon a far call to segment 1ADAh? Should I shrink the first segment to 16 bytes in order to be able to create a new segment starting at 0x1ADA0? Looks like I'll end up with loads of tiny segments. I doubt that's the intended way to go.
    – ScumCoder
    Aug 13, 2014 at 18:42
  • Real programs rarely use overlapping segments.
    – Igor Skochinsky
    Aug 13, 2014 at 18:50
4

I dealt with a ROM image once and faced this problem. I was confused too about what to do until Igor offered his advice.

What seemed to be happening was that the linker was placing every object file into its own segment, so every inter-object function invocation was rendered in the binary as a far call, where the segment base was the base given to all functions within the module. I.e., the case you mention in your reply to Igor's comment did not materialize for me.

To fix it, I searched the binary for all far call instructions and then created a new IDA segment (as large as possible) at the linear address of each referenced x86 segment. I.e., I did indeed end up with lots of tiny segments. This is not really a problem; really, the problem is that by not doing this, the references won't be disassembled correctly. It was pretty quick work and probably could be automated with a script.

2
  • That's right. But there can be a program which calls one and the same function with two different segment values. E.g., the func starts at address 0x12345, and is called from one place as call far 1233h:0015h and from other as call far 1234h:0005h. It may be 'rarely used in real programs', but it is physically possible, and in that case one won't be able to set the func's segment in such way that both calls won't have NONAME problem. It's just strange that such powerful tool as IDA can have problems in such situation, no matter how purely theoretical it is.
    – ScumCoder
    Aug 13, 2014 at 20:31
  • 1
    Yes, it certainly could happen: for obfuscation, or just some assembly coder being clever. Regarding IDA and its limitations, it's my favorite software of all time, along with SoftICE, but many architectural decisions have been made during its long development that can trip you up in your reversing endeavors. Such things used to frustrate and annoy me, until I had a job as a software developer, and now I understand. Hex-Rays makes amazing products considering their limited resources. You can do almost all of what you want, the support is great, and the SDK/IDAPython is good. Make lemonade. Aug 13, 2014 at 20:53
2

The problem behind this is that each segment addresses a maximum of 64KB, and, to generate meaningful assembly, IDA needs to know what the segment registers are supposed to be when code is executed.

Assume you have the following code at linear address 0x23456:

mov bx, 6789
call [bx]

Which function does this call? Well, if your CS register has 0x2000, and your IP is 0x3456, then this calls 2000:6789 or (linear) 0x26789. But, just as well, you could have 0x2345 in CS and 0x0006 in IP. In which case 2345:6789 or (0x23450+0x6789=)0x29BD9 gets called.

There are cases when jump/call targets aren't that ambiguous, for example with absolute far calls, like your call 1AD9:1, or with relative jumps (This is why i used the indirect [bx] call; call 6789 would use an assembly instruction that is relative to IP, so independent of the segment).

Still, offsets don't make sense if you don't know which segment they belong to. If you have code like this

mov ax, 1234
push ax
pop es
mov bx, es:[abcd]
mov ax, 5678
push ax
pop es
mov dx, es:[cdef]

you want a variable definition (for bx) at 1234:abcd, and another one (for dx) at 5678:cdef. Which means IDA must know that one segment starts at 1234 to put the first variable in, and another one starts at 5678 for the second variable. (I used the push/pop because, as far as i remember, there was no processor opcode to load a segment register directly, and i think there were some restrictions with moving them as well, so the push/pop was used heavily to load them).

Of course, the point of segments is that they are a bad idea and caused lots of trouble, but Intel wanted to be able to address more than 64K with a 16 bit processor, so they invented them. Which means they exist, and we need to get them right when disassembling 16-bit programs. Whether or not we like them isn't the question.

The best you can do is find as many references to segments as you can - initial CS/DS/ES/SS values, far calls to some cs:ip location, and values that get loaded into segment registers. Then, write down which segment values occur, assume each of them is large enough to accomodate all space to the next one, and feed this list to IDA.

1
  • Thanks, but I know about the x86 segmentation system. Looks like it has nothing to do with IDA segments. (1) the code I'm disassembling has virtually no near jumps, only short and far ones which don't need proper segments. (2) the call 1AD9h:1 instruction I mentioned is a far jump (opcode 9a 01 00 d9 1a), and it's still marked as invalid. (3) I still don't understand why IDA managed to generate proper XREF to this 'invalid' call. (4) AFAIK you control the values of segment registers not by Program Segmentation view but by Segment Registers view, which seems to be a different thing.
    – ScumCoder
    Aug 13, 2014 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.