October 01, 2017

pwnable.kr unlink solution


Daddy! how can I exploit unlink corruption?





This is a game that reads the "flag" with "unlink_pwn" group permission.

When it is run, I can type something.




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;

void shell(){
system("/bin/sh");
}

void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));

// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;

printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);

// exploit this unlink!
unlink(B);
return 0;
}


This is the full source code.

There is struct variables A, B and C that is linked each other. and unlink() unlinks them.

The hint "exploit this unlink" is in the source code. "gets(A->buf)" seems to be able to cause a heap overflow exploiting "unlink(B)".





I identified the addresses of major functions before analysis.





"[ebp-0x4]" value affects "esp", which means that it affects the return value of the main() function.

Eventually, if [ebp-0x4] is manipulated, the main() function can go to the shell() function on return.





"OBJ" variable is consisted of two pointers(4 Bytes * 2) and eight characters(1 Bytes * 8). Total is 16 bytes.

And before the return of main(), there is the "unlink(B)" function.

"unlink(B)" means this.
    [B->fd]->bk = B->bk
    [B->bk]->fd = B->fd

It may seem complicated, but it is easy to understand when memory is referred. I inserted the value and identified the structure.



The distance between "A" and "B" is 16. I checked in <+78> ~ <+115> of main() disassembled code.

Think of "->fd" as "+0x0" and think of "->bk" as "+0x4".

So "unlink(B)" means this.
    [B]+0x4 = B+0x4
    [B+0x4] = B





Based on the functionality of unlink(B), I thinked this kind of overflow.

"[ebp-0x4]" will have "A+0x8+0x4".
* [B+0x4] = B


And look at this image again.

The "ebp-0x4" pointing value is stored in "ecx" and the [ecx-0x4] address value is stored in "esp".

Therefore, "A+0x8+0x4" is stored in "ecx" and "A+0x8" is stored in "esp". and "ret" stores "shell()" address pointed to by "esp" to "eip".






For reference, the address of "B" and the address of "EBP-0x4" change continuously. Since "B" is in heap area, the address of B is checked by the distance from the allocated address of "A". And since EBP is in stack area, the address of "ebp-0x4" is checked by the distance from the variable address of "A".

In summary, the data to be transmitted is composed as follows.

"shell() address(4 Bytes)" + "dummy(12 Bytes)" + "A+0x8+0x4" + "ebp-0x4"

September 25, 2017

pwnable.kr asm solution



Mommy! I think I know how to make shellcodes.





There is a readme file on the pwnable.kr server. In conclusion, "asm" demands a 64 bit Linux shellcode.

Before entering a shellcode, I looked "asm.c".



<"asm.c" fullcode> 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
        if (ctx == NULL) {
                printf("seccomp error\n");
                exit(0);
        }

        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

        if (seccomp_load(ctx) < 0){
                seccomp_release(ctx);
                printf("seccomp error\n");
                exit(0);
        }
        seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

        setvbuf(stdout, 0, _IONBF, 0);
        setvbuf(stdin, 0, _IOLBF, 0);

        printf("Welcome to shellcoding practice challenge.\n");
        printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
        printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
        printf("If this does not challenge you. you should play 'asg' challenge :)\n");

        char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
        memset(sh, 0x90, 0x1000);
        memcpy(sh, stub, strlen(stub));

        int offset = sizeof(stub);
        printf("give me your x64 shellcode: ");
        read(0, sh+offset, 1000);

        alarm(10);
        chroot("/home/asm_pwn");        // you are in chroot jail. so you can't use symlink in /tmp
        sandbox();
        ((void (*)(void))sh)();
        return 0;
}

❑ Shell code : A small program that runs after a vulnerability attack.

❑ int setvbuf (FILE * stream, char * buf, int type, size_t size); : A function that changes the buffering way.
❍ int type : Setting type of buffering.
* _IONBF(Input Output with No BuFfering) : No buffering.
* _IOLBF(Input Output with Line BuFfering) : Passing data from buffer to device when newline characters(\n) appears.
* _IOFBF(Input Output with Full BuFfering) : Passing data from buffer to device when the set block size is exceeded.

❑ void * mmap (void * addr, size_t length, int prot, int flags, int fd, off_t offset) : Function to map physical address (as much as the length bytes from the offset) of device(fd) to virtual address(addr).
* On success, it returns a pointer to the mapped area. On failure, it returns -1.
❍ addr : The virtual address to map the physical space("0" assigns available virtual address).
❍ length : Size of space to map(Multiple of PAGE_SIZE)
❍ prot : Memory protection mode(e.g. 7 == Readable+Writeble+Executable)
* PROT_NONE(0), PROT_READ(1), PROT_WRITE(2), PROT_EXEC(4)
❍ flags : Property option.
* MAP_FIXED(Selecting only the specified address), MAP_SHARED(Sharing mappings with other processes)↔MAP_PRIVATE(No sharing mappings with other processes), MAP_ANONYMOUS(The fd argument is ignored and is initialized to zero)
❍ fd : File descriptor of the device.
❍ offset : Offset of physical address.

❑ void * memset ( void * addr, int value, size_t n ) : Function to fill memory from address addr as much as n bytes with the value argument.
* On success, it returns the addr pointer. On failure, it returns NULL.

unsigned int alarm(unsigned int n) : Function that terminates program after n seconds.

❑ int chroot(const char * path) :  Specifying a specific directory as the root directory("/").

❑ seccomp(Secure computing mode) : This is sandbox provided by the Linux kernel. It restricts the function of the opened file descriptor according to the specified rule.

❑ ((void (*)(void))sh)() : This is pointer-to-function type. This goes to the address "sh" and executes it with no argumnets and no return value.
* (((Expression)(Arguments))address)()


"sh" is allocated memory by mmap and filled with dummy values. Then, "stub" is entered. The shellcode that I made will be after "stub". Finally, "sh" is executed.





I disassembled the "stub". It cleans the registers by making it 0. No special other function.
* asm/disasm : Function of python's library pwntools.





This is my shellcode for reading the long named file. I used pwntools that is Python library. It just reads and writes.

❑ According to pwntools document, The file descriptor is stored in "rax" after using "open".

❑ According to pwntools document, The return value of "read()" is stored in "rsp".

❑ The value of "mySC" is assembly code and the value of "asm(mySC))" is hex code.

Since the asm() output is broken, I encoded it with enhex().





I submitted the shellcode decoding the encoded code by "enhex()" like above.





Or, it is possible to submit the unbroken asm() shellcode like above.

There is one caution here. The value of ["print '] and ['print "] is defferent. ['print "] makes the values that is intend, because it provides a code to Python without being interpreted internally.


September 12, 2017

pwnable.kr memcpy solution


Are you tired of hakcing? take some rest here.
Just help me out with my small experiment regarding memcpy performance.
after that, flag is yours.





The full code is here(Link). To summarize, after entering 10 numbers to allocate memory, it calls the slow_memcpy() and fast_memcpy() functions for each entered number to compare copy time. And at the end of the code, there is the flag string.

The problem is that it doesn't run to the end of the code because of an error. As the result of debug, the error is occured by "moventps" of the above image.

"moventps" requires operands aligned on 16 bytes. However, the address of "dest" may not have been aligned to 16 bytes, depending on the size entered. This means that the address of each operand should be 0x ~~ 10, 20, 30 etc.

□ gcc -o memcpy memcpy.c -m32 -lm
* -32 : Compile with 32 bit. It requires the "gcc-multilib" and "libc6-dev-i386" packages.
* -lm : Compile including math library.
□ __asm__ : This indicates that the next one is an inline assembly.
□ __volatile__ : The compiler leaves the source code as entered by the programmer. It doesn't optimize the source code, so there is no bug caused by optimization(Remove variable etc.).
□ movntps : Storing packed single-precision floating-point values using non-temporal hint
* Single-precision 단정도 : Computing by basic length of a computer operation(=Word, 4 Bytes) ↔ n-precision n배정도 : computing by n times length(double, quadruple etc.).
* Floating-point 부동 소수점 : Expressing a value as significand*base^exponent 가수*밑^지수
* Non-temporal hint : Using a Write Combining
* The memory operand must be aligned on a 16-byte (128-bit version) or 32-byte (VEX.256 encoded version) boundary otherwise a general-protection exception (#GP) will be generated. The reference is here(Link)
□ movdqa : Moving aligned double quadword
* When the source or destination operand is a memory operand, the operand must be aligned on a 32-byte boundary or a general-protection exception (#GP) will be generated. The reference is here(Link).
□ 16(%1) : Address+16 of %1(=dest)






I downloaded full code and added the above code to check the "dest" address.






I found following rules in 32~64 section.

- Inputting 37~44 → malloc allocates 48 Bytes.
- Inputting 45~52 → malloc allocates 56 Bytes.
- Inputting 53~60 → malloc allocates 64 Bytes.

malloc allocates the size of the "input value + 4" in multiples of 8 bytes.

Because the address of "dest" must end like 0x ~~~10, 0x ~~~20, 0x~~~30, I made the input list as below.

[8~16] 0x~~~08 → 0x~~~20 : 24 Bytes is needed(Input 13 Bytes to make 17 Bytes)
[16~32] 0x~~~20 → 0x~~~40 : 32 Bytes is needed(Input 21 Bytes to make 25 Bytes)
[32~64] 0x~~~40 → 0x~~~70 : 48 Bytes is needed(Input 37 Bytes to make 41 Bytes)
[64~128] 0x~~~70 → 0x~~~C0 : 80 Bytes is needed(Input 69 Bytes to make 73 Bytes)
[128~256] 0x~~~C0 → 0x~~~150 : 144 Bytes is needed(input 133 Bytes to make 137 Bytes)

13 → 21 → 37 → 69 → 133 → (n=(n-1)*2-5) → 261 → 517 → 1029 → 2053 → 4101

In fact, values less than 64 bytes don't need to calculate the memory address because "slow_memcpy()" is executed, but I calculated it for formula derivation.

My Linux allocates memory starting with "0x~~~08" but pwnable.kr may allocates another address, so there may be an error. In this case, modify the value of below 64. This moves whole data gradually.

September 08, 2017

pwnable.kr codemap solution



I have a bianry that has a lot information inside heap.
How fast can you reverse-engineer this?






The hint talks me that I should check the address "0x403E65". But in my assembled code(by OllyDbg), the address was not exist. So I did what I wanted.

① I found the string "the al~" in "Search for→All referenced text Strings". When I entered, there were related assembly codes.

② There is a consistent pattern. "[EBP-xx]'s value→EAX→CALL 01083FE7". The "CALL 01083FE7" is looks like a print function. Because there is only one address that is called to print the value. Therefore, The "EAX" is likely to have the size of a chunk and also the "ECX" is likely to have the value of a chunk.

③ I set the breakpoint to check my assumption at "01083E67". Slightly above, "[EBP-54]" and "EAX" are compared (by "CMP"). The execution flow is jumped (by "JBE") another place if "EAX" is smaller than the "[EBP-54]".





It was correct.

"EAX(=EBP-54)" is the size of a chunk and "EBX(=EBP-60)" is the value of a chunk. And I collected some values at the breakpoint. It was possible because the location of the breakpoint is inside the loop always getting the biggest chunk value. There were 6 values there(The total number of chunks is 1000).

Finding 2nd and 3rd biggest value of chunks is the quiz. What I found values is incorrect. Because the loop always finds the biggest value, It doesn't mind 2nd and 3rd values. It means If the biggest value had been first discovered, I could have collected just one value.





So I set the conditional breakpoint at the loop entrance for finding values bigger than "000184CF". 4 values were found.





P.S
It looks simple, but I wandered from place to place a lot. Originally to solve, IDA's codemap plugin was supposed to be used.

September 06, 2017

pwnable.kr uaf solution


Mommy, what is Use After Free bug?





UAF(Use After Free) bug happens when reallocating memory of the same size. When memory is freed, the data in the memory is still there. It dosen't disappear. And when same size of memory should be allocated, the memory that was freed previously is reallocated containing the previous data. If there is malicious action after it is freed, the malicious data is allocated. This is UAF bug.

In the c++ code above, There are 3 cases in the "switch()". "case 1:" is the action of allocating memory, "case 2:" is  the malicious action, "case 3:" is action of freeing memory. Therefore, in order to implement the UAF bug, You must execute with order 1→3→2→1.

What is important is how to do malicious activity in "case2:". It would be good to use "give_shell ()" of "Class Human". Let's take a closer look.





First I checked the memory address of some functions.
□ 0x40117a = give_shell()
□ 0x401192 = Human->introduce()
□ 0x401376 = Woman->introduce()
□ 0x4012d2 = Man->introduce()





① "eax" is compare with "0x1" and It goes to <main+265>. This means that it is "case 1:"

② "case 1:" is between two same "jmp 0x4010a9(="break;")" and it is separated two section by same pattern.

③④ The data of "rax" is moved in "rdx" and "rdx" is called.  Therefore, the "rax" at point ③ will be pointing "introduce()" method.

So I set breakpoint at <+265>(=0x400fcd) to check "[rbp-038]" that is the source of the "rdx". The information of the Human class may be there.





I checked the address and found "give_shell ()". therefore, "0x00401570" should be in "rax" at point ③ or "0x0040117a" should be in at point ④.

Now let's see the effect of "case 2:".





I inputtedThe 4 bytes("\x42\x42\x42\x42") for "argv[1]" and the path "/tmp/uaftlext" for "argv[2]" for the program. And I selected in order 1→3→2→2→1. As a result, "rax" has been replaced with "0x42424242" + "0x08". More than twice inputting data(2) is effective, the once is not.





I inputted the value "0x00401568" = "0x00401570" - "0x08".

September 03, 2017

pwnable.kr cmd2 solution



Daddy bought me a system command shell.
but he put some filters to prevent me from playing with it without the permission.
but i wanna play anytime I want!





It is the quiz that executes flag with the permission of "cmd2". All environment variable is removed by the "memset". Also "=", "PATH", "export", "/", "`", "flag" keywords are not available for "argv[1]".





A soft-link is needed because of the filtering(prohibiting the "flag" keyword).

I surrounded the whole with single quotes. because double quote interprets inner contents. This may be caught by the filtering.

"echo" is shell builtin command so it is available even if the environment variable PATH is removed.

"\57" is octal data, it is same as "/". I want to use hexadecimal data "\x2F(= / )", but the bourne shell can't interpret it. "system()" function uses bourne shell internally(=sh -c ~).
* check ASCII code : [Link]

The data in the single quote is delivered to "system()", but It just a string(no execution). To execute it, it has to be surrounded by back quotes. But I used $() because back quote(`) is blocked.

September 01, 2017

pwnable.kr cmd1 solution


Mommy! what is PATH environment in Linux?





There is "cmd1" and "cmd.c". The cmd1 program changes PATH environment, blocks keywords which is "flag", "sh", "tmp", executes the parameter "argv[1]".





Because of the changing PATH, absolute path is needed to execute something.





There are two ways I checked.
1. Read the "flag" file with wildcard strings.
2. Read the "flag" file with soft-links.

FYI.
Initially, I thought that the current path(.) should be in the PATH environment variable in order to treat fg_tt as a relative path. But it was wrong. relative file path is available without registering current(.) path(Like above image). The reason is here(Link)